diff options
418 files changed, 41012 insertions, 4765 deletions
diff --git a/.gitignore b/.gitignore index ab7cc5149..3c4e4e115 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ debian *~ *swp +*.pyc diff --git a/.gitmodules b/.gitmodules index b625125db..473a21289 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "fpga-src"] path = fpga-src url = https://github.com/EttusResearch/fpga.git - branch = UHD-3.9.LTS + branch = maint @@ -1,6 +1,27 @@ Change Log for Releases ============================== +## 003.010.000.000 +- Changed version string to quadruplets (Major.API.ABI.Patch) +- Minimum dependencies bumped for gcc, Boost, CMake, clang and Python. +- TwinRX: Added support. Includes LO API for multi_usrp. +- N230: Added support +- Added expert framework +- X300: Completely restructured to use RFNoC +- X300: FPGA builds include git hash, dual 10GigE receive is now supported + (allows 2x200 Msps receive over 2x10GigE connections), DMA FIFO (over DRAM) + now part of builds, added Aurora support +- WBX: Fixed bug that prevented LO locking with 50 MHz ref clock +- pkg-config: Added boost_system +- Utils: uhd_usrp_probe can query sensors, query_gpsdo_sensors: minor fixes, + and cleanup +- Examples: Bugfixes in tx_waveforms, benchmark_rate measures timeouts, +- USB subsystem: Cleanups and minor bugfixes +- Added devtest infrastructure +- Converters: Added s8 and s16 data types +- Added more aggressive optimization strategies for FPGA builds +- Xilinx IP tool upgrade scripts cleaned up + ## 003.009.004 - GPIO control: Fix address mismatch for RX and full duplex. This fixes full-duplex mode for most devices. diff --git a/firmware/usrp3/CMakeLists.txt b/firmware/usrp3/CMakeLists.txt index f71b79b0c..66a43b6bd 100644 --- a/firmware/usrp3/CMakeLists.txt +++ b/firmware/usrp3/CMakeLists.txt @@ -25,6 +25,10 @@ SET(CMAKE_SYSTEM_NAME Generic) CMAKE_FORCE_C_COMPILER(zpu-elf-gcc GNU) PROJECT(USRP3_FW C) +SET(UHD_VERSION_HASH 0 CACHE INTEGER "UHD Version Hash") +EXECUTE_PROCESS(COMMAND ${CMAKE_SOURCE_DIR}/utils/git-hash.sh OUTPUT_VARIABLE UHD_VERSION_HASH) +ADD_DEFINITIONS(-DUHD_VERSION_HASH=0x${UHD_VERSION_HASH}) + INCLUDE_DIRECTORIES(include) find_package(PythonInterp) @@ -130,3 +134,4 @@ ENDMACRO(GEN_OUTPUTS) ######################################################################## ADD_SUBDIRECTORY(lib) ADD_SUBDIRECTORY(x300) +ADD_SUBDIRECTORY(n230) diff --git a/firmware/usrp3/include/cron.h b/firmware/usrp3/include/cron.h new file mode 100644 index 000000000..2d43d97f5 --- /dev/null +++ b/firmware/usrp3/include/cron.h @@ -0,0 +1,75 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_CRON_H +#define INCLUDED_CRON_H + +#include <stdint.h> +#include <stdbool.h> + +#define CRON_MAX_JOBS 4 + +typedef enum { + SEC = 1, MILLISEC = 1000, MICROSEC = 1000000 +} cron_time_unit_t; + +typedef uint32_t (*cron_counter_fetcher_t)(); + +/*! + * \brief Initialize cron subsystem with a mechanism to fetch a counter and its frequency + */ +void cron_init(const cron_counter_fetcher_t fetch_counter, uint32_t counter_freq); + +/*! + * \brief Get the hardware tick count + */ +uint32_t cron_get_ticks(); + +/*! + * \brief Get the time elapsed between start and stop in the specified units + */ +uint32_t get_elapsed_time(uint32_t start_ticks, uint32_t stop_ticks, cron_time_unit_t unit); + +/*! + * \brief Sleep (spinloop) for about 'ticks' counter ticks + * Use only if simulating, _very_ short delay + */ +void sleep_ticks(uint32_t ticks); + +/*! + * \brief Sleep (spinloop) for about 'duration' microseconds + * Use only if simulating, _very_ short delay + */ +void sleep_us(uint32_t duration); + +/*! + * \brief Sleep (spinloop) for about 'duration' milliseconds + * Use only if simulating, _very_ short delay + */ +void sleep_ms(uint32_t duration); + +/*! + * \brief Initialize a unique cron job with 'job_id' and interval 'interval_ms' + */ +void cron_job_init(uint32_t job_id, uint32_t interval_ms); + +/*! + * \brief Check if cron job with 'job_id' is due for execution + */ +bool cron_job_run_due(uint32_t job_id); + +#endif /* INCLUDED_CRON_H */ diff --git a/firmware/usrp3/include/flash/spi_flash.h b/firmware/usrp3/include/flash/spi_flash.h new file mode 100644 index 000000000..8ed73f648 --- /dev/null +++ b/firmware/usrp3/include/flash/spi_flash.h @@ -0,0 +1,92 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef INCLUDED_SPI_FLASH +#define INCLUDED_SPI_FLASH + +#include <wb_spi.h> + +//Device class that encapsulates the geometry and control +//interface for the flash chip +typedef struct { + uint32_t page_size; //in bytes + uint32_t sector_size; //in bytes + uint32_t num_sectors; + const wb_spi_slave_t* bus; +} spi_flash_dev_t; + +//Low level device specific operations +typedef uint16_t (*spif_read_id_fn_t)(const spi_flash_dev_t* flash); +typedef void (*spif_read_fn_t)(const spi_flash_dev_t* flash, uint32_t offset, void *buf, uint32_t num_bytes); +typedef bool (*spif_erase_sector_dispatch_fn_t)(const spi_flash_dev_t* flash, uint32_t offset); +typedef bool (*spif_erase_sector_commit_fn_t)(const spi_flash_dev_t* flash, uint32_t offset); +typedef bool (*spif_erase_sector_busy_fn_t)(const spi_flash_dev_t* flash); +typedef bool (*spif_write_page_dispatch_fn_t)(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); +typedef bool (*spif_write_page_commit_fn_t)(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); +typedef bool (*spif_write_page_busy_fn_t)(const spi_flash_dev_t* flash); + +//Interface struct for all low level device operations +typedef struct { + spif_read_id_fn_t read_id; + spif_read_fn_t read; + spif_erase_sector_dispatch_fn_t erase_sector_dispatch; + spif_erase_sector_commit_fn_t erase_sector_commit; + spif_erase_sector_busy_fn_t erase_sector_busy; + spif_write_page_dispatch_fn_t write_page_dispatch; + spif_write_page_commit_fn_t write_page_commit; + spif_write_page_busy_fn_t write_page_busy; +} spi_flash_ops_t; + +typedef enum { + IDLE, WRITE_IN_PROGRESS, ERASE_IN_PROGRESS +} spi_flash_state_t; + +//A session struct that encapsulates everything about the flash +//in a device agnostic way +typedef struct { + const spi_flash_dev_t* device; + const spi_flash_ops_t* ops; + spi_flash_state_t state; + uint32_t last_offset; + uint16_t id; +} spi_flash_session_t; + +/*! + * Initialize the spi_flash_session_t object + */ +void spif_init(spi_flash_session_t* flash, const spi_flash_dev_t* device, const spi_flash_ops_t* ops); + +/*! + * Read "num_bytes" from "offset" in the flash into the buffer "buf". + * This call will block until all data is available. + */ +void spif_read_sync(const spi_flash_session_t* flash, uint32_t offset, void *buf, uint32_t num_bytes); + +/*! + * Erase sector at "offset" in the flash. + * This call will block until the erase is complete. + */ +bool spif_erase_sector_sync(const spi_flash_session_t* flash, uint32_t offset); + +/*! + * Write "num_bytes" from buffer "buf" at "offset" in the flash. + * This call will block until the write is complete. + */ +bool spif_write_page_sync(const spi_flash_session_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); + + +#endif /* INCLUDED_SPI_FLASH */ diff --git a/firmware/usrp3/include/flash/spif_spsn_s25flxx.h b/firmware/usrp3/include/flash/spif_spsn_s25flxx.h new file mode 100644 index 000000000..1e6eededf --- /dev/null +++ b/firmware/usrp3/include/flash/spif_spsn_s25flxx.h @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef INCLUDED_SPIF_SPSN_S25FLXX_H +#define INCLUDED_SPIF_SPSN_S25FLXX_H + +#include <flash/spi_flash.h> + +const spi_flash_ops_t* spif_spsn_s25flxx_operations(); + +uint16_t spif_spsn_s25flxx_read_id(const spi_flash_dev_t* flash); + +void spif_spsn_s25flxx_read(const spi_flash_dev_t* flash, uint32_t offset, void *buf, uint32_t num_bytes); + +bool spif_spsn_s25flxx_erase_sector_dispatch(const spi_flash_dev_t* flash, uint32_t offset); + +bool spif_spsn_s25flxx_erase_sector_commit(const spi_flash_dev_t* flash, uint32_t offset); + +bool spif_spsn_s25flxx_write_page_dispatch(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); + +bool spif_spsn_s25flxx_write_page_commit(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); + +bool spif_spsn_s25flxx_device_busy(const spi_flash_dev_t* flash); + +#endif /* INCLUDED_SPIF_SPSN_S25FLXX_H */ diff --git a/firmware/usrp3/include/mdelay.h b/firmware/usrp3/include/mdelay.h deleted file mode 100644 index 226bbb3f7..000000000 --- a/firmware/usrp3/include/mdelay.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -*- c -*- */ -/* - * Copyright 2007 Free Software Foundation, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef INCLUDED_MDELAY_H -#define INCLUDED_MDELAY_H - -/*! - * \brief Delay about ms milliseconds - * - * If simulating, _very_ short delay - */ -void mdelay(int ms); - -#endif /* INCLUDED_MDELAY_H */ diff --git a/firmware/usrp3/include/trace.h b/firmware/usrp3/include/trace.h index 0daa231fe..015ae9049 100644 --- a/firmware/usrp3/include/trace.h +++ b/firmware/usrp3/include/trace.h @@ -31,6 +31,7 @@ * An alternate way of defining the level is the "TRACE_LEVEL" * variable in cmake. (eg. -DTRACE_LEVEL=13). */ + //#define UHD_FW_TRACE_LEVEL 13 typedef enum diff --git a/firmware/usrp3/include/wb_soft_reg.h b/firmware/usrp3/include/wb_soft_reg.h new file mode 100644 index 000000000..cbfc311bb --- /dev/null +++ b/firmware/usrp3/include/wb_soft_reg.h @@ -0,0 +1,135 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_SOFT_REG_H +#define INCLUDED_SOFT_REG_H + +#include <stdint.h> +#include <stdbool.h> +#include <wb_utils.h> + +/* Keeps track of all metadata associated with a soft register. + * Use this struct when you need to manage a hardware register that needs + * to be accessed from different sections of software. If a register contains + * several unrelated bitfields, this object can be used to ensure coherency. + * It is recommended that the client hold this as a global/static object. + */ +typedef struct +{ + uint32_t wr_addr; + uint32_t rd_addr; + uint32_t soft_copy; +} soft_reg_t; + +/* A register field is defined as a tuple of the mask and the shift. + * It can be used to make read-modify-write operations more convenient + * For efficiency reasons, it is recommended to always use a constant + * of this type because it will get optimized out by the compiler and + * will result in zero memory overhead + */ +typedef struct +{ + uint8_t num_bits; + uint8_t shift; +} soft_reg_field_t; + + +/*! + * Initialize the soft_reg_t struct as a read-write register. + * Params: + * - reg: Pointer to the soft_reg struct + * - wr_addr: The address used to flush the register to HW + * - rd_addr: The address used to read the register from HW + */ +static inline void initialize_readwrite_soft_reg(soft_reg_t* reg, uint32_t wr_addr, uint32_t rd_addr) +{ + reg->wr_addr = wr_addr; + reg->rd_addr = rd_addr; + reg->soft_copy = 0; +} + +/*! + * Initialize the soft_reg_t struct as a write-only register. + * Params: + * - reg: Pointer to the soft_reg struct + * - addr: The address used to flush the register to HW + */ +static inline void initialize_writeonly_soft_reg(soft_reg_t* reg, uint32_t addr) +{ + reg->wr_addr = addr; + reg->rd_addr = 0; + reg->soft_copy = 0; +} + +/*! + * Update specified field in the soft-copy with the arg value. + * Performs a read-modify-write operation so all other field are preserved. + * NOTE: This does not write the value to hardware. + */ +static inline void soft_reg_set(soft_reg_t* reg, const soft_reg_field_t field, const uint32_t field_value) +{ + const uint32_t mask = ((1<<field.num_bits)-1)<<field.shift; + reg->soft_copy = (reg->soft_copy & ~mask) | ((field_value << field.shift) & mask); +} + +/*! + * Write the contents of the soft-copy to hardware. + */ +static inline void soft_reg_flush(const soft_reg_t* reg) +{ + wb_poke32(reg->wr_addr, reg->soft_copy); +} + +/*! + * Shortcut for a set and a flush. + */ +static inline void soft_reg_write(soft_reg_t* reg, const soft_reg_field_t field, const uint32_t field_value) +{ + soft_reg_set(reg, field, field_value); + soft_reg_flush(reg); +} + +/*! + * Get the value of the specified field from the soft-copy. + * NOTE: This does not read anything from hardware. + */ +static inline uint32_t soft_reg_get(const soft_reg_t* reg, const soft_reg_field_t field) +{ + const uint32_t mask = ((1<<field.num_bits)-1)<<field.shift; + return (reg->soft_copy & mask) >> field.shift; +} + +/*! + * Read the contents of the register from hardware and update the soft copy. + */ +static inline void soft_reg_refresh(soft_reg_t* reg) +{ + if (reg->rd_addr) { + reg->soft_copy = wb_peek32(reg->rd_addr); + } +} + +/*! + * Shortcut for refresh and get + */ +static inline uint32_t soft_reg_read(soft_reg_t* reg, const soft_reg_field_t field) +{ + soft_reg_refresh(reg); + return soft_reg_get(reg, field); +} + +#endif /* INCLUDED_SOFT_REG_H */ diff --git a/firmware/usrp3/include/wb_spi.h b/firmware/usrp3/include/wb_spi.h new file mode 100644 index 000000000..ebbb20b16 --- /dev/null +++ b/firmware/usrp3/include/wb_spi.h @@ -0,0 +1,55 @@ + +// Copyright 2012 Ettus Research LLC + +#ifndef INCLUDED_WB_SPI_H +#define INCLUDED_WB_SPI_H + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> + +typedef enum { + WRITE, WRITE_READ +} wb_spi_rw_mode_t; + +typedef enum { + RISING, FALLING +} wb_spi_edge_t; + +typedef struct { + void* base; + uint32_t slave_sel; + uint32_t clk_div; + wb_spi_edge_t mosi_edge; + wb_spi_edge_t miso_edge; + bool lsb_first; +} wb_spi_slave_t; + +/*! + * \brief Initialize SPI slave device + */ +void wb_spi_init(const wb_spi_slave_t* slave); + +/*! + * \brief Perform a SPI transaction in auto chip-select mode. + */ +inline void wb_spi_transact(const wb_spi_slave_t* slave, + wb_spi_rw_mode_t rw_mode, const void* mosi_buf, void* miso_buf, uint32_t length); + +/*! + * \brief Perform a SPI transaction in manual chip-select mode. + */ +inline void wb_spi_transact_man_ss(const wb_spi_slave_t* slave, + wb_spi_rw_mode_t rw_mode, const void* mosi_buf, void* miso_buf, uint32_t length); + +/*! + * \brief Select SPI slave + */ +void wb_spi_slave_select(const wb_spi_slave_t* slave); + +/*! + * \brief Deselect SPI slave + */ +void wb_spi_slave_deselect(const wb_spi_slave_t* slave); + +#endif /* INCLUDED_WB_SPI_H */ diff --git a/firmware/usrp3/lib/CMakeLists.txt b/firmware/usrp3/lib/CMakeLists.txt index 621b9b611..9d9ee3c6c 100644 --- a/firmware/usrp3/lib/CMakeLists.txt +++ b/firmware/usrp3/lib/CMakeLists.txt @@ -21,12 +21,16 @@ add_library(usrp3fw STATIC udp_uart.c wb_uart.c wb_i2c.c + wb_spi.c printf.c wb_pkt_iface64.c u3_net_stack.c ethernet.c - mdelay.c chinch.c print_addrs.c link_state_route_proto.c + cron.c + fw_comm_protocol.c + flash/spi_flash.c + flash/spif_spsn_s25flxx.c ) diff --git a/firmware/usrp3/lib/cron.c b/firmware/usrp3/lib/cron.c new file mode 100644 index 000000000..24b8feb4e --- /dev/null +++ b/firmware/usrp3/lib/cron.c @@ -0,0 +1,92 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "cron.h" + +//Counter specific +static cron_counter_fetcher_t cron_fetch_counter; +static uint32_t cron_counter_freq; + +//Cron job specific +typedef struct { + uint32_t tick_interval; + uint32_t last_tick_count; +} cron_job_t; + +static cron_job_t cron_job_table[CRON_MAX_JOBS]; + +void cron_init(const cron_counter_fetcher_t fetch_counter, uint32_t counter_freq) +{ + cron_fetch_counter = fetch_counter; + cron_counter_freq = counter_freq; + + for (int i = 0; i < CRON_MAX_JOBS; i++) { + cron_job_table[i].tick_interval = 0; + } +} + +uint32_t cron_get_ticks() +{ + return cron_fetch_counter(); +} + +uint32_t get_elapsed_time(uint32_t start_ticks, uint32_t stop_ticks, cron_time_unit_t unit) +{ + return ((stop_ticks - start_ticks) / cron_counter_freq) * ((uint32_t)unit); +} + +void sleep_ticks(uint32_t ticks) +{ + if (ticks == 0) return; //Handle the 0 delay case quickly + + const uint32_t ticks_begin = cron_fetch_counter(); + while(cron_fetch_counter() - ticks_begin < ticks) { + /*NOP: Spinloop*/ + } +} + +void sleep_us(uint32_t duration) +{ + sleep_ticks((duration * (cron_counter_freq/1000000))); +} + +void sleep_ms(uint32_t duration) +{ + sleep_ticks((duration * (cron_counter_freq/1000))); +} + +void cron_job_init(uint32_t job_id, uint32_t interval_ms) +{ + cron_job_table[job_id].tick_interval = (interval_ms * (cron_counter_freq/1000)); + cron_job_table[job_id].last_tick_count = 0; +} + +bool cron_job_run_due(uint32_t job_id) +{ + uint32_t new_tick_count = cron_fetch_counter(); + bool run_job = (new_tick_count - cron_job_table[job_id].last_tick_count) >= + cron_job_table[job_id].tick_interval; + + if (run_job) { + //If the job is due to run, update the tick count for the next run + //The assumption here is that the caller will actually run their job + //when the return value is true. If not, the caller just missed this + //iteration and will have to option to run the job in the next pass through. + cron_job_table[job_id].last_tick_count = new_tick_count; + } + return run_job; +} diff --git a/firmware/usrp3/lib/ethernet.c b/firmware/usrp3/lib/ethernet.c index 91efbfe1d..e5226489f 100644 --- a/firmware/usrp3/lib/ethernet.c +++ b/firmware/usrp3/lib/ethernet.c @@ -21,7 +21,7 @@ #endif #include "../x300/x300_defs.h" #include "ethernet.h" -#include "mdelay.h" +#include "cron.h" #include <trace.h> #include "wb_i2c.h" #include "wb_utils.h" @@ -161,8 +161,8 @@ ge_write_mdio(const uint32_t base, const uint32_t address, const uint32_t port, static uint32_t read_mdio(const uint8_t eth, const uint32_t address, const uint32_t device, const uint32_t port) { - const uint32_t rb_addr = (eth==0) ? RB_ETH_TYPE0 : RB_ETH_TYPE1; - const uint32_t base = (eth==0) ? XGE0_BASE : XGE1_BASE; + const uint32_t rb_addr = (eth==0) ? RB_SFP0_TYPE : RB_SFP1_TYPE; + const uint32_t base = (eth==0) ? SFP0_MAC_BASE : SFP1_MAC_BASE; if (wb_peek32(SR_ADDR(RB0_BASE, rb_addr)) != 0) { return xge_read_mdio(base, address, device, port); @@ -175,8 +175,8 @@ static uint32_t read_mdio(const uint8_t eth, const uint32_t address, const uint3 static void write_mdio(const uint8_t eth, const uint32_t address, const uint32_t device, const uint32_t port, const uint32_t data) { - const uint32_t rb_addr = (eth==0) ? RB_ETH_TYPE0 : RB_ETH_TYPE1; - const uint32_t base = (eth==0) ? XGE0_BASE : XGE1_BASE; + const uint32_t rb_addr = (eth==0) ? RB_SFP0_TYPE : RB_SFP1_TYPE; + const uint32_t base = (eth==0) ? SFP0_MAC_BASE : SFP1_MAC_BASE; if (wb_peek32(SR_ADDR(RB0_BASE, rb_addr)) != 0) { return xge_write_mdio(base, address, device, port, data); @@ -220,7 +220,7 @@ xge_read_sfpp_type(const uint32_t base, const uint32_t delay_ms) int x; // Delay read of SFPP if (delay_ms) - mdelay(delay_ms); + sleep_ms(delay_ms); // Read ID code from SFP x = xge_i2c_rd(base, MODULE_DEV_ADDR, 3); // I2C Error? @@ -312,10 +312,9 @@ static void xge_mac_init(const uint32_t base) } // base is pointer to XGE MAC on Wishbone. -static void xge_phy_init(const uint8_t eth, const uint32_t mdio_port_arg) +static void xge_phy_init(const uint8_t eth, const uint32_t mdio_port) { int x; - uint32_t mdio_port = eth==0 ? 1 : mdio_port_arg; // Read LASI Ctrl register to capture state. //y = xge_read_mdio(0x9002,XGE_MDIO_DEVICE_PMA,XGE_MDIO_ADDR_PHY_A); UHD_FW_TRACE(DEBUG, "Begining XGE PHY init sequence."); @@ -323,23 +322,24 @@ static void xge_phy_init(const uint8_t eth, const uint32_t mdio_port_arg) x = read_mdio(eth, 0x0, XGE_MDIO_DEVICE_PMA,mdio_port); x = x | (1 << 15); write_mdio(eth, 0x0,XGE_MDIO_DEVICE_PMA,mdio_port,x); + uint32_t loopCount = 0; while(x&(1<<15)) { x = read_mdio(eth, 0x0,XGE_MDIO_DEVICE_PMA,mdio_port); + if( loopCount++ > 200 ) break; // usually succeeds after 22 or 23 polls } } -void update_eth_state(const uint32_t eth) +void update_eth_state(const uint32_t eth, const uint32_t sfp_type) { const bool old_link_up = links_up[eth]; - const uint32_t status_reg_addr = (eth==0) ? RB_SFPP_STATUS0 : RB_SFPP_STATUS1; - const bool is_10g = (wb_peek32(SR_ADDR(RB0_BASE, eth == 0 ? RB_ETH_TYPE0 : RB_ETH_TYPE1)) == 1); + const uint32_t status_reg_addr = (eth==0) ? RB_SFP0_STATUS : RB_SFP1_STATUS; uint32_t sfpp_status = wb_peek32(SR_ADDR(RB0_BASE, status_reg_addr)) & 0xFFFF; if ((sfpp_status & (SFPP_STATUS_RXLOS|SFPP_STATUS_TXFAULT|SFPP_STATUS_MODABS)) == 0) { //SFP+ pin state changed. Reinitialize PHY and MAC - if (is_10g) { - xge_mac_init((eth==0) ? XGE0_BASE : XGE1_BASE); - xge_phy_init(eth ,MDIO_PORT); + if (sfp_type == RB_SFP_10G_ETH) { + xge_mac_init((eth==0) ? SFP0_MAC_BASE : SFP1_MAC_BASE); + xge_phy_init(eth, MDIO_PORT); } else { //No-op for 1G } @@ -347,7 +347,7 @@ void update_eth_state(const uint32_t eth) int8_t timeout = 100; bool link_up = false; do { - if (is_10g) { + if (sfp_type == RB_SFP_10G_ETH) { link_up = ((read_mdio(eth, XGE_MDIO_STATUS1,XGE_MDIO_DEVICE_PMA,MDIO_PORT)) & (1 << 2)) != 0; } else { link_up = ((wb_peek32(SR_ADDR(RB0_BASE, status_reg_addr)) >> 16) & 0x1) != 0; @@ -362,54 +362,66 @@ void update_eth_state(const uint32_t eth) } if (!old_link_up && links_up[eth]) u3_net_stack_send_arp_request(eth, u3_net_stack_get_ip_addr(eth)); - UHD_FW_TRACE_FSTR(INFO, "The link on eth port %u is %s", eth, links_up[eth]?"up":"down"); } -void poll_sfpp_status(const uint32_t eth) +void poll_sfpp_status(const uint32_t sfp) { - uint32_t x; - // Has MODDET/MODAbS changed since we last looked? - x = wb_peek32(SR_ADDR(RB0_BASE, (eth==0) ? RB_SFPP_STATUS0 : RB_SFPP_STATUS1 )); + uint32_t type = wb_peek32(SR_ADDR(RB0_BASE, (sfp==0) ? RB_SFP0_TYPE : RB_SFP1_TYPE)); + uint32_t status = wb_peek32(SR_ADDR(RB0_BASE, (sfp==0) ? RB_SFP0_STATUS : RB_SFP1_STATUS)); - if (x & SFPP_STATUS_RXLOS_CHG) - UHD_FW_TRACE_FSTR(DEBUG, "eth%1d RXLOS changed state: %d", eth, (x & SFPP_STATUS_RXLOS)); - if (x & SFPP_STATUS_TXFAULT_CHG) - UHD_FW_TRACE_FSTR(DEBUG, "eth%1d TXFAULT changed state: %d", eth, ((x & SFPP_STATUS_TXFAULT) >> 1)); - if (x & SFPP_STATUS_MODABS_CHG) - UHD_FW_TRACE_FSTR(DEBUG, "eth%1d MODABS changed state: %d", eth, ((x & SFPP_STATUS_MODABS) >> 2)); - - //update the link up status - if ((x & SFPP_STATUS_RXLOS_CHG) || (x & SFPP_STATUS_TXFAULT_CHG) || (x & SFPP_STATUS_MODABS_CHG)) - { - update_eth_state(eth); - } - - if (x & SFPP_STATUS_MODABS_CHG) { + if (status & SFPP_STATUS_MODABS_CHG) { // MODDET has changed state since last checked - if (x & SFPP_STATUS_MODABS) { + if (status & SFPP_STATUS_MODABS) { // MODDET is high, module currently removed. - UHD_FW_TRACE_FSTR(INFO, "An SFP+ module has been removed from eth port %d.", eth); + UHD_FW_TRACE_FSTR(INFO, "An SFP+ module has been removed from eth port %d.", sfp); } else { // MODDET is low, module currently inserted. // Return status. - UHD_FW_TRACE_FSTR(INFO, "A new SFP+ module has been inserted into eth port %d.", eth); - xge_read_sfpp_type((eth==0) ? I2C0_BASE : I2C2_BASE,1); + UHD_FW_TRACE_FSTR(INFO, "A new SFP+ module has been inserted into eth port %d.", sfp); + if (type == RB_SFP_10G_ETH) { + xge_read_sfpp_type((sfp==0) ? I2C0_BASE : I2C2_BASE,1); + } } } + + if (status & SFPP_STATUS_RXLOS_CHG) { + UHD_FW_TRACE_FSTR(DEBUG, "SFP%1d RXLOS changed state: %d", sfp, (status & SFPP_STATUS_RXLOS)); + } + if (status & SFPP_STATUS_TXFAULT_CHG) { + UHD_FW_TRACE_FSTR(DEBUG, "SFP%1d TXFAULT changed state: %d", sfp, ((status & SFPP_STATUS_TXFAULT) >> 1)); + } + if (status & SFPP_STATUS_MODABS_CHG) { + UHD_FW_TRACE_FSTR(DEBUG, "SFP%1d MODABS changed state: %d", sfp, ((status & SFPP_STATUS_MODABS) >> 2)); + } + + //update the link up status + const bool old_link_up = links_up[sfp]; + if (type == RB_SFP_AURORA) { + links_up[sfp] = ((wb_peek32(SR_ADDR(RB0_BASE, (sfp==0) ? RB_SFP0_STATUS : RB_SFP1_STATUS)) >> 16) & 0x1) != 0; + } else { + if ((status & SFPP_STATUS_RXLOS_CHG) || + (status & SFPP_STATUS_TXFAULT_CHG) || + (status & SFPP_STATUS_MODABS_CHG)) + { + update_eth_state(sfp, type); + } + } + if (old_link_up != links_up[sfp]) { + UHD_FW_TRACE_FSTR(INFO, "The link on SFP port %u is %s", sfp, links_up[sfp]?"up":"down"); + } } -void ethernet_init(const uint32_t eth) +void ethernet_init(const uint32_t sfp) { #ifdef UHD_FW_TRACE_LEVEL - uint32_t x = wb_peek32(SR_ADDR(RB0_BASE, (eth==0) ? RB_SFPP_STATUS0 : RB_SFPP_STATUS1 )); - UHD_FW_TRACE_FSTR(DEBUG, "eth%1d SFP initial state: RXLOS: %d TXFAULT: %d MODABS: %d", - eth, + uint32_t x = wb_peek32(SR_ADDR(RB0_BASE, (sfp==0) ? RB_SFP0_STATUS : RB_SFP1_STATUS )); + UHD_FW_TRACE_FSTR(DEBUG, "SFP%1d SFP initial state: RXLOS: %d TXFAULT: %d MODABS: %d", + sfp, (x & SFPP_STATUS_RXLOS), ((x & SFPP_STATUS_TXFAULT) >> 1), ((x & SFPP_STATUS_MODABS) >> 2)); #endif - links_up[eth] = false; - update_eth_state(eth); + update_eth_state(sfp, wb_peek32(SR_ADDR(RB0_BASE, (sfp==0) ? RB_SFP0_TYPE : RB_SFP1_TYPE))); } // diff --git a/firmware/usrp3/lib/flash/spi_flash.c b/firmware/usrp3/lib/flash/spi_flash.c new file mode 100644 index 000000000..b4257c96f --- /dev/null +++ b/firmware/usrp3/lib/flash/spi_flash.c @@ -0,0 +1,50 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <flash/spi_flash.h> + +void spif_init(spi_flash_session_t* flash, const spi_flash_dev_t* device, const spi_flash_ops_t* ops) +{ + flash->device = device; + flash->ops = ops; + flash->state = IDLE; + flash->last_offset = 0; + flash->id = ops->read_id(device); +} + +void spif_read_sync(const spi_flash_session_t* flash, uint32_t offset, void *buf, uint32_t num_bytes) +{ + flash->ops->read(flash->device, offset, buf, num_bytes); +} + +bool spif_erase_sector_sync(const spi_flash_session_t* flash, uint32_t offset) +{ + if (flash->ops->erase_sector_dispatch(flash->device, offset)) { + return flash->ops->erase_sector_commit(flash->device, offset); + } else { + return false; + } +} + +bool spif_write_page_sync(const spi_flash_session_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes) +{ + if (flash->ops->write_page_dispatch(flash->device, offset, buf, num_bytes)) { + return flash->ops->write_page_commit(flash->device, offset, buf, num_bytes); + } else { + return false; + } +} diff --git a/firmware/usrp3/lib/flash/spif_spsn_s25flxx.c b/firmware/usrp3/lib/flash/spif_spsn_s25flxx.c new file mode 100644 index 000000000..244115b6f --- /dev/null +++ b/firmware/usrp3/lib/flash/spif_spsn_s25flxx.c @@ -0,0 +1,238 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <wb_spi.h> +#include <flash/spif_spsn_s25flxx.h> +#include <cron.h> +#include <trace.h> +#include <string.h> //for memset, memcpy + +#define S25FLXX_CMD_WIDTH 8 +#define S25FLXX_ADDR_WIDTH 24 + +/* S25FLxx-specific commands */ +#define S25FLXX_CMD_READID 0x90 /* Read Manufacturer and Device Identification */ +#define S25FLXX_CMD_READSIG 0xAB /* Read Electronic Signature (Will release from Deep PD) */ +#define S25FLXX_CMD_READ 0x03 /* Read Data Bytes */ +#define S25FLXX_CMD_FAST_READ 0x0B /* Read Data Bytes at Higher Speed */ + +#define S25FLXX_CMD_WREN 0x06 /* Write Enable */ +#define S25FLXX_CMD_WRDI 0x04 /* Write Disable */ + +#define S25FLXX_CMD_PP 0x02 /* Page Program */ +#define S25FLXX_CMD_SE 0xD8 /* Sector Erase */ +#define S25FLXX_CMD_BE 0xC7 /* Bulk Erase */ +#define S25FLXX_CMD_DP 0xB9 /* Deep Power-down */ + +#define S25FLXX_CMD_RDSR 0x05 /* Read Status Register */ +#define S25FLXX_CMD_WRSR 0x01 /* Write Status Register */ + +#define S25FLXX_STATUS_WIP 0x01 /* Write in Progress */ +#define S25FLXX_STATUS_E_ERR 0x20 /* Erase Error Occured */ +#define S25FLXX_STATUS_P_ERR 0x40 /* Programming Error Occured */ + +#define S25FLXX_SECTOR_ERASE_TIME_MS 750 //Spec: 650ms +#define S25FLXX_PAGE_WRITE_TIME_MS 1 //Spec: 750us + +#define S25FLXX_SMALL_SECTORS_PER_LOGICAL 16 //16 4-kB physical sectors per logical sector +#define S25FLXX_LARGE_SECTOR_BASE 0x20000 //Large physical sectors start at logical sector 2 + +inline static uint8_t _spif_read_status(const spi_flash_dev_t* flash) +{ + uint16_t cmd = S25FLXX_CMD_RDSR << 8, status = 0xFFFF; + wb_spi_transact(flash->bus, WRITE_READ, &cmd, &status, S25FLXX_CMD_WIDTH + 8 /* 8 bits of status */); + return status; +} + +inline static bool _spif_wait_ready(const spi_flash_dev_t* flash, uint32_t timeout_ms) +{ + uint32_t start_ticks = cron_get_ticks(); + do { + if ((_spif_read_status(flash) & S25FLXX_STATUS_WIP) == 0) { + return true; + } + } while (get_elapsed_time(start_ticks, cron_get_ticks(), MILLISEC) < timeout_ms); + + return false; // Timed out +} + +inline static void _spi_flash_set_write_enabled(const spi_flash_dev_t* flash, bool enabled) +{ + uint8_t cmd = enabled ? S25FLXX_CMD_WREN : S25FLXX_CMD_WRDI; + wb_spi_transact(flash->bus, WRITE, &cmd, NULL, S25FLXX_CMD_WIDTH); +} + +const spi_flash_ops_t spif_spsn_s25flxx_ops = +{ + .read_id = spif_spsn_s25flxx_read_id, + .read = spif_spsn_s25flxx_read, + .erase_sector_dispatch = spif_spsn_s25flxx_erase_sector_dispatch, + .erase_sector_commit = spif_spsn_s25flxx_erase_sector_commit, + .erase_sector_busy = spif_spsn_s25flxx_device_busy, + .write_page_dispatch = spif_spsn_s25flxx_write_page_dispatch, + .write_page_commit = spif_spsn_s25flxx_write_page_commit, + .write_page_busy = spif_spsn_s25flxx_device_busy +}; + +const spi_flash_ops_t* spif_spsn_s25flxx_operations() +{ + return &spif_spsn_s25flxx_ops; +} + +uint16_t spif_spsn_s25flxx_read_id(const spi_flash_dev_t* flash) +{ + wb_spi_slave_select(flash->bus); + uint32_t command = S25FLXX_CMD_READID << 24; + wb_spi_transact_man_ss(flash->bus, WRITE, &command, NULL, 32); + uint16_t id = 0; + wb_spi_transact_man_ss(flash->bus, WRITE_READ, NULL, &id, 16); + wb_spi_slave_deselect(flash->bus); + return id; +} + +void spif_spsn_s25flxx_read(const spi_flash_dev_t* flash, uint32_t offset, void *buf, uint32_t num_bytes) +{ + //We explicitly control the slave select here, so that we can + //do the entire read operation as a single transaction from + //device's point of view. (The most our SPI peripheral can transfer + //in a single shot is 16 bytes.) + + //Do the 5 byte instruction tranfer: + //FAST_READ_CMD, ADDR2, ADDR1, ADDR0, DUMMY (0) + uint8_t read_cmd[5]; + read_cmd[4] = S25FLXX_CMD_FAST_READ; + *((uint32_t*)(read_cmd + 3)) = (offset << 8); + + wb_spi_slave_select(flash->bus); + wb_spi_transact_man_ss(flash->bus, WRITE_READ, read_cmd, NULL, 5*8); + + //Read up to 4 bytes at a time until done + uint8_t data_sw[16], data[16]; + size_t xact_size = 16; + unsigned char *bytes = (unsigned char *) buf; + for (size_t i = 0; i < num_bytes; i += 16) { + if (xact_size > num_bytes - i) xact_size = num_bytes - i; + wb_spi_transact_man_ss(flash->bus, WRITE_READ, NULL, data_sw, xact_size*8); + for (size_t k = 0; k < 4; k++) { //Fix word level significance + ((uint32_t*)data)[k] = ((uint32_t*)data_sw)[3-k]; + } + for (size_t j = 0; j < xact_size; j++) { + *bytes = data[j]; + bytes++; + } + } + wb_spi_slave_deselect(flash->bus); +} + +bool spif_spsn_s25flxx_erase_sector_dispatch(const spi_flash_dev_t* flash, uint32_t offset) +{ + //Sanity check sector size + if (offset % flash->sector_size) { + UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_erase_sector: Erase offset not a multiple of sector size."); + return false; + } + + if (!_spif_wait_ready(flash, S25FLXX_SECTOR_ERASE_TIME_MS)) { + UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_erase_sector: Timeout. Sector at 0x%X was not ready for erase.", offset); + return false; + } + _spi_flash_set_write_enabled(flash, true); + + //Send sector erase command + uint32_t command = (S25FLXX_CMD_SE << 24) | (offset & 0x00FFFFFF); + wb_spi_transact(flash->bus, WRITE_READ, &command, NULL, 32); + + return true; +} + +bool spif_spsn_s25flxx_erase_sector_commit(const spi_flash_dev_t* flash, uint32_t offset) +{ + //Poll status until write done + uint8_t phy_sector_count = (offset < S25FLXX_LARGE_SECTOR_BASE) ? S25FLXX_SMALL_SECTORS_PER_LOGICAL : 1; + bool status = false; + for (uint8_t i = 0; i < phy_sector_count && !status; i++) { + status = _spif_wait_ready(flash, S25FLXX_SECTOR_ERASE_TIME_MS); + } + if (!status) { + UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_erase_sector_commit: Timeout. Sector at 0x%X did not finish erasing in time.", offset); + } + _spi_flash_set_write_enabled(flash, false); + return status; +} + +bool spif_spsn_s25flxx_write_page_dispatch(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes) +{ + if (num_bytes == 0 || num_bytes > flash->page_size) { + UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_write_page: Invalid size. Must be > 0 and <= Page Size."); + return false; + } + if (num_bytes > (flash->sector_size * flash->num_sectors)) { + UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_write_page: Cannot write past flash boundary."); + return false; + } + + //Wait until ready and enable write enabled + if (!_spif_wait_ready(flash, S25FLXX_PAGE_WRITE_TIME_MS)) { + UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_write_page: Timeout. Page at 0x%X was not ready for write.", offset); + return false; + } + _spi_flash_set_write_enabled(flash, true); + + //We explicitly control the slave select here, so that we can + //do the entire read operation as a single transaction from + //device's point of view. (The most our SPI peripheral can transfer + //in a single shot is 16 bytes.) + + //Do the 4 byte instruction tranfer: + //PP_CMD, ADDR2, ADDR1, ADDR0 + uint32_t write_cmd = (S25FLXX_CMD_PP << 24) | (offset & 0x00FFFFFF); + + wb_spi_slave_select(flash->bus); + wb_spi_transact_man_ss(flash->bus, WRITE, &write_cmd, NULL, 32); + + //Write the page 16 bytes at a time. + uint8_t bytes_sw[16]; + uint8_t* bytes = (uint8_t*) buf; + for (int32_t bytes_left = num_bytes; bytes_left > 0; bytes_left -= 16) { + const uint32_t xact_size = (bytes_left < 16) ? bytes_left : 16; + for (size_t k = 0; k < 4; k++) { //Fix word level significance + ((uint32_t*)bytes_sw)[k] = ((uint32_t*)bytes)[3-k]; + } + wb_spi_transact_man_ss(flash->bus, WRITE, bytes_sw, NULL, xact_size * 8); + bytes += xact_size; + } + wb_spi_slave_deselect(flash->bus); + + return true; +} + +bool spif_spsn_s25flxx_write_page_commit(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes) +{ + //Wait until write done + if (!_spif_wait_ready(flash, S25FLXX_PAGE_WRITE_TIME_MS)) { + UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_commit_write: Timeout. Page did not finish writing in time."); + return false; + } + _spi_flash_set_write_enabled(flash, false); + return true; +} + +bool spif_spsn_s25flxx_device_busy(const spi_flash_dev_t* flash) +{ + return (_spif_read_status(flash) & S25FLXX_STATUS_WIP); +} + diff --git a/firmware/usrp3/lib/fw_comm_protocol.c b/firmware/usrp3/lib/fw_comm_protocol.c new file mode 100644 index 000000000..0cc931a76 --- /dev/null +++ b/firmware/usrp3/lib/fw_comm_protocol.c @@ -0,0 +1,105 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "../../../host/lib/usrp/common/fw_comm_protocol.h" + +#include <trace.h> +#include <string.h> //memcmp + +bool process_fw_comm_protocol_pkt( + const fw_comm_pkt_t* request, + fw_comm_pkt_t* response, + uint8_t product_id, + uint32_t iface_id, + poke32_func poke_callback, + peek32_func peek_callback) +{ + bool send_response = false; + + uint16_t signature = request->id; + uint8_t version = FW_COMM_GET_PROTOCOL_VER(request->id); + uint8_t product = FW_COMM_GET_PRODUCT_ID(request->id); + if (signature == FW_COMM_PROTOCOL_SIGNATURE && //Verify protocol + version <= FW_COMM_PROTOCOL_VERSION && //Verify protocol version (older versions supported) + product == product_id) //Verify device + { + //Request is valid. Copy it into the reply. + memcpy(response, request, sizeof(fw_comm_pkt_t)); + + //Start assuming no error + response->flags &= ~FW_COMM_FLAGS_ERROR_MASK; + + //Otherwise, run the command set by the flags + switch (request->flags & FW_COMM_FLAGS_CMD_MASK) { + case FW_COMM_CMD_ECHO: { + UHD_FW_TRACE(DEBUG, "fw_comm_protocol::echo()"); + response->data_words = 1; + response->data[0] = iface_id; + } break; + + case FW_COMM_CMD_POKE32: { + UHD_FW_TRACE_FSTR(DEBUG, "fw_comm_protocol::poke32(0x%x)=0x%x", + request->addr,*(request->data)); + poke_callback(request->addr, *(request->data)); + } break; + + case FW_COMM_CMD_PEEK32: { + *(response->data) = peek_callback(request->addr); + UHD_FW_TRACE_FSTR(DEBUG, "fw_comm_protocol::peek32(0x%x)=0x%x", + request->addr,*(response->data)); + } break; + + case FW_COMM_CMD_BLOCK_POKE32: { + if (request->data_words > FW_COMM_MAX_DATA_WORDS) { + response->flags |= FW_COMM_ERR_SIZE_ERROR; + response->data_words = FW_COMM_MAX_DATA_WORDS; + } else { + response->data_words = request->data_words; + } + UHD_FW_TRACE_FSTR(DEBUG, "fw_comm_protocol::block_poke32(0x%x,%d)",request->addr,response->data_words); + for (uint32_t i = 0; i < response->data_words; i++) { + poke_callback(request->addr + (i * sizeof(uint32_t)), request->data[i]); + } + } break; + + case FW_COMM_CMD_BLOCK_PEEK32: { + if (request->data_words > FW_COMM_MAX_DATA_WORDS) { + response->flags |= FW_COMM_ERR_SIZE_ERROR; + response->data_words = FW_COMM_MAX_DATA_WORDS; + } else { + response->data_words = request->data_words; + } + for (uint32_t i = 0; i < response->data_words; i++) { + response->data[i] = peek_callback(request->addr + (i * sizeof(uint32_t))); + } + UHD_FW_TRACE_FSTR(DEBUG, "fw_comm_protocol::block_peek32(0x%x,%d)",request->addr,response->data_words); + } break; + + default: { + UHD_FW_TRACE(ERROR, "fw_comm_protocol got an invalid command."); + response->flags |= FW_COMM_ERR_CMD_ERROR; + } + } + + //Send a reply if ack requested + send_response = (request->flags & FW_COMM_FLAGS_ACK); + } else { //Size, protocol, product check failed + UHD_FW_TRACE(WARN, "fw_comm_protocol ignored an unknown request."); + send_response = false; + } + return send_response; +} diff --git a/firmware/usrp3/lib/mdelay.c b/firmware/usrp3/lib/mdelay.c deleted file mode 100644 index 6d2742206..000000000 --- a/firmware/usrp3/lib/mdelay.c +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- c -*- */ -/* - * Copyright 2007 Free Software Foundation, Inc. - * Copyright 2009 Ettus Research LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "mdelay.h" -#include "wb_utils.h" -#include "printf.h" -#include <stdint.h> -//IJB FIXME. -#include "../x300/x300_defs.h" - -void mdelay(int ms){ - for(int i = 0; i < ms; i++){ - static const uint32_t num_ticks = CPU_CLOCK/1000; - const uint32_t ticks_begin = wb_peek32(SR_ADDR(RB0_BASE, RB_COUNTER)); - // printf("DEBUG: Counter is %d\n",ticks_begin); - while((wb_peek32(SR_ADDR(RB0_BASE, RB_COUNTER)) - ticks_begin) < num_ticks) { - /*NOP*/ - } - } -} diff --git a/firmware/usrp3/lib/wb_spi.c b/firmware/usrp3/lib/wb_spi.c new file mode 100644 index 000000000..04904feea --- /dev/null +++ b/firmware/usrp3/lib/wb_spi.c @@ -0,0 +1,206 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <wb_spi.h> +#include <trace.h> + +typedef struct { + volatile uint32_t data0; + volatile uint32_t data1; + volatile uint32_t data2; + volatile uint32_t data3; + volatile uint32_t ctrl_status; + volatile uint32_t clkdiv; + volatile uint32_t slavesel; +} wb_spi_regs_t; + +#define WB_SPI_REGS(base) ((wb_spi_regs_t *) base) + +// Masks for different parts of CTRL reg +#define WB_SPI_CTRL_AUTO_SS (1 << 13) +#define WB_SPI_CTRL_IE (1 << 12) +#define WB_SPI_CTRL_LSB (1 << 11) +#define WB_SPI_CTRL_TXNEG (1 << 10) +#define WB_SPI_CTRL_RXNEG (1 << 9) +#define WB_SPI_CTRL_GO_BSY (1 << 8) +#define WB_SPI_CTRL_LENGTH(x) (x & 0x7F) + +static inline uint32_t _wb_spi_get_flags(const wb_spi_slave_t* slave) +{ + uint32_t flags = 0; + //If the SPI slave samples on the rising edge then shift + //data out on the falling edge. + if (slave->mosi_edge == RISING) flags |= WB_SPI_CTRL_TXNEG; + //If the SPI slave drives on the rising edge then shift + //data in on the falling edge. + if (slave->miso_edge == RISING) flags |= WB_SPI_CTRL_RXNEG; + if (slave->lsb_first) flags |= WB_SPI_CTRL_LSB; + return flags; +} + +static inline void _wait_for_xfer(const wb_spi_slave_t* slave) +{ + while (WB_SPI_REGS(slave->base)->ctrl_status & WB_SPI_CTRL_GO_BSY) { + /*NOP*/ + } +} + +void wb_spi_init(const wb_spi_slave_t* slave) +{ + WB_SPI_REGS(slave->base)->clkdiv = slave->clk_div; + WB_SPI_REGS(slave->base)->slavesel = 0; + + //Do a dummy transaction with no slave selected to prime the engine + uint32_t ctrl = WB_SPI_CTRL_LENGTH(8) | _wb_spi_get_flags(slave); + WB_SPI_REGS(slave->base)->ctrl_status = ctrl | WB_SPI_CTRL_GO_BSY; + _wait_for_xfer(slave); +} + +void _wb_spi_transact_buf( + const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode, + const void* mosi_buf, void* miso_buf, uint32_t length, + bool auto_slave_sel) +{ + if (length == 0) return; + + //Wait for previous transaction to finish + _wait_for_xfer(slave); + + //Write SPI data register(s) + if (mosi_buf) { + uint8_t* mosi_bytes = (uint8_t*) mosi_buf; + uint8_t bits_left = length; + for (uint32_t reg_index = 0; reg_index < 4; reg_index++) { + uint32_t word = 0; + if (bits_left < 32) { + if (bits_left <= 8) { + word = (uint32_t) mosi_bytes[0]; + } else if (bits_left <= 16) { + word = (((uint32_t) mosi_bytes[1]) << 0) | + (((uint32_t) mosi_bytes[0]) << 8); + } else if (bits_left <= 24) { + word = (((uint32_t) mosi_bytes[2]) << 0) | + (((uint32_t) mosi_bytes[1]) << 8) | + (((uint32_t) mosi_bytes[0]) << 16); + } else { + word = *((uint32_t*) mosi_bytes); + } + bits_left = 0; + } else { + word = *((uint32_t*) mosi_bytes); + mosi_bytes += 4; + bits_left -= 32; + } + + switch (reg_index) { + case 0: WB_SPI_REGS(slave->base)->data0 = word; break; + case 1: WB_SPI_REGS(slave->base)->data1 = word; break; + case 2: WB_SPI_REGS(slave->base)->data2 = word; break; + case 3: WB_SPI_REGS(slave->base)->data3 = word; break; + } + + if (bits_left == 0) break; + } + } + + //Compute flags for slave and write control register + uint32_t ctrl = WB_SPI_CTRL_LENGTH(length) | _wb_spi_get_flags(slave); + if (auto_slave_sel) ctrl |= WB_SPI_CTRL_AUTO_SS; + WB_SPI_REGS(slave->base)->ctrl_status = ctrl; + + // Tell it which SPI slave device to access + WB_SPI_REGS(slave->base)->slavesel = slave->slave_sel; + + //Go go go! + WB_SPI_REGS(slave->base)->ctrl_status = ctrl | WB_SPI_CTRL_GO_BSY; + + if (rw_mode == WRITE_READ) { + //Wait for SPI read operation to complete + _wait_for_xfer(slave); + + if (miso_buf) { + //Read SPI data registers + uint8_t* miso_bytes = (uint8_t*) miso_buf; + uint8_t bits_left = length; + for (uint32_t reg_index = 0; reg_index < 4; reg_index++) { + uint32_t word = 0; + switch (reg_index) { + case 0: word = WB_SPI_REGS(slave->base)->data0; break; + case 1: word = WB_SPI_REGS(slave->base)->data1; break; + case 2: word = WB_SPI_REGS(slave->base)->data2; break; + case 3: word = WB_SPI_REGS(slave->base)->data3; break; + } + + if (bits_left < 32) { + if (bits_left <= 8) { + miso_bytes[0] = word & 0xFF; + } else if (bits_left <= 16) { + miso_bytes[1] = word & 0xFF; + miso_bytes[0] = (word >> 8) & 0xFF; + } else if (bits_left <= 24) { + miso_bytes[2] = word & 0xFF; + miso_bytes[1] = (word >> 8) & 0xFF; + miso_bytes[0] = (word >> 16) & 0xFF; + } else { + *((uint32_t*) miso_bytes) = word; + } + bits_left = 0; + } else { + *((uint32_t*) miso_bytes) = word; + miso_bytes += 4; + bits_left -= 32; + } + + if (bits_left == 0) break; + } + } + } +} + +void wb_spi_transact( + const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode, + const void* mosi_buf, void* miso_buf, uint32_t length) +{ + return _wb_spi_transact_buf(slave, rw_mode, mosi_buf, miso_buf, length, true); +} + +void wb_spi_transact_man_ss( + const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode, + const void* mosi_buf, void* miso_buf, uint32_t length) +{ + return _wb_spi_transact_buf(slave, rw_mode, mosi_buf, miso_buf, length, false); +} + +void wb_spi_slave_select(const wb_spi_slave_t* slave) +{ + //Wait for previous transactions to finish + _wait_for_xfer(slave); + //Disable auto slave select + WB_SPI_REGS(slave->base)->ctrl_status = _wb_spi_get_flags(slave); + //Manually select slave + WB_SPI_REGS(slave->base)->slavesel = slave->slave_sel; +} + +void wb_spi_slave_deselect(const wb_spi_slave_t* slave) +{ + //Wait for previous transactions to finish + _wait_for_xfer(slave); + //Disable auto slave select + WB_SPI_REGS(slave->base)->ctrl_status = _wb_spi_get_flags(slave); + //Manually deselect slave + WB_SPI_REGS(slave->base)->slavesel = 0; +} diff --git a/firmware/usrp3/n230/CMakeLists.txt b/firmware/usrp3/n230/CMakeLists.txt new file mode 100644 index 000000000..6247477f0 --- /dev/null +++ b/firmware/usrp3/n230/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# Copyright 2010-2014,2016 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/../../host/lib/usrp/n230) + +list(APPEND n230_sources n230_eeprom.c n230_eth_handlers.c n230_init.c n230_main.c) + +######################################################################## +set(GEN_OUTPUTS_BIN_SIZE 0x7fff) + +add_executable(n230_main.elf ${n230_sources}) +target_link_libraries(n230_main.elf usrp3fw) +GEN_OUTPUTS(n230_main.elf n230) + +#INSTALL( +# FILES ${CMAKE_CURRENT_BINARY_DIR}/n230_main.bin +# DESTINATION share/uhd/images +# RENAME usrp_n230_fw.bin +#) diff --git a/firmware/usrp3/n230/n230_burner.py b/firmware/usrp3/n230/n230_burner.py new file mode 100755 index 000000000..7b9920de7 --- /dev/null +++ b/firmware/usrp3/n230/n230_burner.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python +# +# Copyright 2014 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import optparse +import math +import socket +import struct +import os.path +import sys +from array import array + +######################################################################## +# constants +######################################################################## +N230_FLASH_COMM_UDP_PORT = 49154 +N230_FLASH_COMM_PAYLOAD_SIZE = 128 +N230_FLASH_COMM_SECTOR_SIZE = 65536 + +N230_FLASH_COMM_FLAGS_ACK = 0x00000001 +N230_FLASH_COMM_FLAGS_CMD_MASK = 0x00000FF0 +N230_FLASH_COMM_FLAGS_ERROR_MASK = 0xFF000000 + +N230_FLASH_COMM_CMD_READ_NV_DATA = 0x00000010 +N230_FLASH_COMM_CMD_WRITE_NV_DATA = 0x00000020 +N230_FLASH_COMM_CMD_READ_FPGA = 0x00000030 +N230_FLASH_COMM_CMD_WRITE_FPGA = 0x00000040 +N230_FLASH_COMM_CMD_ERASE_FPGA = 0x00000050 + +N230_FLASH_COMM_ERR_PKT_ERROR = 0x80000000 +N230_FLASH_COMM_ERR_CMD_ERROR = 0x40000000 +N230_FLASH_COMM_ERR_SIZE_ERROR = 0x20000000 + +N230_FLASH_COMM_SAFE_IMG_BASE = 0x000000 +N230_FLASH_COMM_PROD_IMG_BASE = 0x400000 +N230_FLASH_COMM_FPGA_IMG_MAX_SIZE = 0x400000 + +UDP_MAX_XFER_BYTES = 256 +UDP_TIMEOUT = 3 + +_seq = -1 +def next_seq(): + global _seq + _seq = _seq+1 + return _seq + +def seq(): + return _seq + +######################################################################## +# helper functions +######################################################################## + +short = struct.Struct('>H') +ulong = struct.Struct('>I') + +def unpack_flash_transaction(buf): + (flags, seqno, offset, size) = struct.unpack_from('!LLLL', buf) + check_error(flags) + if (seqno != seq()): + raise Exception("The flash transaction operation returned an incorrect sequence number") + data = bytes() + for i in xrange(16, len(buf), 1): + data += buf[i] + return (flags, offset, size, data) + +def pack_flash_transaction(flags, offset, size, data): + buf = bytes() + buf = struct.pack('!LLLL', flags, next_seq(), offset, size) + for i in range(N230_FLASH_COMM_PAYLOAD_SIZE): + if (i < size): + buf += struct.pack('!B', data[i]) + else: + buf += struct.pack('!B', 0) + return buf + +def check_error(flags): + if flags & N230_FLASH_COMM_ERR_PKT_ERROR == N230_FLASH_COMM_ERR_PKT_ERROR: + raise Exception("The flash transaction operation returned a packet error") + if flags & N230_FLASH_COMM_ERR_CMD_ERROR == N230_FLASH_COMM_ERR_CMD_ERROR: + raise Exception("The flash transaction operation returned a command error") + if flags & N230_FLASH_COMM_ERR_SIZE_ERROR == N230_FLASH_COMM_ERR_SIZE_ERROR: + raise Exception("The flash transaction operation returned a size error") + +def chunkify(stuff, n): + return [stuff[i:i+n] for i in range(0, len(stuff), n)] + +def draw_progress_bar(percent, bar_len = 32): + sys.stdout.write("\r") + progress = "" + for i in range(bar_len): + if i < int((bar_len * percent) / 100): + progress += "=" + else: + progress += "-" + sys.stdout.write("[%s] %d%%" % (progress, percent)) + sys.stdout.flush() + +######################################################################## +# Burner class, holds a socket and send/recv routines +######################################################################## +class ctrl_socket(object): + def __init__(self, addr): + self._safe_image = False + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.settimeout(UDP_TIMEOUT) + self._sock.connect((addr, N230_FLASH_COMM_UDP_PORT)) + self.set_callbacks(lambda *a: None, lambda *a: None) + + def set_safe_image(self, noprompt): + confirm_msg = ('----------------------------------------------------------------------\n' + 'WARNING!!! You are about to access the safe-image stored in the flash \n' + '----------------------------------------------------------------------\n' + 'Writing a non-functional image will brick the device.\n' + 'Are you sure you want to proceed?') + if not noprompt: + if raw_input("%s (y/N) " % confirm_msg).lower() == 'y': + self._safe_image = True + else: + print 'Aborted by user' + sys.exit(1) + else: + print '[WARNING] Operating on safe image without a prompt as requested' + self._safe_image = True + + def set_callbacks(self, progress_cb, status_cb): + self._progress_cb = progress_cb + self._status_cb = status_cb + + def send_and_recv(self, pkt): + self._sock.send(pkt) + return self._sock.recv(UDP_MAX_XFER_BYTES) + + def compute_offset(self, offset): + base = N230_FLASH_COMM_SAFE_IMG_BASE if (self._safe_image) else N230_FLASH_COMM_PROD_IMG_BASE + return base + offset + + def burn_fpga_to_flash(self, bitfile_path, noprompt): + print '[BURN] Reading ' + bitfile_path + '...' + with open(bitfile_path, 'rb') as bitfile: + header = file_bytes = bitfile.read() + if (self._safe_image != self.parse_bitfile_header(file_bytes)['safe-image']): + confirm_msg = ('----------------------------------------------------------------------\n' + 'WARNING!!! You are about to burn a safe image into a production slot \n' + ' or a production image into a safe slot. \n' + '----------------------------------------------------------------------\n' + 'This is dangerous and can cause the device to boot incorrectly.\n' + 'Are you sure you want to proceed?') + if not noprompt: + if raw_input("%s (y/N) " % confirm_msg).lower() != 'y': + print '[BURN] Aborted by user' + return + else: + print '[WARNING] Burning image to the wrong slot without a prompt as requested' + + print '[BURN] Writing to flash...' + pkt_chunks = chunkify(file_bytes, N230_FLASH_COMM_PAYLOAD_SIZE) + offset = 0 + for pkt_data in pkt_chunks: + pkt_data = array("B", pkt_data) + size = N230_FLASH_COMM_PAYLOAD_SIZE if (len(pkt_data) >= N230_FLASH_COMM_PAYLOAD_SIZE) else len(pkt_data) + #Erase sector + if (offset % N230_FLASH_COMM_SECTOR_SIZE == 0): + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, pkt_data) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + #Write data + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, pkt_data) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + #Increment + offset += N230_FLASH_COMM_PAYLOAD_SIZE + draw_progress_bar((((offset/N230_FLASH_COMM_PAYLOAD_SIZE)+1)*100)/len(pkt_chunks)) + print('\n[BURN] DONE') + + def parse_bitfile_header(self, header_bytes): + xil_header = dict() + n230_header = dict() + n230_header['valid'] = False + ptr = 0 + #Field 1 + if short.unpack(header_bytes[ptr:ptr+2])[0] == 9 and ulong.unpack(header_bytes[ptr+2:ptr+6])[0] == 0x0ff00ff0: + #Headers + ptr += short.unpack(header_bytes[ptr:ptr+2])[0] + 2 + ptr += short.unpack(header_bytes[ptr:ptr+2])[0] + 1 + #Fields a-d + for keynum in range(0, 4): + key = header_bytes[ptr] + ptr += 1 + val_len = short.unpack(header_bytes[ptr:ptr+2])[0] + ptr += 2 + val = header_bytes[ptr:ptr+val_len] + ptr += val_len + xil_header[key] = val + #Field e + ptr += 1 + length = ulong.unpack(header_bytes[ptr:ptr+4])[0] + xil_header['bl'] = length #Bitstream length + ptr += 4 + xil_header['hl'] = ptr #Header lengt + + #Map Xilinx header field to N230 specific ones + if xil_header and xil_header['a'].split(';')[0] == 'n230': + n230_header['valid'] = True + n230_header['user-id'] = int(xil_header['a'].split(';')[1].split('=')[1], 16) + n230_header['safe-image'] = (n230_header['user-id'] >> 16 == 0x5AFE) + n230_header['product'] = xil_header['b'] + n230_header['timestamp'] = xil_header['c'] + ' ' + xil_header['d'] + n230_header['filesize'] = xil_header['hl'] + xil_header['bl'] + return n230_header + + def read_bitfile_header_from_flash(self): + max_header_size = 1024 #Should be enough + header_bytes = bytes() + for offset in range(0, max_header_size, N230_FLASH_COMM_PAYLOAD_SIZE): + #Read data + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_READ_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), N230_FLASH_COMM_PAYLOAD_SIZE, [0]*N230_FLASH_COMM_PAYLOAD_SIZE) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + header_bytes += data + return self.parse_bitfile_header(header_bytes) + + def extract_fpga_from_flash(self, bitfile_path): + header = self.read_bitfile_header_from_flash(); + if not header['valid']: + raise Exception("Could not detect a vaild Xilinx .bit burned into the flash") + max_offset = header['filesize'] + print '[EXTRACT] Writing ' + bitfile_path + '...' + with open(bitfile_path, 'wb') as bitfile: + for i in range(0, int(math.ceil(float(max_offset)/N230_FLASH_COMM_PAYLOAD_SIZE))): + offset = i * N230_FLASH_COMM_PAYLOAD_SIZE + size = N230_FLASH_COMM_PAYLOAD_SIZE if (max_offset - offset >= N230_FLASH_COMM_PAYLOAD_SIZE) else (max_offset - offset) + #Read data + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_READ_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, [0]*N230_FLASH_COMM_PAYLOAD_SIZE) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + bitfile.write(data[:size]) + draw_progress_bar(((offset*100)/max_offset) + 1) + print('\n[EXTRACT] DONE') + + def erase_fpga_from_flash(self): + print '[ERASE] Erasing image from flash...' + for offset in range(0, N230_FLASH_COMM_FPGA_IMG_MAX_SIZE, N230_FLASH_COMM_SECTOR_SIZE): + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), N230_FLASH_COMM_PAYLOAD_SIZE, [0]*N230_FLASH_COMM_PAYLOAD_SIZE) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + draw_progress_bar(((offset+N230_FLASH_COMM_SECTOR_SIZE)*100)/N230_FLASH_COMM_FPGA_IMG_MAX_SIZE) + print('\n[ERASE] DONE') + + def wipe_user_data(self, noprompt): + confirm_msg = ('-------------------------------------------------------------------\n' + 'WARNING!!! You are about to erase all the user data from the flash \n' + '-------------------------------------------------------------------\n' + 'This will cause the device to lose the following:\n' + ' * IP Address (Will default to 192.168.10.2)\n' + ' * Subnet Mask (Will default to 255.255.255.2)\n' + ' * MAC Address\n' + ' * Serial Number\n' + ' * Hardware Revision\n' + ' * ...and other identification info\n' + 'Are you sure you want to proceed?') + if not noprompt: + if raw_input("%s (y/N) " % confirm_msg).lower() == 'y': + wipe_ok = True + else: + print '[WIPE] Aborted by user' + wipe_ok = False + else: + print '[WARNING] Wiping user data without prompt a as requested' + wipe_ok = True + + if wipe_ok: + print '[WIPE] Erasing all user data from flash...' + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_NV_DATA + out_pkt = pack_flash_transaction(flags, 0, N230_FLASH_COMM_PAYLOAD_SIZE, [0xFF]*N230_FLASH_COMM_PAYLOAD_SIZE) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + print('[WIPE] DONE. Please power-cycle the device.') + + def print_status(self): + header = self.read_bitfile_header_from_flash(); + if header['valid']: + print('[STATUS] Detected a valid .bit header in the flash (Product = %s, Datestamp = %s%s)' % \ + (header['product'], header['timestamp'], ', Safe-Image' if header['safe-image'] else '')) + else: + print('[STATUS] No .bit header detected. Either the flash is uninitialized or the image is corrupt.') + + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = optparse.OptionParser() + parser.add_option("--addr", type="string", help="N230 device address", default='') + parser.add_option("--status", action="store_true", help="Print out the status of the burned image", default=False) + parser.add_option("--erase", action="store_true", help="Erase FPGA bitstream from flash", default=False) + parser.add_option("--burn", type="string", help="Path to FPGA bitstream (.bit) to burn to flash", default=None) + parser.add_option("--extract", type="string", help="Destination bitfile to dump contents of the extracted image", default=None) + parser.add_option("--safe_image", action="store_true", help="Operate on the safe image. WARNING: This could be dangerous", default=False) + parser.add_option("--wipe_user_data", action="store_true", help="Erase all user data like IP, MAC, S/N, etc from flash", default=False) + parser.add_option("--no_prompt", action="store_true", help="Suppress all warning prompts", default=False) + (options, args) = parser.parse_args() + return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + + if not options.addr: raise Exception('No address specified') + + ctrl_sock = ctrl_socket(addr=options.addr) + + # Initialize safe image selector first + if options.safe_image: + ctrl_sock.set_safe_image(options.no_prompt) + + if options.status: + ctrl_sock.print_status() + + # Order of operations: + # 1. Extract (if specified) + # 2. Erase (if specified) + # 3. Burn (if specified) + + if options.extract is not None: + file_path = options.extract + ctrl_sock.print_status() + ctrl_sock.extract_fpga_from_flash(file_path) + + if options.erase: + ctrl_sock.erase_fpga_from_flash() + ctrl_sock.print_status() + + if options.burn is not None: + file_path = options.burn + extension = os.path.splitext(file_path)[1] + if (extension.lower() == '.bit'): + ctrl_sock.burn_fpga_to_flash(file_path, options.no_prompt) + ctrl_sock.print_status() + else: + raise Exception("Unsupported FPGA bitfile format. You must use a .bit file.") + + if options.wipe_user_data: + ctrl_sock.wipe_user_data(options.no_prompt) diff --git a/firmware/usrp3/n230/n230_debug.py b/firmware/usrp3/n230/n230_debug.py new file mode 100755 index 000000000..f9ff64ab7 --- /dev/null +++ b/firmware/usrp3/n230/n230_debug.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import optparse +import math +import socket +import struct +import array +import os.path +import sys +import time +try: + import fcntl + N230_DEVICE_DISCOVERY_AVAILABLE = True +except: + N230_DEVICE_DISCOVERY_AVAILABLE = False + +######################################################################## +# constants +######################################################################## +N230_FW_COMMS_UDP_PORT = 49152 +N230_FW_COMMS_MAX_DATA_WORDS = 16 + +N230_FW_COMMS_FLAGS_ACK = 0x00000001 +N230_FW_COMMS_FLAGS_ERROR_MASK = 0xF0000000 +N230_FW_COMMS_FLAGS_CMD_MASK = 0x000000F0 + +N230_FW_COMMS_CMD_ECHO = 0x00000000 +N230_FW_COMMS_CMD_POKE32 = 0x00000010 +N230_FW_COMMS_CMD_PEEK32 = 0x00000020 +N230_FW_COMMS_CMD_BLOCK_POKE32 = 0x00000030 +N230_FW_COMMS_CMD_BLOCK_PEEK32 = 0x00000040 + +N230_FW_COMMS_ERR_PKT_ERROR = 0x80000000 +N230_FW_COMMS_ERR_CMD_ERROR = 0x40000000 +N230_FW_COMMS_ERR_SIZE_ERROR = 0x20000000 + +N230_FW_COMMS_ID = 0x0001ACE3 + +N230_FW_LOADER_ADDR = 0xfa00 +N230_FW_LOADER_DATA = 0xfa04 +N230_FW_LOADER_NUM_WORDS = 8192 +N230_FW_LOADER_BOOT_DONE_ADDR = 0xA004 +N230_FW_LOADER_BOOT_TIMEOUT = 5 + +N230_JESD204_TEST = 0xA014 +N230_FPGA_HASH_ADDR = 0xA010 +N230_FW_HASH_ADDR = 0x10004 +N230_ICAP_ADDR = 0xF800 +#ICAP_DUMMY_WORD = 0xFFFFFFFF +#ICAP_SYNC_WORD = 0xAA995566 +#ICAP_TYPE1_NOP = 0x20000000 +#ICAP_WRITE_WBSTAR = 0x30020001 +#ICAP_WBSTAR_ADDR = 0x00000000 +#ICAP_WRITE_CMD = 0x30008001 +#ICAP_IPROG_CMD = 0x0000000F +# Bit reversed values per Xilinx UG470 - Bits reversed within bytes. +ICAP_DUMMY_WORD = 0xFFFFFFFF +ICAP_SYNC_WORD = 0x5599AA66 +ICAP_TYPE1_NOP = 0x04000000 +ICAP_WRITE_WBSTAR = 0x0C400080 +ICAP_WBSTAR_ADDR = 0x00000000 +ICAP_WRITE_CMD = 0x0C000180 +ICAP_IPROG_CMD = 0x000000F0 + + +UDP_MAX_XFER_BYTES = 256 +UDP_TIMEOUT = 3 +FPGA_LOAD_TIMEOUT = 10 + +_seq = -1 +def seq(): + global _seq + _seq = _seq+1 + return _seq + +######################################################################## +# helper functions +######################################################################## + +def pack_fw_command(flags, seq, num_words, addr, data_arr): + if (num_words > N230_FW_COMMS_MAX_DATA_WORDS): + raise Exception("Data size too large. Firmware supports a max 16 words per block." % (addr)) + buf = bytes() + buf = struct.pack('!IIIII', N230_FW_COMMS_ID, flags, seq, num_words, addr) + for i in range(N230_FW_COMMS_MAX_DATA_WORDS): + if (i < num_words): + buf += struct.pack('!I', data_arr[i]) + else: + buf += struct.pack('!I', 0) + return buf + +def unpack_fw_command(buf, fmt=None): + (id, flags, seq, num_words, addr) = struct.unpack_from('!IIIII', buf) + fw_check_error(flags) + data = [] + if fmt is None: + fmt = 'I' + for i in xrange(20, len(buf), 4): + data.append(struct.unpack('!'+fmt, buf[i:i+4])[0]) + return (flags, seq, num_words, addr, data) + +def fw_check_error(flags): + if flags & N230_FW_COMMS_ERR_PKT_ERROR == N230_FW_COMMS_ERR_PKT_ERROR: + raise Exception("The fiwmware operation returned a packet error") + if flags & N230_FW_COMMS_ERR_CMD_ERROR == N230_FW_COMMS_ERR_CMD_ERROR: + raise Exception("The fiwmware operation returned a command error") + if flags & N230_FW_COMMS_ERR_SIZE_ERROR == N230_FW_COMMS_ERR_SIZE_ERROR: + raise Exception("The fiwmware operation returned a size error") + +def chunkify(stuff, n): + return [stuff[i:i+n] for i in range(0, len(stuff), n)] + +def draw_progress_bar(percent, bar_len = 32): + sys.stdout.write("\r") + progress = "" + for i in range(bar_len): + if i < int((bar_len * percent) / 100): + progress += "=" + else: + progress += "-" + sys.stdout.write("[%s] %d%%" % (progress, percent)) + sys.stdout.flush() + +######################################################################## +# Discovery class +######################################################################## +class discovery_socket(object): + def __init__(self): + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + self._sock.settimeout(0.250) + + def get_bcast_addrs(self): + max_possible = 128 # arbitrary. raise if needed. + num_bytes = max_possible * 32 + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array('B', '\0' * num_bytes) + outbytes = struct.unpack('iL', fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack('iL', num_bytes, names.buffer_info()[0]) + ))[0] + namestr = names.tostring() + lst = [] + for i in range(0, outbytes, 40): + name = namestr[i:i+16].split('\0', 1)[0] + ip = map(ord, namestr[i+20:i+24]) + mask = map(ord, fcntl.ioctl(s.fileno(), 0x891B, struct.pack('256s', name))[20:24]) + bcast = [] + for i in range(len(ip)): + bcast.append((ip[i] | (~mask[i])) & 0xFF) + if (name != 'lo'): + lst.append(str(bcast[0]) + '.' + str(bcast[1]) + '.' + str(bcast[2]) + '.' + str(bcast[3])) + return lst + + def discover(self): + addrs = [] + for bcast_addr in self.get_bcast_addrs(): + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_ECHO|N230_FW_COMMS_FLAGS_ACK, seq(), 0, 0, [0]) + self._sock.sendto(out_pkt, (bcast_addr, N230_FW_COMMS_UDP_PORT)) + while 1: + try: + (in_pkt, addr_pair) = self._sock.recvfrom(UDP_MAX_XFER_BYTES) + if len(in_pkt) < 20: + continue + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt) + addrs.append(addr_pair[0]) + except socket.error: + break + return addrs + + +######################################################################## +# Communications class, holds a socket and send/recv routine +######################################################################## +class ctrl_socket(object): + def __init__(self, addr, port): + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.settimeout(UDP_TIMEOUT) + self._sock.connect((addr, port)) + self.set_callbacks(lambda *a: None, lambda *a: None) + self.peek(0) #Dummy read + + def set_callbacks(self, progress_cb, status_cb): + self._progress_cb = progress_cb + self._status_cb = status_cb + + def send(self, pkt): + self._sock.send(pkt) + + def recv(self): + return self._sock.recv(UDP_MAX_XFER_BYTES) + + def send_and_recv(self, pkt): + self.send(pkt) + return self.recv() + + def peek(self, peek_addr, fmt=None): + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 1, peek_addr, [0]) + in_pkt = self.send_and_recv(out_pkt) + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt, fmt) + return data[0] + + def peek64(self, peek_addr, fmt=None): + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_BLOCK_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 2, peek_addr, [0,0]) + in_pkt = self.send_and_recv(out_pkt) + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt, fmt) + return (data[0] | (data[1] << 32)) + + def poke(self, poke_addr, poke_data, ack=True): + ack_flag = N230_FW_COMMS_FLAGS_ACK if ack else 0 + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_POKE32|ack_flag, seq(), 1, poke_addr, [poke_data]) + self.send(out_pkt) + if ack: + in_pkt = self.recv() + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt) + + def live_load_firmware_bin(self, bin_path): + raise Exception("live_load_firmware_bin not implemented yet!") + + def live_load_firmware_coe(self, coe_path): + with open(coe_path, 'r') as coe_file: + print("Loading %s..." % coe_path) + coe_lines = [line.strip(',;\n ') for line in coe_file] + start_index = coe_lines.index("memory_initialization_vector=") + 1 + coe_words = coe_lines[start_index:] + if len(coe_words) != N230_FW_LOADER_NUM_WORDS: + raise Exception("invalid COE file. Must contain 8192 words!") + self.poke(N230_FW_LOADER_ADDR, 0) #Load start address + for i in range(0, len(coe_words)): + self.poke(N230_FW_LOADER_DATA, int(coe_words[i],16), (i%10==0) and (i<len(coe_words)-1)) + draw_progress_bar(((i+1)*100)/len(coe_words)) + print("\nRebooting...") + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_POKE32, seq(), 1, N230_FW_LOADER_BOOT_DONE_ADDR, [1]) + self._sock.send(out_pkt) + self._sock.settimeout(1) + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 1, 0, [0]) + for i in range(N230_FW_LOADER_BOOT_TIMEOUT): + try: + self._sock.send(out_pkt) + in_pkt = self._sock.recv(UDP_MAX_XFER_BYTES) + print("Firmware is alive!") + self._sock.settimeout(UDP_TIMEOUT) + return + except socket.error: + pass + print("Firmware boot FAILED!!!") + self._sock.settimeout(UDP_TIMEOUT) + + def read_hash(self): + fpga_hash = self.peek(N230_FPGA_HASH_ADDR) + fpga_status = "clean" if (fpga_hash & 0xf0000000 == 0x0) else "modified" + fw_hash = self.peek(N230_FW_HASH_ADDR) + fw_status = "clean" if (fw_hash & 0xf0000000 == 0x0) else "modified" + print("FPGA Version : %x (%s)" % (fpga_hash & 0xfffffff, fpga_status)) + print("Firmware Version : %x (%s)" % (fw_hash & 0xfffffff, fw_status)) + + def is_claimed(self): + claimed = self.peek(0x10008) + print("Claimed : %s") % ('YES' if claimed else 'NO') + + def reset_fpga(self): + print("Reseting USRP...") + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_DUMMY_WORD) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_SYNC_WORD) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WRITE_WBSTAR) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WBSTAR_ADDR) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WRITE_CMD) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_IPROG_CMD, False) + print("Waiting for FPGA to load...") + self._sock.settimeout(1) + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_ECHO|N230_FW_COMMS_FLAGS_ACK, seq(), 1, 0, [0]) + for i in range(FPGA_LOAD_TIMEOUT): + try: + in_pkt = self.send_and_recv(out_pkt) + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt) + print("FPGA loaded successfully.") + self._sock.settimeout(UDP_TIMEOUT) + return + except socket.error: + pass + print("FPGA load FAILED!!!") + self._sock.settimeout(UDP_TIMEOUT) + + def jesd204_test_connector(self): + print("Testing JESD204 connectors. Molex cable #79576-2102 must be connected") + ctrl_sock.poke(N230_JESD204_TEST,0x1) + while True: + jesd204_test_status = ctrl_sock.peek(N230_JESD204_TEST) + if (jesd204_test_status & 0x10000 == 0x10000): + break + ctrl_sock.poke(N230_JESD204_TEST,0x0) + if (jesd204_test_status & 0xFFFF != 0x0): + print("JESD204 loopback test Failed!: Returned status is %4x" % (jesd204_test_status & 0xFFFF)) + else: + print("JESD204 loopback test Passed.") + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = optparse.OptionParser() + parser.add_option("--discover", action="store_true",help="Find all devices connected N230 devices", default=False) + parser.add_option("--addr", type="string", help="N230 device address", default='') + parser.add_option("--peek", type="int", help="Read from memory map", default=None) + parser.add_option("--poke", type="int", help="Write to memory map", default=None) + parser.add_option("--data", type="int", help="Data for poke", default=None) + parser.add_option("--fw", type="string", help="Path to FW image to load", default=None) + parser.add_option("--hash", action="store_true",help="Display FPGA git hash", default=False) + parser.add_option("--reset", action="store_true",help="Reset and Reload USRP FPGA.", default=False) + parser.add_option("--jesd204test", action="store_true",help="Test mini-SAS connectors with loopback cable..", default=False) + + (options, args) = parser.parse_args() + return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + + if options.discover: + if N230_DEVICE_DISCOVERY_AVAILABLE: + disc_sock = discovery_socket() + for addr in disc_sock.discover(): + print '==== FOUND ' + addr + ' ====' + ctrl_sock = ctrl_socket(addr, N230_FW_COMMS_UDP_PORT) + ctrl_sock.read_hash() + ctrl_sock.is_claimed() + sys.exit() + else: + raise Exception('Discovery is only supported on Linux.') + + if not options.addr: + raise Exception('No address specified') + + ctrl_sock = ctrl_socket(options.addr, N230_FW_COMMS_UDP_PORT) + + if options.fw is not None: + file_path = options.fw + extension = os.path.splitext(file_path)[1] + if (extension.lower() == '.coe'): + ctrl_sock.live_load_firmware_coe(file_path) + elif (extension.lower() == '.bin'): + ctrl_sock.live_load_firmware_bin(file_path) + else: + raise Exception("Unsupported firmware file format") + + if options.hash: + ctrl_sock.read_hash() + + if options.peek is not None: + addr = options.peek + data = ctrl_sock.peek(addr) + print("PEEK[0x%x (%d)] => 0x%x (%d)" % (addr,addr,data,data)) + + if options.poke is not None and options.data is not None: + addr = options.poke + data = options.data + ctrl_sock.poke(addr,data) + print("POKE[0x%x (%d)] <= 0x%x (%d)" % (addr,addr,data,data)) + + if options.reset: + ctrl_sock.reset_fpga() + + if options.jesd204test: + ctrl_sock.jesd204_test_connector() diff --git a/firmware/usrp3/n230/n230_eeprom.c b/firmware/usrp3/n230/n230_eeprom.c new file mode 100644 index 000000000..8f756d41f --- /dev/null +++ b/firmware/usrp3/n230/n230_eeprom.c @@ -0,0 +1,196 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "../../../host/lib/usrp/n230/n230_eeprom.h" + +#include <trace.h> +#include <stddef.h> +#include <flash/spi_flash.h> +#include <flash/spif_spsn_s25flxx.h> +#include <string.h> //memcpy + +#include "../../../host/lib/usrp/n230/n230_fw_defs.h" +#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h" + +static const wb_spi_slave_t flash_spi_slave = { + .base = (void*) 0xB000, + .slave_sel = 0x0001, + .clk_div = 4, //80MHz/4 = 20MHz + .mosi_edge = RISING, + .miso_edge = FALLING, + .lsb_first = false +}; + +static const spi_flash_dev_t spi_flash_device = { + .page_size = 256, + .sector_size = 65536, + .num_sectors = 254, + .bus = &flash_spi_slave +}; + +/*********************************************************************** + * Non-volatile device data + **********************************************************************/ +#define N230_FLASH_NV_DATA_OFFSET 0x800000 + +//Default values in case the EEPROM is not read, corrupt +const n230_eeprom_map_t default_eeprom = { + .data_version_major = N230_EEPROM_VER_MAJOR, + .data_version_minor = N230_EEPROM_VER_MINOR, + .hw_revision = 0, + .hw_product = 0x01, + .gateway = N230_DEFAULT_GATEWAY, + .eth_info = { + { //eth0 + .mac_addr = N230_DEFAULT_ETH0_MAC, + .subnet = N230_DEFAULT_ETH0_MASK, + .ip_addr = N230_DEFAULT_ETH0_IP + }, + { //eth1 + .mac_addr = N230_DEFAULT_ETH1_MAC, + .subnet = N230_DEFAULT_ETH1_MASK, + .ip_addr = N230_DEFAULT_ETH1_IP + } + } +}; + +//EEPROM cache +static spi_flash_session_t flash_session = {.device = NULL}; +static n230_eeprom_map_t eeprom_cache; +static bool cache_dirty = true; + +bool read_n230_eeprom() +{ + bool status = false; + if (flash_session.device == NULL) { //Initialize flash session structure for the first time + wb_spi_init(spi_flash_device.bus); + spif_init(&flash_session, &spi_flash_device, spif_spsn_s25flxx_operations()); + } + spif_read_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET, &eeprom_cache, sizeof(n230_eeprom_map_t)); + + //Verify data format + status = (eeprom_cache.data_version_major == default_eeprom.data_version_major); + //Sanity communication info + if (eeprom_cache.eth_info[0].ip_addr == 0xFFFFFFFF) + eeprom_cache.eth_info[0].ip_addr = default_eeprom.eth_info[0].ip_addr; + if (eeprom_cache.eth_info[1].ip_addr == 0xFFFFFFFF) + eeprom_cache.eth_info[1].ip_addr = default_eeprom.eth_info[1].ip_addr; + if (eeprom_cache.eth_info[0].subnet == 0xFFFFFFFF) + eeprom_cache.eth_info[0].subnet = default_eeprom.eth_info[0].subnet; + if (eeprom_cache.eth_info[1].subnet == 0xFFFFFFFF) + eeprom_cache.eth_info[1].subnet = default_eeprom.eth_info[1].subnet; + + if (!status) { + UHD_FW_TRACE(WARN, "read_n230_eeprom: Initialized cache to the default map."); + memcpy(&eeprom_cache, &default_eeprom, sizeof(n230_eeprom_map_t)); + } + cache_dirty = !status; + return status; +} + +bool write_n230_eeprom() +{ + //Assumption: sizeof(n230_eeprom_map_t) <= flash_page_size + //This function would need to be reimplemented if this assumption is no longer true + if (sizeof(n230_eeprom_map_t) > flash_session.device->page_size) { + UHD_FW_TRACE(ERROR, "write_n230_eeprom: sizeof(n230_eeprom_map_t) > flash_page_size"); + return false; + } + + bool status = true; + if (cache_dirty) { + n230_eeprom_map_t device_eeprom; + spif_read_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET, &device_eeprom, sizeof(n230_eeprom_map_t)); + if (memcmp(&eeprom_cache, &device_eeprom, sizeof(n230_eeprom_map_t)) != 0) { + //Cache does not match read state. Write. + UHD_FW_TRACE(DEBUG, "write_n230_eeprom: Writing data to flash..."); + status = spif_erase_sector_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET); + if (status) { + status = spif_write_page_sync( + &flash_session, N230_FLASH_NV_DATA_OFFSET, &eeprom_cache, sizeof(n230_eeprom_map_t)); + } + if (!status) { + UHD_FW_TRACE(ERROR, "write_n230_eeprom: Operation failed!"); + } + cache_dirty = !status; + } else { + UHD_FW_TRACE(DEBUG, "write_n230_eeprom: No new data. Write skipped."); + //Cache matches read state. So mark as clean + cache_dirty = false; + } + } + return status; +} + +bool is_n230_eeprom_cache_dirty() +{ + return cache_dirty; +} + +n230_eeprom_map_t* get_n230_eeprom_map() +{ + cache_dirty = true; + return &eeprom_cache; +} + +const n230_eeprom_map_t* get_n230_const_eeprom_map() +{ + return &eeprom_cache; +} + +const n230_eth_eeprom_map_t* get_n230_ethernet_info(uint32_t iface) { + if (iface >= N230_NUM_ETH_PORTS) { + UHD_FW_TRACE_FSTR(ERROR, + "get_n230_ethernet_info called with iface=%d when there are only %d ports!!!", + iface, N230_NUM_ETH_PORTS); + } + return &(get_n230_const_eeprom_map()->eth_info[iface]); +} + + +/*********************************************************************** + * Storage for bootstrap FPGA Image + **********************************************************************/ +#define N230_FLASH_FPGA_IMAGE_OFFSET 0x000000 +#define N230_FLASH_FPGA_IMAGE_SIZE 0x400000 +#define N230_FLASH_NUM_FPGA_IMAGES 2 + +void read_n230_fpga_image_page(uint32_t offset, void *buf, uint32_t num_bytes) +{ + if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) { + UHD_FW_TRACE_FSTR(ERROR, "read_n230_fpga_image_page: Offset 0x%x out of bounds", offset); + } + spif_read_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset, buf, num_bytes); +} + +bool write_n230_fpga_image_page(uint32_t offset, const void *buf, uint32_t num_bytes) +{ + if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) { + UHD_FW_TRACE_FSTR(ERROR, "write_n230_fpga_image_page: Offset 0x%x out of bounds", offset); + return false; + } + return spif_write_page_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset, buf, num_bytes); +} + +bool erase_n230_fpga_image_sector(uint32_t offset) +{ + if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) { + UHD_FW_TRACE_FSTR(ERROR, "erase_n230_fpga_image_sector: Offset 0x%x out of bounds", offset); + return false; + } + return spif_erase_sector_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset); +} diff --git a/firmware/usrp3/n230/n230_eth_handlers.c b/firmware/usrp3/n230/n230_eth_handlers.c new file mode 100644 index 000000000..b291bb39f --- /dev/null +++ b/firmware/usrp3/n230/n230_eth_handlers.c @@ -0,0 +1,340 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_eth_handlers.h" + +#include <wb_utils.h> +#include <string.h> //memcmp +#include <u3_net_stack.h> +#include <print_addrs.h> +#include <trace.h> +#include "../../../host/lib/usrp/common/fw_comm_protocol.h" +#include "../../../host/lib/usrp/n230/n230_fw_defs.h" +#include "../n230/n230_fw_host_iface.h" +#include "../../../host/lib/usrp/n230/n230_eeprom.h" + +static n230_host_shared_mem_t* host_shared_mem_ptr; + +static const soft_reg_field_t LED_REG_FIELD_ETH_LINK2 = {.num_bits=1, .shift=0}; +static const soft_reg_field_t LED_REG_FIELD_ETH_LINK1 = {.num_bits=1, .shift=1}; +static const soft_reg_field_t LED_REG_FIELD_ETH_ACT2 = {.num_bits=1, .shift=2}; +static const soft_reg_field_t LED_REG_FIELD_ETH_ACT1 = {.num_bits=1, .shift=3}; + +/*********************************************************************** + * Handler for host <-> firmware communication + **********************************************************************/ + +static inline void n230_poke32(const uint32_t addr, const uint32_t data) +{ + if (addr >= N230_FW_HOST_SHMEM_RW_BASE_ADDR && addr <= N230_FW_HOST_SHMEM_MAX_ADDR) { + host_shared_mem_ptr->buff[(addr - N230_FW_HOST_SHMEM_BASE_ADDR)/sizeof(uint32_t)] = data; + } else if (addr < N230_FW_HOST_SHMEM_BASE_ADDR) { + wb_poke32(addr, data); + } +} + +static inline uint32_t n230_peek32(const uint32_t addr) +{ + if (addr >= N230_FW_HOST_SHMEM_BASE_ADDR && addr <= N230_FW_HOST_SHMEM_MAX_ADDR) { + return host_shared_mem_ptr->buff[(addr - N230_FW_HOST_SHMEM_BASE_ADDR)/sizeof(uint32_t)]; + } else if (addr < N230_FW_HOST_SHMEM_BASE_ADDR) { + return wb_peek32(addr); + } else { + return 0; + } +} + +void n230_handle_udp_fw_comms( + const uint8_t ethno, + const struct ip_addr *src, const struct ip_addr *dst, + const uint16_t src_port, const uint16_t dst_port, + const void *buff, const size_t num_bytes) +{ + if (buff == NULL) { + UHD_FW_TRACE(WARN, "n230_handle_udp_fw_comms got an ICMP_DUR"); + /* We got here from ICMP_DUR undeliverable packet */ + /* Future space for hooks to tear down streaming radios etc */ + } else if (num_bytes != sizeof(fw_comm_pkt_t)) { + UHD_FW_TRACE(WARN, "n230_handle_udp_fw_comms got an unknown request (bad size)."); + } else { + const fw_comm_pkt_t *request = (const fw_comm_pkt_t *)buff; + fw_comm_pkt_t response; + bool send_response = process_fw_comm_protocol_pkt( + request, &response, + N230_FW_PRODUCT_ID, + (uint32_t)ethno, + n230_poke32, n230_peek32); + + if (send_response) { + u3_net_stack_send_udp_pkt(ethno, src, dst_port, src_port, &response, sizeof(response)); + } + } +} + +void n230_register_udp_fw_comms_handler(n230_host_shared_mem_t* shared_mem_ptr) +{ + host_shared_mem_ptr = shared_mem_ptr; + u3_net_stack_register_udp_handler(N230_FW_COMMS_UDP_PORT, &n230_handle_udp_fw_comms); +} + + +/*********************************************************************** + * Handler for UDP framer program packets + **********************************************************************/ +void program_udp_framer( + const uint8_t ethno, + const uint32_t sid, + const struct ip_addr *dst_ip, + const uint16_t dst_port, + const uint16_t src_port) +{ + const eth_mac_addr_t *dst_mac = u3_net_stack_arp_cache_lookup(dst_ip); + const size_t vdest = (sid >> 16) & 0xff; + + uint32_t framer_base = + ((ethno == 1) ? SR_ZPU_ETHINT1 : SR_ZPU_ETHINT0) + SR_ZPU_ETHINT_FRAMER_BASE; + + //setup source framer + const eth_mac_addr_t *src_mac = u3_net_stack_get_mac_addr(ethno); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_MAC_HI), + (((uint32_t)src_mac->addr[0]) << 8) | (((uint32_t)src_mac->addr[1]) << 0)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_MAC_LO), + (((uint32_t)src_mac->addr[2]) << 24) | (((uint32_t)src_mac->addr[3]) << 16) | + (((uint32_t)src_mac->addr[4]) << 8) | (((uint32_t)src_mac->addr[5]) << 0)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_IP_ADDR), u3_net_stack_get_ip_addr(ethno)->addr); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_UDP_PORT), src_port); + + //setup destination framer + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_RAM_ADDR), vdest); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_IP_ADDR), dst_ip->addr); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_UDP_MAC), + (((uint32_t)dst_port) << 16) | + (((uint32_t)dst_mac->addr[0]) << 8) | (((uint32_t)dst_mac->addr[1]) << 0)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_MAC_LO), + (((uint32_t)dst_mac->addr[2]) << 24) | (((uint32_t)dst_mac->addr[3]) << 16) | + (((uint32_t)dst_mac->addr[4]) << 8) | (((uint32_t)dst_mac->addr[5]) << 0)); +} + +void handle_udp_prog_framer( + const uint8_t ethno, + const struct ip_addr *src, const struct ip_addr *dst, + const uint16_t src_port, const uint16_t dst_port, + const void *buff, const size_t num_bytes) +{ + if (buff == NULL) { + /* We got here from ICMP_DUR undeliverable packet */ + /* Future space for hooks to tear down streaming radios etc */ + } else { + const uint32_t sid = ((const uint32_t *)buff)[1]; + program_udp_framer(ethno, sid, src, src_port, dst_port); + UHD_FW_TRACE_FSTR(INFO, "Reprogrammed eth%d framer. Src=%s:%d, Dest=%s:%d", + ethno,ip_addr_to_str(src),src_port,ip_addr_to_str(dst),dst_port); + } +} + +void n230_register_udp_prog_framer() +{ + u3_net_stack_register_udp_handler(N230_FW_COMMS_CVITA_PORT, &handle_udp_prog_framer); +} + + +/*********************************************************************** + * Handler for flash programming interface over UDP + **********************************************************************/ + +void n230_handle_flash_prog_comms( + const uint8_t ethno, + const struct ip_addr *src, const struct ip_addr *dst, + const uint16_t src_port, const uint16_t dst_port, + const void *buff, const size_t num_bytes) +{ + if (buff == NULL) { + UHD_FW_TRACE(WARN, "n230_handle_flash_prog_comms got an ICMP_DUR"); + /* We got here from ICMP_DUR undeliverable packet */ + /* Future space for hooks to tear down streaming radios etc */ + } else if (num_bytes != sizeof(n230_flash_prog_t)) { + UHD_FW_TRACE(WARN, "n230_handle_flash_prog_comms got an unknown request (bad size)."); + } else { + const n230_flash_prog_t *request = (const n230_flash_prog_t *)buff; + n230_flash_prog_t response; + bool ack_requested = request->flags & N230_FLASH_COMM_FLAGS_ACK; + + //Request is valid. Copy it into the reply. + memcpy(&response, request, sizeof(n230_flash_prog_t)); + + switch (request->flags & N230_FLASH_COMM_FLAGS_CMD_MASK) { + case N230_FLASH_COMM_CMD_READ_NV_DATA: { + UHD_FW_TRACE(DEBUG, "n230_handle_flash_prog_comms::read_nv_data()"); + //Offset ignored because all non-volatile data fits in a packet. + if (is_n230_eeprom_cache_dirty()) { + read_n230_eeprom(); + } + //EEPROM cache is up-to-date. Copy it into the packet. + //Assumption: Cache size < 256. If this is no longer true, the offset field + //will have to be used. + memcpy(response.data, get_n230_const_eeprom_map(), sizeof(n230_eeprom_map_t)); + ack_requested = true; + } break; + + case N230_FLASH_COMM_CMD_WRITE_NV_DATA: { + UHD_FW_TRACE(DEBUG, "n230_handle_flash_prog_comms::write_nv_data()"); + //Offset ignored because all non-volatile data fits in a packet. + memcpy(get_n230_eeprom_map(), request->data, sizeof(n230_eeprom_map_t)); + if (!write_n230_eeprom()) { + response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR; + } + } break; + + case N230_FLASH_COMM_CMD_READ_FPGA: { + UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::read_fpga_page(offset=0x%x, size=%d)", + request->offset, request->size); + read_n230_fpga_image_page(request->offset, response.data, request->size); + ack_requested = true; + } break; + + case N230_FLASH_COMM_CMD_WRITE_FPGA: { + UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::write_fpga_page(offset=0x%x, size=%d)", + request->offset, request->size); + if (!write_n230_fpga_image_page(request->offset, request->data, request->size)) { + response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR; + } + } break; + + case N230_FLASH_COMM_CMD_ERASE_FPGA: { + UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::erase_fpga_sector(offset=0x%x)", + request->offset); + if (!erase_n230_fpga_image_sector(request->offset)) { + response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR; + } + } break; + + default :{ + UHD_FW_TRACE(ERROR, "n230_handle_flash_prog_comms got an invalid command."); + response.flags |= FW_COMM_ERR_CMD_ERROR; + } + } + //Send a reply if ack requested + if (ack_requested) { + u3_net_stack_send_udp_pkt(ethno, src, dst_port, src_port, &response, sizeof(response)); + } + } +} + +void n230_register_flash_comms_handler() +{ + u3_net_stack_register_udp_handler(N230_FW_COMMS_FLASH_PROG_PORT, &n230_handle_flash_prog_comms); +} + +/*********************************************************************** + * Handler for SFP state changes + **********************************************************************/ +#define SFPP_STATUS_MODABS_CHG (1 << 5) // Has MODABS changed since last read? +#define SFPP_STATUS_TXFAULT_CHG (1 << 4) // Has TXFAULT changed since last read? +#define SFPP_STATUS_RXLOS_CHG (1 << 3) // Has RXLOS changed since last read? +#define SFPP_STATUS_MODABS (1 << 2) // MODABS state +#define SFPP_STATUS_TXFAULT (1 << 1) // TXFAULT state +#define SFPP_STATUS_RXLOS (1 << 0) // RXLOS state + +static bool links_up[N230_MAX_NUM_ETH_PORTS] = {}; +static uint32_t packet_count[N230_MAX_NUM_ETH_PORTS] = {}; + +void n230_poll_sfp_status(const uint32_t eth, bool force, bool* state_updated) +{ + // Has MODDET/MODAbS changed since we last looked? + uint32_t rb = wb_peek32(SR_ADDR(WB_SBRB_BASE, (eth==0) ? RB_ZPU_SFP_STATUS0 : RB_ZPU_SFP_STATUS1)); + + if (rb & SFPP_STATUS_RXLOS_CHG) + UHD_FW_TRACE_FSTR(DEBUG, "eth%1d RXLOS changed state: %d", eth, (rb & SFPP_STATUS_RXLOS)); + if (rb & SFPP_STATUS_TXFAULT_CHG) + UHD_FW_TRACE_FSTR(DEBUG, "eth%1d TXFAULT changed state: %d", eth, ((rb & SFPP_STATUS_TXFAULT) >> 1)); + if (rb & SFPP_STATUS_MODABS_CHG) + UHD_FW_TRACE_FSTR(DEBUG, "eth%1d MODABS changed state: %d", eth, ((rb & SFPP_STATUS_MODABS) >> 2)); + + //update the link up status + if ((rb & SFPP_STATUS_RXLOS_CHG) || (rb & SFPP_STATUS_TXFAULT_CHG) || (rb & SFPP_STATUS_MODABS_CHG) || force) + { + const bool old_link_up = links_up[eth]; + const uint32_t status_reg_addr = (eth==0) ? RB_ZPU_SFP_STATUS0 : RB_ZPU_SFP_STATUS1; + + uint32_t sfpp_status = wb_peek32(SR_ADDR(WB_SBRB_BASE, status_reg_addr)) & 0xFFFF; + if ((sfpp_status & (SFPP_STATUS_RXLOS|SFPP_STATUS_TXFAULT|SFPP_STATUS_MODABS)) == 0) { + int8_t timeout = 100; + bool link_up = false; + do { + link_up = ((wb_peek32(SR_ADDR(WB_SBRB_BASE, status_reg_addr)) >> 16) & 0x1) != 0; + } while (!link_up && timeout-- > 0); + + links_up[eth] = link_up; + } else { + links_up[eth] = false; + } + + if (!old_link_up && links_up[eth]) u3_net_stack_send_arp_request(eth, u3_net_stack_get_ip_addr(eth)); + UHD_FW_TRACE_FSTR(INFO, "The link on eth port %u is %s", eth, links_up[eth]?"up":"down"); + if (rb & SFPP_STATUS_MODABS_CHG) { + // MODDET has changed state since last checked + if (rb & SFPP_STATUS_MODABS) { + // MODDET is high, module currently removed. + UHD_FW_TRACE_FSTR(INFO, "An SFP+ module has been removed from eth port %d.", eth); + } else { + // MODDET is low, module currently inserted. + // Return status. + UHD_FW_TRACE_FSTR(INFO, "A new SFP+ module has been inserted into eth port %d.", eth); + } + } + *state_updated = true; + } else { + *state_updated = false; + } +} + +void n230_update_link_act_state(soft_reg_t* led_reg) +{ + static bool first_poll = 1; + static uint32_t poll_cnt; + + bool activity[N230_MAX_NUM_ETH_PORTS] = {}; + for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) { + if (first_poll) { + links_up[i] = 0; + packet_count[i] = 0; + poll_cnt = 0; + } + + //Check SFP status and update links_up + bool link_state_from_sfp = false; + n230_poll_sfp_status(i, first_poll, &link_state_from_sfp); + + //Check packet counters less frequently to keep the LED on for a visible duration + uint32_t cnt = wb_peek32(SR_ADDR(WB_SBRB_BASE, (i==0)?RB_ZPU_ETH0_PKT_CNT:RB_ZPU_ETH1_PKT_CNT)); + activity[i] = (cnt != packet_count[i]); + packet_count[i] = cnt; + + //Update links_up if there is activity only if the SFP + //handler has not updated it + if (activity[i] && !link_state_from_sfp) links_up[i] = true; + } + + //TODO: Swap this when Ethernet port swap issues is fixed + soft_reg_write(led_reg, LED_REG_FIELD_ETH_LINK2, links_up[0]?1:0); + soft_reg_write(led_reg, LED_REG_FIELD_ETH_LINK1, links_up[1]?1:0); + soft_reg_write(led_reg, LED_REG_FIELD_ETH_ACT2, activity[0]?1:0); + soft_reg_write(led_reg, LED_REG_FIELD_ETH_ACT1, activity[1]?1:0); + + first_poll = 0; +} + diff --git a/firmware/usrp3/n230/n230_eth_handlers.h b/firmware/usrp3/n230/n230_eth_handlers.h new file mode 100644 index 000000000..67afbb246 --- /dev/null +++ b/firmware/usrp3/n230/n230_eth_handlers.h @@ -0,0 +1,48 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_ETH_HANDLERS_H +#define INCLUDED_N230_ETH_HANDLERS_H + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> +#include <lwip/ip_addr.h> +#include <wb_soft_reg.h> +#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h" + +/*! + * Registrar for host firmware communications handler. + */ +void n230_register_udp_fw_comms_handler(n230_host_shared_mem_t* shared_mem_ptr); + +/*! + * Registrar for framer programmer handler. + */ +void n230_register_udp_prog_framer(); + +/*! + * Registrar for host firmware communications handler. + */ +void n230_register_flash_comms_handler(); + +/*! + * Handle SFP updates. + */ +void n230_update_link_act_state(soft_reg_t* led_reg); + +#endif /* INCLUDED_N230_ETH_HANDLERS_H */ diff --git a/firmware/usrp3/n230/n230_init.c b/firmware/usrp3/n230/n230_init.c new file mode 100644 index 000000000..14f5ebd77 --- /dev/null +++ b/firmware/usrp3/n230/n230_init.c @@ -0,0 +1,125 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <cron.h> +#include <printf.h> +#include <wb_utils.h> +#include <wb_uart.h> +#include <wb_i2c.h> +#include <wb_pkt_iface64.h> +#include <u3_net_stack.h> +#include <print_addrs.h> +#include <trace.h> +#include "../../../host/lib/usrp/n230/n230_eeprom.h" +#include "n230_init.h" +#include "../../../host/lib/usrp/n230/n230_fw_defs.h" + +static wb_pkt_iface64_config_t pkt_config; + +static void putc(void *p, char c) +{ +//If FW_TRACE_LEVEL is defined, then the trace level is set +//to a non-zero number. Turn on the debug UART to enable tracing +#ifdef UHD_FW_TRACE_LEVEL + wb_uart_putc(WB_DBG_UART_BASE, c); +#endif +} + +static uint32_t get_counter_val() +{ + return wb_peek32(SR_ADDR(WB_SBRB_BASE, RB_ZPU_COUNTER)); +} + +void n230_init(void) +{ + //TODO: We may need to remove the debug UART before we release. + //Initialize the debug UART first. + wb_uart_init(WB_DBG_UART_BASE, CPU_CLOCK_FREQ/DBG_UART_BAUD); + init_printf(NULL, putc); + + //Now we can init the rest with prints + UHD_FW_TRACE_FSTR(INFO, "[ZPU Init Begin -- CPU CLOCK is %d MHz]", (CPU_CLOCK_FREQ/1000000)); + + //Initialize cron and the per millisecond cron job + UHD_FW_TRACE(INFO, "Initializing cron..."); + cron_init(get_counter_val, CPU_CLOCK_FREQ); + cron_job_init(PER_MILLISEC_CRON_JOBID, 1); + cron_job_init(PER_SECOND_CRON_JOBID, 1000); + + //Initialize rate for I2C cores + UHD_FW_TRACE(INFO, "Initializing I2C..."); + for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) { + wb_i2c_init((i==1)?WB_ETH1_I2C_BASE:WB_ETH0_I2C_BASE, CPU_CLOCK_FREQ); + } + + //Initialize eeprom + read_n230_eeprom(); + + UHD_FW_TRACE(INFO, "Initializing network stack..."); + init_network_stack(); +} + +void init_network_stack(void) +{ + //Hold Ethernet PHYs in reset + wb_poke32(SR_ADDR(WB_SBRB_BASE, SR_ZPU_SW_RST), SR_ZPU_SW_RST_PHY); + + //Initialize ethernet packet interface + pkt_config = wb_pkt_iface64_init(WB_PKT_RAM_BASE, WB_PKT_RAM_CTRL_OFFSET); + u3_net_stack_init(&pkt_config); + + //Initialize MACs + for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) { + init_ethernet_mac(i); + } + + //Pull Ethernet PHYs out of reset + wb_poke32(SR_ADDR(WB_SBRB_BASE, SR_ZPU_SW_RST), SR_ZPU_SW_RST_NONE); +} + +void init_ethernet_mac(uint32_t iface_num) +{ + UHD_FW_TRACE_FSTR(INFO, "Initializing eth%d...", iface_num); + + //Get interface info from the EEPROM (or defaults otherwise) + const n230_eth_eeprom_map_t* eth_eeprom_map = get_n230_ethernet_info(iface_num); + const eth_mac_addr_t *my_mac = (const eth_mac_addr_t *) &(eth_eeprom_map->mac_addr); + const struct ip_addr *my_ip = (const struct ip_addr *) &(eth_eeprom_map->ip_addr); + const struct ip_addr *subnet = (const struct ip_addr *) &(eth_eeprom_map->subnet); + + //Init software fields related to ethernet + u3_net_stack_init_eth(iface_num, my_mac, my_ip, subnet); + + uint32_t dispatcher_base = + ((iface_num == 1) ? SR_ZPU_ETHINT1 : SR_ZPU_ETHINT0) + SR_ZPU_ETHINT_DISPATCHER_BASE; + + //Program dispatcher + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 0), + (my_mac->addr[5] << 0) | (my_mac->addr[4] << 8) | (my_mac->addr[3] << 16) | (my_mac->addr[2] << 24)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 1), (my_mac->addr[1] << 0) | (my_mac->addr[0] << 8)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 2), my_ip->addr); + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 4), 0/*nofwd*/); + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 5), (ICMP_IRQ << 8) | 0); //no fwd: type, code + + //DEBUG: Print initialized info + UHD_FW_TRACE_FSTR(INFO, "-- MAC%u: %s", iface_num, mac_addr_to_str(u3_net_stack_get_mac_addr(iface_num))); + UHD_FW_TRACE_FSTR(INFO, "-- IP%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_ip_addr(iface_num))); + UHD_FW_TRACE_FSTR(INFO, "-- SUBNET%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_subnet(iface_num))); + UHD_FW_TRACE_FSTR(INFO, "-- BCAST%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_bcast(iface_num))); +} + + diff --git a/firmware/usrp3/n230/n230_init.h b/firmware/usrp3/n230/n230_init.h new file mode 100644 index 000000000..e2231909e --- /dev/null +++ b/firmware/usrp3/n230/n230_init.h @@ -0,0 +1,28 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_INIT_H +#define INCLUDED_N230_INIT_H + +#include <stdint.h> +#include <stdbool.h> + +void n230_init(void); +void init_network_stack(void); +void init_ethernet_mac(uint32_t iface_num); + +#endif /* INCLUDED_B250_INIT_H */ diff --git a/firmware/usrp3/n230/n230_main.c b/firmware/usrp3/n230/n230_main.c new file mode 100644 index 000000000..a6c12e56d --- /dev/null +++ b/firmware/usrp3/n230/n230_main.c @@ -0,0 +1,113 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <cron.h> +#include <wb_soft_reg.h> +#include <u3_net_stack.h> +#include <trace.h> +#include "../../../host/lib/usrp/n230/n230_fw_defs.h" +#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h" +#include "n230_eth_handlers.h" +#include "n230_init.h" + +//The version hash should come from a cmake build variable +//If it doesn't then the build system does not support the feature +//so just default to 0xFFFFFFFF +#ifndef UHD_VERSION_HASH +#define UHD_VERSION_HASH 0xFFFFFFFF +#endif + +//TODO: This is just for initial debugging. +static soft_reg_t g_led_register; + +//Shared memory +static n230_host_shared_mem_t g_host_shared_mem; + +//Functions +static void n230_handle_claim(); + +/*********************************************************************** + * Main loop runs all the handlers + **********************************************************************/ +int main(void) +{ + //Initialize host shared mem + g_host_shared_mem.data.fw_compat_num = N230_FW_COMPAT_NUM; + g_host_shared_mem.data.fw_version_hash = UHD_VERSION_HASH; + + //Main initialization function + n230_init(); + + //Initialize UDP Handlers + n230_register_udp_fw_comms_handler(&g_host_shared_mem); + n230_register_udp_prog_framer(); + n230_register_flash_comms_handler(); + + initialize_writeonly_soft_reg(&g_led_register, SR_ADDR(WB_SBRB_BASE, SR_ZPU_LEDS)); + + uint32_t heart_beat = 0; + while(true) + { + //TODO: This is just for initial debugging. Once the firmware + //is somewhat stable we should delete this cron job + if (cron_job_run_due(PER_SECOND_CRON_JOBID)) { + //Everything in this block runs approx once per second + if (heart_beat % 10 == 0) { + UHD_FW_TRACE_FSTR(INFO, "0.1Hz Heartbeat (%u)", heart_beat); + } + heart_beat++; + } + + if (cron_job_run_due(PER_MILLISEC_CRON_JOBID)) { + //Everything in this block runs approx once per millisecond + n230_handle_claim(); + n230_update_link_act_state(&g_led_register); + } + + //run the network stack - poll and handle + u3_net_stack_handle_one(); + } + return 0; +} + +// Watchdog timer for claimer +static void n230_handle_claim() +{ + static uint32_t last_time = 0; + static size_t timeout = 0; + + if (g_host_shared_mem.data.claim_time == 0) { + //If time is 0 if the claim was forfeit + g_host_shared_mem.data.claim_status = 0; + } else if (last_time != g_host_shared_mem.data.claim_time) { + //If the time changes, reset timeout + g_host_shared_mem.data.claim_status = 1; + timeout = 0; + } else { + //Otherwise increment for timeout + timeout++; + } + + //Always stash the last seen time + last_time = g_host_shared_mem.data.claim_time; + + //Timeout logic + if (timeout > N230_CLAIMER_TIMEOUT_IN_MS) { + g_host_shared_mem.data.claim_time = 0; + } +} + diff --git a/firmware/usrp3/utils/git-hash.sh b/firmware/usrp3/utils/git-hash.sh new file mode 100755 index 000000000..ff7ae5ecb --- /dev/null +++ b/firmware/usrp3/utils/git-hash.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +if [[ $(command -v git) = "" ]]; then + short_hash="FFFFFFFF" +else + if (git diff --quiet); then + #Clean + short_hash="0$(git rev-parse --verify HEAD --short)" + else + #Dirty + short_hash="F$(git rev-parse --verify HEAD --short)" + fi +fi +echo ${short_hash^^}
\ No newline at end of file diff --git a/firmware/usrp3/x300/x300_aurora_bist.py b/firmware/usrp3/x300/x300_aurora_bist.py new file mode 100755 index 000000000..225cd030b --- /dev/null +++ b/firmware/usrp3/x300/x300_aurora_bist.py @@ -0,0 +1,213 @@ +from __future__ import print_function +from builtins import str +from builtins import range +#!/usr/bin/env python +# +# Copyright 2016 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import x300_debug +import argparse +import time +import datetime +import math +import tqdm + +######################################################################## +# constants +######################################################################## +SFP0_MAC_REG_BASE = 0xC000 +SFP1_MAC_REG_BASE = 0xD000 +SFP0_TYPE_REG_OFFSET = 0xA000 + 16 +SFP1_TYPE_REG_OFFSET = 0xA000 + 20 +SFP0_STATUS_REG_OFFSET = 0xA000 + 32 +SFP1_STATUS_REG_OFFSET = 0xA000 + 36 + +SFP_TYPE_AURORA = 2 + +MAC_REG_CTRL = 0 +MAC_REG_STATUS = 0 +MAC_REG_OVERRUNS = 4 +MAC_REG_BIST_SAMPS = 8 +MAC_REG_BIST_ERRORS = 12 + +MAC_STATUS_LINK_UP_MSK = 0x00000001 +MAC_STATUS_HARD_ERR_MSK = 0x00000002 +MAC_STATUS_SOFT_ERR_MSK = 0x00000004 +MAC_STATUS_BIST_LOCKED_MSK = 0x00000008 +MAC_STATUS_BIST_LATENCY_MSK = 0x000FFFF0 +MAC_STATUS_BIST_LATENCY_OFFSET = 4 +MAC_STATUS_CHECKSUM_ERRS_MSK = 0xFFF00000 +MAC_STATUS_CHECKSUM_ERRS_OFFSET = 20 + +MAC_CTRL_BIST_CHECKER_EN = 0x00000001 +MAC_CTRL_BIST_GEN_EN = 0x00000002 +MAC_CTRL_BIST_LOOPBACK_EN = 0x00000004 +MAC_CTRL_PHY_RESET = 0x00000100 +MAC_CTRL_BIST_RATE_MSK = 0x000000F8 +MAC_CTRL_BIST_RATE_OFFSET = 3 + +AURORA_CLK_RATE = 156.25e6 +BUS_CLK_RATE = 166.66e6 +BIST_MAX_TIME_LIMIT = math.floor(pow(2,48)/AURORA_CLK_RATE)-1 + +######################################################################## +# utils +######################################################################## +def get_aurora_info(ctrl): + if (ctrl.peek(SFP0_TYPE_REG_OFFSET) == SFP_TYPE_AURORA): + aur_port = 0 + elif (ctrl.peek(SFP1_TYPE_REG_OFFSET) == SFP_TYPE_AURORA): + aur_port = 1 + else: + aur_port = -1 + link_up = False + if aur_port != -1: + mac_base = SFP0_MAC_REG_BASE if aur_port == 0 else SFP1_MAC_REG_BASE + link_up = ((ctrl.peek(mac_base + MAC_REG_STATUS) & MAC_STATUS_LINK_UP_MSK) != 0) + return (aur_port, link_up) + +def get_rate_setting(rate): + for div in range(2,32): + if (rate < 8e-6 * BUS_CLK_RATE * (1.0 - 1.0/div)): + return (div-1, 8e-6 * BUS_CLK_RATE * (1.0 - 1.0/(div-1))) + return (0, 8e-6 * BUS_CLK_RATE) + +def run_loopback_bist(ctrls, duration, xxx_todo_changeme): + (rate_sett, rate) = xxx_todo_changeme + print('[INFO] Running Loopback BIST at %.0fMB/s for %.0fs...'%(rate,duration)) + # Determine offsets + (mst_port, link_up) = get_aurora_info(ctrls['master']) + MST_MAC_REG_BASE = SFP0_MAC_REG_BASE if (mst_port == 0) else SFP1_MAC_REG_BASE + if 'slave' in ctrls: + (sla_port, link_up) = get_aurora_info(ctrls['slave']) + SLA_MAC_REG_BASE = SFP0_MAC_REG_BASE if (sla_port == 0) else SFP1_MAC_REG_BASE + # Reset both PHYS + ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_PHY_RESET) + ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, 0) + if 'slave' in ctrls: + ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_PHY_RESET) + ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0) + time.sleep(1.5) + # Put the slave in loopback mode and the master in BIST mode + if 'slave' in ctrls: + ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_LOOPBACK_EN) + if rate_sett == 0: + master_ctrl = MAC_CTRL_BIST_GEN_EN|MAC_CTRL_BIST_CHECKER_EN + else: + master_ctrl = ((rate_sett - 1) << MAC_CTRL_BIST_RATE_OFFSET)|MAC_CTRL_BIST_GEN_EN|MAC_CTRL_BIST_CHECKER_EN + ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, master_ctrl) + start_time = datetime.datetime.now() + # Wait and check if BIST locked + time.sleep(0.5) + mst_status = ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_STATUS) + if (not (mst_status & MAC_STATUS_BIST_LOCKED_MSK)): + print('[ERROR] BIST engine did not lock onto a PRBS word!') + # Wait for requested time + try: + for i in tqdm.tqdm(list(range(duration)), desc='[INFO] Progress'): + time.sleep(1.0) + except KeyboardInterrupt: + print('[WARNING] Operation cancelled by user.') + # Turn off the BIST generator and loopback + ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN) + stop_time = datetime.datetime.now() + time.sleep(0.5) + if 'slave' in ctrls: + ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0) + # Validate status and no overruns + mst_status = ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_STATUS) + mst_overruns = ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_OVERRUNS) + if (mst_status & MAC_STATUS_HARD_ERR_MSK): + print('[ERROR] Hard errors in master PHY') + if (mst_overruns > 0): + print('[ERROR] Buffer overruns in master PHY') + if 'slave' in ctrls: + sla_status = ctrls['slave'].peek(SLA_MAC_REG_BASE + MAC_REG_STATUS) + sla_overruns = ctrls['slave'].peek(SLA_MAC_REG_BASE + MAC_REG_OVERRUNS) + if (sla_status & MAC_STATUS_HARD_ERR_MSK): + print('[ERROR] Hard errors in slave PHY') + if (sla_overruns > 0): + print('[ERROR] Buffer overruns in slave PHY') + # Compure latency + mst_samps = 65536.0*ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_BIST_SAMPS) + mst_errors = 1.0*ctrls['master'].peek(MST_MAC_REG_BASE + MAC_REG_BIST_ERRORS) + if (mst_samps != 0): + mst_latency_cyc = 16.0*((mst_status & MAC_STATUS_BIST_LATENCY_MSK) >> MAC_STATUS_BIST_LATENCY_OFFSET) + time_diff = stop_time - start_time + print('[INFO] BIST Complete!') + print('- Elapsed Time = ' + str(time_diff)) + print('- Max BER (Bit Error Ratio) = %.4g (%d errors out of %d)'%((mst_errors+1)/mst_samps,mst_errors,mst_samps)) + print('- Max Roundtrip Latency = %.1fus'%(1e6*mst_latency_cyc/AURORA_CLK_RATE)) + print('- Approx Throughput = %.0fMB/s'%((8e-6*mst_samps)/time_diff.total_seconds())) + else: + print('[ERROR] BIST Failed!') + # Drain and Cleanup + print('[INFO] Cleaning up...') + ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN) + if 'slave' in ctrls: + ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, MAC_CTRL_BIST_CHECKER_EN) + time.sleep(0.5) + ctrls['master'].poke(MST_MAC_REG_BASE + MAC_REG_CTRL, 0) + if 'slave' in ctrls: + ctrls['slave'].poke(SLA_MAC_REG_BASE + MAC_REG_CTRL, 0) + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = argparse.ArgumentParser(description='Controller for the USRP X3X0 Aurora BIST Engine') + parser.add_argument('--master', type=str, default=None, required=True, help='IP Address of master USRP-X3X0 device') + parser.add_argument('--slave', type=str, default=None, help='IP Address of slave USRP-X3X0 device') + parser.add_argument('--duration', type=int, default=10, help='Duration of test in seconds') + parser.add_argument('--rate', type=int, default=1245, help='BIST throughput in MB/s') + return parser.parse_args() + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + + if (options.duration < 0 or options.duration > BIST_MAX_TIME_LIMIT): + raise Exception('Invalid duration. Min = 0s and Max = %ds'%(BIST_MAX_TIME_LIMIT)) + + ctrls = dict() + ctrls['master'] = x300_debug.ctrl_socket(addr=options.master) + if options.slave: + ctrls['slave'] = x300_debug.ctrl_socket(addr=options.slave) + + # Report device and core info + links_up = True + for node in ctrls: + print('[INFO] ' + node.upper() + ':') + ctrl = ctrls[node] + (aur_port, link_up) = get_aurora_info(ctrl) + if aur_port >= 0: + status_str = str(aur_port) + (' UP' if link_up else ' DOWN') + else: + status_str = 'Not Detected!' + print('- Mgmt IP Addr : ' + (options.master if node == 'master' else options.slave)) + print('- Aurora Status : Port ' + status_str) + links_up = links_up & link_up + + # Sanity check + if not links_up: + print('[ERROR] At least one of the links is down. Cannot proceed.') + exit(1) + + # Run BIST + run_loopback_bist(ctrls, options.duration, get_rate_setting(options.rate)) diff --git a/firmware/usrp3/x300/x300_debug.py b/firmware/usrp3/x300/x300_debug.py index c9bcbb138..e134a7275 100755 --- a/firmware/usrp3/x300/x300_debug.py +++ b/firmware/usrp3/x300/x300_debug.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2010-2011 Ettus Research LLC +# Copyright 2010-2014 Ettus Research LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,27 +21,36 @@ import math import socket import struct - ######################################################################## # constants ######################################################################## -B250_FW_COMMS_UDP_PORT = 49152 +X300_FW_COMMS_UDP_PORT = 49152 + +X300_FW_COMMS_FLAGS_ACK = 1 +X300_FW_COMMS_FLAGS_ERROR = 2 +X300_FW_COMMS_FLAGS_POKE32 = 4 +X300_FW_COMMS_FLAGS_PEEK32 = 8 + +X300_FIXED_PORTS = 5 -B250_FW_COMMS_FLAGS_ACK = 1 -B250_FW_COMMS_FLAGS_ERROR = 2 -B250_FW_COMMS_FLAGS_POKE32 = 4 -B250_FW_COMMS_FLAGS_PEEK32 = 8 +X300_ZPU_MISC_SR_BUS_OFFSET = 0xA000 +X300_ZPU_XBAR_SR_BUS_OFFSET = 0xB000 + +# Settings register bus addresses (hangs off ZPU wishbone bus) +# Multiple by 4 as ZPU wishbone bus is word aligned +X300_SR_NUM_CE = X300_ZPU_MISC_SR_BUS_OFFSET + 4*7 +X300_SR_RB_ADDR_XBAR = X300_ZPU_MISC_SR_BUS_OFFSET + 4*128 +# Readback addresses +X300_RB_CROSSBAR = X300_ZPU_MISC_SR_BUS_OFFSET + 4*128 #UDP_CTRL_PORT = 49183 UDP_MAX_XFER_BYTES = 1024 UDP_TIMEOUT = 3 -#USRP2_FW_PROTO_VERSION = 11 #should be unused after r6 #REG_ARGS_FMT = '!LLLLLB15x' #REG_IP_FMT = '!LLLL20x' REG_PEEK_POKE_FMT = '!LLLL' - _seq = -1 def seq(): global _seq @@ -66,7 +75,7 @@ class ctrl_socket(object): def __init__(self, addr): self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._sock.settimeout(UDP_TIMEOUT) - self._sock.connect((addr, B250_FW_COMMS_UDP_PORT)) + self._sock.connect((addr, X300_FW_COMMS_UDP_PORT)) self.set_callbacks(lambda *a: None, lambda *a: None) #self.init_update() #check that the device is there @@ -78,47 +87,71 @@ class ctrl_socket(object): self._sock.send(pkt) return self._sock.recv(UDP_MAX_XFER_BYTES) - def read_router_stats(self): + def read_router_stats(self, blocks=None, ignore=None): + # Readback number of CEs + num_ports = self.peek(X300_SR_NUM_CE) + X300_FIXED_PORTS + ports = ['eth0', 'eth1', 'pcie', 'radio0', 'radio1'] + ["Block{num}".format(num=x) for x in range(num_ports-X300_FIXED_PORTS)] + if blocks is None: + print("\nNote: Using default CE port names (use --blocks to specify)\n") + else: + user_ports = [x.strip() for x in blocks.split(",") if len(x.strip())] + for idx, user_port in enumerate(user_ports): + ports[idx + X300_FIXED_PORTS] = user_port + if ignore is None: + ignore = [] + else: + ignore = [int(x.strip()) for x in ignore.split(",") if len(x.strip())] + print("Egress Port "), + # Write out xbar ports + PORT_MAX_LEN = 12 + for idx, in_prt in enumerate(ports): + if idx in ignore: + continue + print "{spaces}{name}".format(spaces=(" "*max(0, PORT_MAX_LEN-len(in_prt))), name=in_prt), print - print(" "), - ports = [' eth0',' eth1',' radio0',' radio1',' compute0',' compute1',' compute2',' pcie'] - for in_prt in ports: - print("%s" % in_prt), - print(" Egress Port") - print(" "), - for in_prt in range (0, 8): - print("____________"), + print(" "*(PORT_MAX_LEN+1)), + for in_prt in range(num_ports-len(ignore)): + print("_" * (PORT_MAX_LEN-1) + " "), print - for in_prt in range (0, 8): - print("%s |" % ports[in_prt]), - for out_prt in range (0, 8): - out_pkt = pack_reg_peek_poke_fmt(B250_FW_COMMS_FLAGS_PEEK32|B250_FW_COMMS_FLAGS_ACK, seq(), 0xA000+256+((in_prt*8+out_prt)*4), 0) - in_pkt = self.send_and_recv(out_pkt) - (flags, rxseq, addr, data) = unpack_reg_peek_poke_fmt(in_pkt) - if flags & B250_FW_COMMS_FLAGS_ERROR == B250_FW_COMMS_FLAGS_ERROR: - raise Exception("B250 peek returns error code") + for in_prt, port_name in enumerate(ports): + if in_prt in ignore: + continue + print "{spaces}{name} |".format(spaces=(" "*max(0, PORT_MAX_LEN-len(port_name))), name=port_name), + for out_prt in range(num_ports): + if out_prt in ignore: + continue + self.poke(X300_SR_RB_ADDR_XBAR,(in_prt*num_ports+out_prt)) + data = self.peek(X300_RB_CROSSBAR) print("%10d " % (data)), print print print("Ingress Port") print + def peek_print(self,peek_addr): + peek_data = self.peek(peek_addr) + print("PEEK of address %d(0x%x) reads %d(0x%x)" % (peek_addr,peek_addr,peek_data,peek_data)) + return peek_data def peek(self,peek_addr): - out_pkt = pack_reg_peek_poke_fmt(B250_FW_COMMS_FLAGS_PEEK32|B250_FW_COMMS_FLAGS_ACK, seq(), peek_addr, 0) + out_pkt = pack_reg_peek_poke_fmt(X300_FW_COMMS_FLAGS_PEEK32|X300_FW_COMMS_FLAGS_ACK, seq(), peek_addr, 0) in_pkt = self.send_and_recv(out_pkt) (flags, rxseq, addr, data) = unpack_reg_peek_poke_fmt(in_pkt) - if flags & B250_FW_COMMS_FLAGS_ERROR == B250_FW_COMMS_FLAGS_ERROR: - raise Exception("B250 peek of address %d returns error code" % (addr)) - print("PEEK of address %d(0x%x) reads %d(0x%x)" % (addr,addr,data,data)) + if flags & X300_FW_COMMS_FLAGS_ERROR == X300_FW_COMMS_FLAGS_ERROR: + raise Exception("X300 peek of address %d returns error code" % (addr)) + return data + + def poke_print(self,poke_addr,poke_data): + print("POKE of address %d(0x%x) with %d(0x%x)" % (poke_addr,poke_addr,poke_data,poke_data)) + return(self.poke(poke_addr,poke_data)) def poke(self,poke_addr,poke_data): - out_pkt = pack_reg_peek_poke_fmt(B250_FW_COMMS_FLAGS_POKE32|B250_FW_COMMS_FLAGS_ACK, seq(), poke_addr, poke_data) + out_pkt = pack_reg_peek_poke_fmt(X300_FW_COMMS_FLAGS_POKE32|X300_FW_COMMS_FLAGS_ACK, seq(), poke_addr, poke_data) in_pkt = self.send_and_recv(out_pkt) (flags, rxseq, addr, data) = unpack_reg_peek_poke_fmt(in_pkt) - if flags & B250_FW_COMMS_FLAGS_ERROR == B250_FW_COMMS_FLAGS_ERROR: - raise Exception("B250 peek of address %d returns error code" % (addr)) - print("POKE of address %d(0x%x) with %d(0x%x)" % (poke_addr,poke_addr,poke_data,poke_data) ) + if flags & X300_FW_COMMS_FLAGS_ERROR == X300_FW_COMMS_FLAGS_ERROR: + raise Exception("X300 peek of address %d returns error code" % (addr)) + return data ######################################################################## @@ -126,14 +159,14 @@ class ctrl_socket(object): ######################################################################## def get_options(): parser = optparse.OptionParser() - parser.add_option("--addr", type="string", help="USRP-N2XX device address", default='') - parser.add_option("--list", action="store_true", help="list possible network devices", default=False) - parser.add_option("--peek", type="int", help="Read from memory map", default=None) - parser.add_option("--poke", type="int", help="Write to memory map", default=None) - parser.add_option("--data", type="int", help="Data for poke", default=None) - parser.add_option("--stats", action="store_true", help="Display SuperMIMO Network Stats", default=False) + parser.add_option("--addr", type="string", help="USRP-X300 device address", default='') + parser.add_option("--stats", action="store_true", help="Display RFNoC Crossbar Stats", default=False) + parser.add_option("--peek", type="int", help="Read from memory map", default=None) + parser.add_option("--poke", type="int", help="Write to memory map", default=None) + parser.add_option("--data", type="int", help="Data for poke", default=None) + parser.add_option("--blocks", help="List names of blocks (post-radio)", default=None) + parser.add_option("--ignore", help="List of ports to ignore", default=None) (options, args) = parser.parse_args() - return options @@ -143,25 +176,18 @@ def get_options(): if __name__=='__main__': options = get_options() - - if options.list: - print('Possible network devices:') - print(' ' + '\n '.join(enumerate_devices())) - exit() - if not options.addr: raise Exception('no address specified') status = ctrl_socket(addr=options.addr) if options.stats: - status.read_router_stats() - + status.read_router_stats(options.blocks, options.ignore) if options.peek is not None: addr = options.peek - status.peek(addr) + status.peek_print(addr) if options.poke is not None and options.data is not None: addr = options.poke data = options.data - status.poke(addr,data) + status.poke_print(addr,data) diff --git a/firmware/usrp3/x300/x300_defs.h b/firmware/usrp3/x300/x300_defs.h index c4011bd12..efd44b49d 100644 --- a/firmware/usrp3/x300/x300_defs.h +++ b/firmware/usrp3/x300/x300_defs.h @@ -7,8 +7,8 @@ #define CPU_CLOCK 166666667 #define MAIN_RAM_BASE 0x0000 #define PKT_RAM0_BASE 0x8000 -#define XGE0_BASE 0xC000 -#define XGE1_BASE 0xD000 +#define SFP0_MAC_BASE 0xC000 +#define SFP1_MAC_BASE 0xD000 #define BOOT_LDR_BASE 0xFC00 #define UART0_BASE 0xfd00 #define UART0_BAUD 115200 @@ -21,9 +21,6 @@ #define RB0_BASE 0xa000 //same as set #define SETXB_BASE 0xb000 -#define ETH1G -//#define ETH10G - //eeprom map for mboard addrs #define MBOARD_EEPROM_ADDR 0x50 @@ -36,6 +33,7 @@ static const int SR_SFPP_CTRL = 4; static const int SR_SPI = 32; static const int SR_ETHINT0 = 40; static const int SR_ETHINT1 = 56; +static const int SR_RB_ADDR = 128; //led shifts for SR_LEDS static const int LED_ACT1 = (1 << 5); @@ -49,11 +47,12 @@ static const int LED_LINKACT = (1 << 0); static const int RB_COUNTER = 0; static const int RB_SPI_RDY = 1; static const int RB_SPI_DATA = 2; -static const int RB_ETH_TYPE0 = 4; -static const int RB_ETH_TYPE1 = 5; +static const int RB_SFP0_TYPE = 4; +static const int RB_SFP1_TYPE = 5; static const int RB_FPGA_COMPAT = 6; -static const int RB_SFPP_STATUS0 = 8; -static const int RB_SFPP_STATUS1 = 9; +static const int RB_SFP0_STATUS = 8; +static const int RB_SFP1_STATUS = 9; +static const int RB_XBAR = 128; // Bootloader Memory Map static const int BL_ADDRESS = 0; @@ -73,4 +72,9 @@ static const int BL_DATA = 1; #define ETH_FRAMER_DST_UDP_MAC 6 #define ETH_FRAMER_DST_MAC_LO 7 +// SFP type constants +#define RB_SFP_1G_ETH 0 +#define RB_SFP_10G_ETH 1 +#define RB_SFP_AURORA 2 + #endif /* INCLUDED_X300_DEFS_H */ diff --git a/firmware/usrp3/x300/x300_init.c b/firmware/usrp3/x300/x300_init.c index ef97412a2..57c1faa2c 100644 --- a/firmware/usrp3/x300/x300_init.c +++ b/firmware/usrp3/x300/x300_init.c @@ -1,7 +1,7 @@ #include "x300_init.h" #include "x300_defs.h" #include "ethernet.h" -#include "mdelay.h" +#include "cron.h" #include <wb_utils.h> #include <wb_uart.h> #include <wb_i2c.h> @@ -84,8 +84,8 @@ static void init_network(void) wb_i2c_read(I2C1_BASE, MBOARD_EEPROM_ADDR, (uint8_t *)(&eeprom_map), sizeof(eeprom_map)); //determine interface number - const size_t eth0no = wb_peek32(SR_ADDR(RB0_BASE, RB_ETH_TYPE0))? 2 : 0; - const size_t eth1no = wb_peek32(SR_ADDR(RB0_BASE, RB_ETH_TYPE1))? 3 : 1; + const size_t eth0no = wb_peek32(SR_ADDR(RB0_BASE, RB_SFP0_TYPE))? 2 : 0; + const size_t eth1no = wb_peek32(SR_ADDR(RB0_BASE, RB_SFP1_TYPE))? 3 : 1; //pick the address from eeprom or default const eth_mac_addr_t *my_mac0 = (const eth_mac_addr_t *)pick_inited_field(&eeprom_map.mac_addr0, &default_map.mac_addr0, 6); @@ -121,6 +121,11 @@ static void putc(void *p, char c) #endif } +static uint32_t get_counter_val() +{ + return wb_peek32(SR_ADDR(RB0_BASE, RB_COUNTER)); +} + void x300_init(void) { //first - uart @@ -136,6 +141,9 @@ void x300_init(void) UHD_FW_TRACE_FSTR(INFO, "-- FPGA Compat Number: %u.%u", (fpga_compat>>16), (fpga_compat&0xFFFF)); UHD_FW_TRACE_FSTR(INFO, "-- Clock Frequency: %u MHz", (CPU_CLOCK/1000000)); + //Initialize cron + cron_init(get_counter_val, CPU_CLOCK); + //i2c rate init wb_i2c_init(I2C0_BASE, CPU_CLOCK); wb_i2c_init(I2C1_BASE, CPU_CLOCK); @@ -151,19 +159,23 @@ void x300_init(void) wb_poke32(SR_ADDR(SET0_BASE, SR_SW_RST), 0); //print network summary - for (uint8_t e = 0; e < ethernet_ninterfaces(); e++) + for (uint8_t sfp = 0; sfp < ethernet_ninterfaces(); sfp++) { - uint32_t offset = SR_ADDR(RB0_BASE, ((e==1)?RB_ETH_TYPE1:RB_ETH_TYPE0)); - UHD_FW_TRACE_FSTR(INFO, "Ethernet Port %u:", (int)e); - UHD_FW_TRACE_FSTR(INFO, "-- PHY: %s", ((wb_peek32(offset)==1) ? "10Gbps" : "1Gbps")); - UHD_FW_TRACE_FSTR(INFO, "-- MAC: %s", mac_addr_to_str(u3_net_stack_get_mac_addr(e))); - UHD_FW_TRACE_FSTR(INFO, "-- IP: %s", ip_addr_to_str(u3_net_stack_get_ip_addr(e))); - UHD_FW_TRACE_FSTR(INFO, "-- SUBNET: %s", ip_addr_to_str(u3_net_stack_get_subnet(e))); - UHD_FW_TRACE_FSTR(INFO, "-- BCAST: %s", ip_addr_to_str(u3_net_stack_get_bcast(e))); + uint32_t sfp_type = wb_peek32(SR_ADDR(RB0_BASE, ((sfp==1) ? RB_SFP1_TYPE : RB_SFP0_TYPE))); + UHD_FW_TRACE_FSTR(INFO, "SFP+ Port %u:", (int)sfp); + if (sfp_type == RB_SFP_AURORA) { + UHD_FW_TRACE (INFO, "-- PHY: 10Gbps Aurora"); + } else { + UHD_FW_TRACE_FSTR(INFO, "-- PHY: %s", (sfp_type == RB_SFP_10G_ETH) ? "10Gbps Ethernet" : "1Gbps Ethernet"); + UHD_FW_TRACE_FSTR(INFO, "-- MAC: %s", mac_addr_to_str(u3_net_stack_get_mac_addr(sfp))); + UHD_FW_TRACE_FSTR(INFO, "-- IP: %s", ip_addr_to_str(u3_net_stack_get_ip_addr(sfp))); + UHD_FW_TRACE_FSTR(INFO, "-- SUBNET: %s", ip_addr_to_str(u3_net_stack_get_subnet(sfp))); + UHD_FW_TRACE_FSTR(INFO, "-- BCAST: %s", ip_addr_to_str(u3_net_stack_get_bcast(sfp))); + } } // For eth interfaces, initialize the PHY's - mdelay(100); + sleep_ms(100); ethernet_init(0); ethernet_init(1); } diff --git a/firmware/usrp3/x300/x300_main.c b/firmware/usrp3/x300/x300_main.c index 3b812a2c4..42ee3248b 100644 --- a/firmware/usrp3/x300/x300_main.c +++ b/firmware/usrp3/x300/x300_main.c @@ -6,7 +6,6 @@ #include "xge_phy.h" #include "ethernet.h" #include "chinch.h" -#include "mdelay.h" #include <wb_utils.h> #include <wb_uart.h> @@ -247,18 +246,16 @@ static void handle_claim(void) /*********************************************************************** * LED blinky logic and support utilities **********************************************************************/ -static uint32_t get_xbar_total(const uint8_t port) +static uint32_t get_xbar_total(const uint32_t port) { - #define get_xbar_stat(in_prt, out_prt) \ - wb_peek32(RB0_BASE+256+(((in_prt)*8+(out_prt))*4)) + static const uint32_t NUM_PORTS = 16; uint32_t total = 0; - for (size_t i = 0; i < 8; i++) + for (uint32_t i = 0; i < NUM_PORTS; i++) { - total += get_xbar_stat(port, i); - } - for (size_t i = 0; i < 8; i++) - { - total += get_xbar_stat(i, port); + wb_poke32(SET0_BASE + SR_RB_ADDR*4, (NUM_PORTS*port + i)); + total += wb_peek32(RB0_BASE + RB_XBAR*4); + wb_poke32(SET0_BASE + SR_RB_ADDR*4, (NUM_PORTS*i + port)); + total += wb_peek32(RB0_BASE + RB_XBAR*4); } if (port < 2) //also netstack if applicable { @@ -280,10 +277,10 @@ static size_t popcntll(uint64_t num) static void update_leds(void) { //update activity status for all ports - uint64_t activity_shreg[8]; - for (size_t i = 0; i < 8; i++) + uint64_t activity_shreg[16]; + for (uint32_t i = 0; i < 16; i++) { - static uint32_t last_total[8]; + static uint32_t last_total[16]; const uint32_t total = get_xbar_total(i); activity_shreg[i] <<= 1; activity_shreg[i] |= (total == last_total[i])? 0 : 1; @@ -321,8 +318,10 @@ static void garp(void) count = 0; for (size_t e = 0; e < ethernet_ninterfaces(); e++) { - if (!ethernet_get_link_up(e)) continue; - u3_net_stack_send_arp_request(e, u3_net_stack_get_ip_addr(e)); + if (wb_peek32(SR_ADDR(RB0_BASE, e == 0 ? RB_SFP0_TYPE : RB_SFP1_TYPE)) != RB_SFP_AURORA) { + if (!ethernet_get_link_up(e)) continue; + u3_net_stack_send_arp_request(e, u3_net_stack_get_ip_addr(e)); + } } } @@ -447,7 +446,7 @@ int main(void) { poll_sfpp_status(0); // Every so often poll XGE Phy to look for SFP+ hotplug events. poll_sfpp_status(1); // Every so often poll XGE Phy to look for SFP+ hotplug events. - handle_link_state(); //deal with router table update + //handle_link_state(); //deal with router table update handle_claim(); //deal with the host claim register update_leds(); //run the link and activity leds garp(); //send periodic garps diff --git a/fpga-src b/fpga-src -Subproject 593aae943b7d18e4e165ccd91a6b6581005e62e +Subproject 275be08783b6d57dfbf5471ac151b984f1cd2e2 diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index 9534dd7cd..643857708 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -31,6 +31,56 @@ ENABLE_TESTING() list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules) ######################################################################## +# Check Compiler Version +######################################################################## +# Full C++11 came with GCC 4.7. +SET(GCC_MIN_VERSION "4.8.0") +# for c++0x or c++11 support, require: +# Apple Clang >= 500 +# or +# Clang >= 3.3.0 +SET(CLANG_MIN_VERSION "3.3.0") +SET(APPLECLANG_MIN_VERSION "500") + +IF(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") + IF(DEFINED CMAKE_CXX_COMPILER_VERSION) + IF(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS ${GCC_MIN_VERSION}) + MESSAGE(WARNING "\nThe compiler selected to build UHD (GCC version ${CMAKE_CXX_COMPILER_VERSION} : ${CMAKE_CXX_COMPILER}) is older than that officially supported (${GCC_MIN_VERSION} minimum). This build may or not work. We highly recommend using a more recent GCC version.") + ENDIF() + ELSE() + MESSAGE(WARNING "\nCannot determine the version of the compiler selected to build UHD (GCC : ${CMAKE_CXX_COMPILER}). This build may or not work. We highly recommend using GCC version ${GCC_MIN_VERSION} or more recent.") + ENDIF() + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") +ELSEIF(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + EXECUTE_PROCESS(COMMAND + ${CMAKE_CXX_COMPILER} -v + RESULT_VARIABLE res ERROR_VARIABLE err + ERROR_STRIP_TRAILING_WHITESPACE) + IF(${res} STREQUAL "0") + # output is in error stream + STRING(REGEX MATCH "^Apple.*" IS_APPLE ${err}) + IF("${IS_APPLE}" STREQUAL "") + SET(MIN_VERSION ${CLANG_MIN_VERSION}) + SET(APPLE_STR "") + # retrieve the compiler's version from it + STRING(REGEX MATCH "clang version [0-9.]+" CLANG_OTHER_VERSION ${err}) + STRING(REGEX MATCH "[0-9.]+" CLANG_VERSION ${CLANG_OTHER_VERSION}) + ELSE() + SET(MIN_VERSION ${APPLECLANG_MIN_VERSION}) + SET(APPLE_STR "Apple ") + # retrieve the compiler's version from it + STRING(REGEX MATCH "(clang-[0-9.]+)" CLANG_APPLE_VERSION ${err}) + STRING(REGEX MATCH "[0-9.]+" CLANG_VERSION ${CLANG_APPLE_VERSION}) + ENDIF() + IF(${CLANG_VERSION} VERSION_LESS "${MIN_VERSION}") + MESSAGE(WARNING "\nThe compiler selected to build UHD (${APPLE_STR}Clang version ${CLANG_VERSION} : ${CMAKE_CXX_COMPILER}) is older than that officially supported (${MIN_VERSION} minimum). This build may or not work. We highly recommend using Apple Clang version ${APPLECLANG_MIN_VERSION} or more recent, or Clang version ${CLANG_MIN_VERSION} or more recent.") + ENDIF() + ELSE() + MESSAGE(WARNING "\nCannot determine the version of the compiler selected to build UHD (${APPLE_STR}Clang : ${CMAKE_CXX_COMPILER}). This build may or not work. We highly recommend using Apple Clang version ${APPLECLANG_MIN_VERSION} or more recent, or Clang version ${CLANG_MIN_VERSION} or more recent.") + ENDIF() +ENDIF() + +######################################################################## # Packaging Variables ######################################################################## @@ -138,7 +188,8 @@ SET(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "") #force UHD_RELEASE_MODE to be a string for cmake-gui SET(UHD_RELEASE_MODE "${UHD_RELEASE_MODE}" CACHE STRING "UHD Release Mode") -IF(CMAKE_COMPILER_IS_GNUCXX) +IF(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR + ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") IF(STRIP_BINARIES) IF(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") @@ -155,7 +206,7 @@ IF(CMAKE_COMPILER_IS_GNUCXX) UHD_ADD_OPTIONAL_CXX_COMPILER_FLAG(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN) UHD_ADD_OPTIONAL_CXX_COMPILER_FLAG(-fvisibility-inlines-hidden HAVE_VISIBILITY_INLINES_HIDDEN) ENDIF(NOT WIN32) -ENDIF(CMAKE_COMPILER_IS_GNUCXX) +ENDIF() IF(MSVC) INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/cmake/msvc) @@ -187,6 +238,7 @@ ENDIF(WIN32) MESSAGE(STATUS "") MESSAGE(STATUS "Configuring Boost C++ Libraries...") SET(BOOST_REQUIRED_COMPONENTS + chrono date_time filesystem program_options @@ -217,9 +269,9 @@ ENDIF(MSVC) SET(Boost_ADDITIONAL_VERSIONS "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.48.0" "1.49" "1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54" "1.55.0" "1.55" - "1.56.0" "1.56" + "1.56.0" "1.56" "1.57" "1.57" "1.58" "1.59" "1.60" ) -FIND_PACKAGE(Boost 1.46 COMPONENTS ${BOOST_REQUIRED_COMPONENTS}) +FIND_PACKAGE(Boost 1.53 COMPONENTS ${BOOST_REQUIRED_COMPONENTS}) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) @@ -231,6 +283,8 @@ MESSAGE(STATUS "Boost libraries: ${Boost_LIBRARIES}") ######################################################################## # Additional settings for build environment ######################################################################## +# Note: RFNoC never gets fully disabled, but the public APIs do +SET(ENABLE_RFNOC OFF CACHE BOOL "Export RFNoC includes and symbols") INCLUDE(UHDGlobalDefs) ######################################################################## @@ -239,8 +293,8 @@ INCLUDE(UHDGlobalDefs) INCLUDE(UHDPython) PYTHON_CHECK_MODULE( - "Python version 2.6 or greater" - "platform" "platform.python_version() >= '2.6'" + "Python version 2.7 or greater" + "platform" "platform.python_version() >= '2.7'" HAVE_PYTHON_PLAT_MIN_VERSION ) @@ -278,8 +332,8 @@ UHD_INSTALL(FILES #{{{IMG_SECTION # This section is written automatically by /images/create_imgs_package.py # Any manual changes in here will be overwritten. -SET(UHD_IMAGES_MD5SUM "c33b36a74d82198c8cfede64d8775c46") -SET(UHD_IMAGES_DOWNLOAD_SRC "uhd-images_003.009.004-release.zip") +SET(UHD_IMAGES_MD5SUM "26473d4f18d2c9a207062e107bda1d11") +SET(UHD_IMAGES_DOWNLOAD_SRC "uhd-images_003.010.000.000-release.zip") #}}} ######################################################################## @@ -309,8 +363,6 @@ ENDIF(EXISTS "${FPGA_SUBMODULE_DIR}/docs/fpga.md") ######################################################################## # Add the subdirectories ######################################################################## -ADD_SUBDIRECTORY(docs) - IF(ENABLE_LIBUHD) ADD_SUBDIRECTORY(lib) ENDIF(ENABLE_LIBUHD) @@ -329,6 +381,8 @@ IF(ENABLE_UTILS) ADD_SUBDIRECTORY(utils) ENDIF(ENABLE_UTILS) +ADD_SUBDIRECTORY(docs) + ######################################################################## # Create Pkg Config File ######################################################################## @@ -380,6 +434,12 @@ ENDFOREACH(Boost_Comp) IF(ENABLE_USB) LIST(APPEND UHD_LINK_LIST_STATIC "usb-1.0") ENDIF(ENABLE_USB) +# UHDConfig.cmake also needs UHD_RFNOC_FOUND +IF(ENABLE_RFNOC) + SET(UHD_RFNOC_FOUND "TRUE") +ELSE() + SET(UHD_RFNOC_FOUND "FALSE") +ENDIF(ENABLE_RFNOC) CONFIGURE_FILE( ${CMAKE_SOURCE_DIR}/cmake/Modules/UHDConfigVersion.cmake.in @@ -430,9 +490,9 @@ ELSEIF(UHDHOST_PKG) SET(PRINT_APPEND " (Debian uhd-host package configuration)") ENDIF(LIBUHD_PKG) UHD_PRINT_COMPONENT_SUMMARY() -IF(UHD_VERSION_DEVEL) +IF(UHD_VERSION_DEVEL AND NOT UHD_GIT_BRANCH STREQUAL "maint") MESSAGE(STATUS "******************************************************") - IF(UHD_VERSION_PATCH STREQUAL "git") + IF(UHD_GIT_BRANCH STREQUAL "master") MESSAGE(STATUS "* You are building the UHD development master branch.") MESSAGE(STATUS "* For production code, we recommend our stable,") MESSAGE(STATUS "* releases or using the release branch (maint).") @@ -441,8 +501,8 @@ IF(UHD_VERSION_DEVEL) MESSAGE(STATUS "* These branches are designed to provide early access") MESSAGE(STATUS "* to UHD and USRP features, but should be considered") MESSAGE(STATUS "* unstable and/or experimental!") - ENDIF(UHD_VERSION_PATCH STREQUAL "git") + ENDIF(UHD_GIT_BRANCH STREQUAL "master") MESSAGE(STATUS "******************************************************") -ENDIF(UHD_VERSION_DEVEL) +ENDIF(UHD_VERSION_DEVEL AND NOT UHD_GIT_BRANCH STREQUAL "maint") MESSAGE(STATUS "Building version: ${UHD_VERSION}${PRINT_APPEND}") MESSAGE(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") diff --git a/host/cmake/Modules/UHDBuildInfo.cmake b/host/cmake/Modules/UHDBuildInfo.cmake new file mode 100644 index 000000000..c64f748ae --- /dev/null +++ b/host/cmake/Modules/UHDBuildInfo.cmake @@ -0,0 +1,64 @@ +# +# Copyright 2015-2016 National Instruments Corp. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +# +# We need this to be macro because GET_DIRECTORY_PROPERTY works with +# the current directory. +# +MACRO(UHD_LOAD_BUILD_INFO) + MESSAGE(STATUS "") + MESSAGE(STATUS "Loading build info.") + + # Build date + IF(IGNORE_BUILD_DATE) + SET(UHD_BUILD_DATE "") + ELSE() + EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -c + "import time; print(time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime()))" + OUTPUT_VARIABLE UHD_BUILD_DATE OUTPUT_STRIP_TRAILING_WHITESPACE + ) + ENDIF(IGNORE_BUILD_DATE) + + # Compiler name + IF(MSVC) + IF(MSVC10) + SET(UHD_C_COMPILER "MSVC 2010") + SET(UHD_CXX_COMPILER "MSVC 2010") + ELSEIF(MSVC11) + SET(UHD_C_COMPILER "MSVC 2012") + SET(UHD_CXX_COMPILER "MSVC 2012") + ELSEIF(MSVC12) + SET(UHD_C_COMPILER "MSVC 2013") + SET(UHD_CXX_COMPILER "MSVC 2013") + ELSEIF(MSVC14) + SET(UHD_C_COMPILER "MSVC 2015") + SET(UHD_CXX_COMPILER "MSVC 2015") + ELSE() + # Go with the ugly string + SET(UHD_C_COMPILER "MSVC ${CMAKE_C_COMPILER_VERSION}") + SET(UHD_CXX_COMPILER "MSVC ${CMAKE_CXX_COMPILER_VERSION}") + ENDIF(MSVC10) + ELSE() + SET(UHD_C_COMPILER "${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}") + SET(UHD_CXX_COMPILER "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") + ENDIF(MSVC) + + # Compiler flags + GET_DIRECTORY_PROPERTY(uhd_flags COMPILE_DEFINITIONS) + SET(UHD_C_FLAGS "${uhd_flags}${CMAKE_C_FLAGS}") # CMAKE_C_FLAGS starts with a space + SET(UHD_CXX_FLAGS "${uhd_flags}${CMAKE_CXX_FLAGS}") # CMAKE_CXX_FLAGS starts with a space +ENDMACRO(UHD_LOAD_BUILD_INFO) diff --git a/host/cmake/Modules/UHDConfig.cmake.in b/host/cmake/Modules/UHDConfig.cmake.in index 78f01706f..e0951a93e 100644 --- a/host/cmake/Modules/UHDConfig.cmake.in +++ b/host/cmake/Modules/UHDConfig.cmake.in @@ -36,6 +36,7 @@ set(ENV{UHD_CONFIG_USED} TRUE) # set default values SET(UHD_FOUND TRUE) +SET(UHD_RFNOC_FOUND @UHD_RFNOC_FOUND@) SET(UHD_INCLUDE_HINTS) SET(UHD_LIBDIR_HINTS) SET(UHD_DIR $ENV{UHD_DIR}) diff --git a/host/cmake/Modules/UHDConfigVersion.cmake.in b/host/cmake/Modules/UHDConfigVersion.cmake.in index 67e0e408d..549798324 100644 --- a/host/cmake/Modules/UHDConfigVersion.cmake.in +++ b/host/cmake/Modules/UHDConfigVersion.cmake.in @@ -30,7 +30,8 @@ set(ENV{UHD_CONFIG_VERSION_USED} TRUE) # statically in here to avoid using Python all over again. SET(MAJOR_VERSION @TRIMMED_VERSION_MAJOR@) -SET(MINOR_VERSION @TRIMMED_VERSION_MINOR@) +SET(API_VERSION @TRIMMED_VERSION_API@) +SET(ABI_VERSION @TRIMMED_VERSION_ABI@) SET(PATCH_VERSION @TRIMMED_VERSION_PATCH@) SET(DEVEL_VERSION @UHD_VERSION_DEVEL@) @@ -53,7 +54,7 @@ ENDIF(NOT PACKAGE_FIND_VERSION) # to add a fake patch version that should be higher than anything the user # requests. IF(DEVEL_VERSION) - SET(PACKAGE_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.999") + SET(PACKAGE_VERSION "${MAJOR_VERSION}.${API_VERSION}.${ABI_VERSION}.999") ENDIF(DEVEL_VERSION) # assume incorrect versioning by default diff --git a/host/cmake/Modules/UHDGlobalDefs.cmake b/host/cmake/Modules/UHDGlobalDefs.cmake index 58c0b1287..167861402 100644 --- a/host/cmake/Modules/UHDGlobalDefs.cmake +++ b/host/cmake/Modules/UHDGlobalDefs.cmake @@ -1,5 +1,5 @@ # -# Copyright 2015 Ettus Research LLC +# Copyright 2015,2016 Ettus Research LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,11 +24,16 @@ CHECK_CXX_SYMBOL_EXISTS(log2 cmath HAVE_LOG2) ## Macros for the version number IF(UHD_VERSION_DEVEL) - MATH(EXPR UHD_VERSION_ADDED "10000 * ${TRIMMED_VERSION_MAJOR} + 100 * ${TRIMMED_VERSION_MINOR} + 99") + MATH(EXPR UHD_VERSION_ADDED "1000000 * ${TRIMMED_VERSION_MAJOR} + 10000 * ${TRIMMED_VERSION_API} + 100 * ${TRIMMED_VERSION_ABI} + 99") ELSE() - MATH(EXPR UHD_VERSION_ADDED "10000 * ${TRIMMED_VERSION_MAJOR} + 100 * ${TRIMMED_VERSION_MINOR} + ${TRIMMED_VERSION_PATCH}") + MATH(EXPR UHD_VERSION_ADDED "1000000 * ${TRIMMED_VERSION_MAJOR} + 10000 * ${TRIMMED_VERSION_API} + 100 * ${TRIMMED_VERSION_ABI} + ${TRIMMED_VERSION_PATCH}") ENDIF(UHD_VERSION_DEVEL) ADD_DEFINITIONS(-DUHD_VERSION=${UHD_VERSION_ADDED}) +## RFNoC +IF(ENABLE_RFNOC) + ADD_DEFINITIONS(-DUHD_RFNOC_ENABLED) +ENDIF(ENABLE_RFNOC) + ## make sure the code knows about config.h ADD_DEFINITIONS(-DHAVE_CONFIG_H) diff --git a/host/cmake/Modules/UHDPackage.cmake b/host/cmake/Modules/UHDPackage.cmake index b9fcdf9ed..6c36407b9 100644 --- a/host/cmake/Modules/UHDPackage.cmake +++ b/host/cmake/Modules/UHDPackage.cmake @@ -1,5 +1,5 @@ # -# Copyright 2010-2014 Ettus Research LLC +# Copyright 2010-2016 Ettus Research LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -114,7 +114,7 @@ ENDIF() # Setup CPack General ######################################################################## SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Ettus Research - USRP Hardware Driver") -SET(CPACK_PACKAGE_VENDOR "Ettus Research LLC") +SET(CPACK_PACKAGE_VENDOR "Ettus Research (National Instruments)") SET(CPACK_PACKAGE_CONTACT "Ettus Research <support@ettus.com>") SET(CPACK_PACKAGE_VERSION "${UHD_VERSION}") SET(CPACK_RESOURCE_FILE_WELCOME ${CMAKE_SOURCE_DIR}/README.md) diff --git a/host/cmake/Modules/UHDUnitTest.cmake b/host/cmake/Modules/UHDUnitTest.cmake index f3e848906..b543a4d1c 100644 --- a/host/cmake/Modules/UHDUnitTest.cmake +++ b/host/cmake/Modules/UHDUnitTest.cmake @@ -60,7 +60,7 @@ function(UHD_ADD_TEST test_name) #replace list separator with the path separator string(REPLACE ";" ":" libpath "${libpath}") - list(APPEND environs "PATH=${binpath}" "${LD_PATH_VAR}=${libpath}") + list(APPEND environs "PATH=${binpath}" "${LD_PATH_VAR}=${libpath}" "UHD_RFNOC_DIR=${CMAKE_SOURCE_DIR}/include/uhd/rfnoc") #generate a bat file that sets the environment and runs the test if (CMAKE_CROSSCOMPILING) @@ -92,7 +92,7 @@ function(UHD_ADD_TEST test_name) #replace list separator with the path separator (escaped) string(REPLACE ";" "\\;" libpath "${libpath}") - list(APPEND environs "PATH=${libpath}") + list(APPEND environs "PATH=${libpath}" "UHD_RFNOC_DIR=${CMAKE_SOURCE_DIR}/include/uhd/rfnoc") #generate a bat file that sets the environment and runs the test set(bat_file ${CMAKE_CURRENT_BINARY_DIR}/${test_name}_test.bat) diff --git a/host/cmake/Modules/UHDVersion.cmake b/host/cmake/Modules/UHDVersion.cmake index 4b26efdb2..5b1c314f0 100644 --- a/host/cmake/Modules/UHDVersion.cmake +++ b/host/cmake/Modules/UHDVersion.cmake @@ -1,5 +1,5 @@ # -# Copyright 2010-2014 Ettus Research LLC +# Copyright 2010-2014,2016 Ettus Research LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,20 +21,61 @@ FIND_PACKAGE(Git QUIET) ######################################################################## # Setup Version Numbers -# - increment major on api compatibility changes -# - increment minor on feature-level changes -# - increment patch on for bug fixes and docs +# - Increment major on large-scale library changes +# - Increment API on API changes +# - Increment ABI on ABI changes +# - Increment patch for bugfixes and docs # - set UHD_VERSION_DEVEL to true for master and development branches ######################################################################## SET(UHD_VERSION_MAJOR 003) -SET(UHD_VERSION_MINOR 009) -SET(UHD_VERSION_PATCH 004) +SET(UHD_VERSION_API 010) +SET(UHD_VERSION_ABI 000) +SET(UHD_VERSION_PATCH 000) SET(UHD_VERSION_DEVEL FALSE) ######################################################################## -# Set up trimmed version numbers for DLL resource files and packages +# If we're on a development branch, we skip the patch version ######################################################################## +IF(DEFINED UHD_VERSION_PATCH_OVERRIDE) + SET(UHD_VERSION_DEVEL FALSE) + SET(UHD_VERSION_PATCH ${UHD_VERSION_PATCH_OVERRIDE}) +ENDIF(DEFINED UHD_VERSION_PATCH_OVERRIDE) +IF(NOT DEFINED UHD_VERSION_DEVEL) + SET(UHD_VERSION_DEVEL FALSE) +ENDIF(NOT DEFINED UHD_VERSION_DEVEL) +SET(UHD_GIT_BRANCH "") +IF(GIT_FOUND) + EXECUTE_PROCESS( + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + OUTPUT_VARIABLE _git_branch OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _git_branch_result + ) + IF(_git_branch_result EQUAL 0) + SET(UHD_GIT_BRANCH ${_git_branch}) + IF(UHD_GIT_BRANCH STREQUAL "maint") + MESSAGE(STATUS "Operating on maint branch (stable).") + SET(UHD_VERSION_DEVEL FALSE) + ELSEIF(UHD_GIT_BRANCH STREQUAL "master") + MESSAGE(STATUS "Operating on master branch.") + SET(UHD_VERSION_DEVEL TRUE) + ELSE() + MESSAGE(STATUS "Working off of feature or development branch. Updating version number.") + EXECUTE_PROCESS( + COMMAND ${PYTHON_EXECUTABLE} -c "print('${_git_branch}'.replace('/', '-'))" + OUTPUT_VARIABLE _git_safe_branch OUTPUT_STRIP_TRAILING_WHITESPACE + ) + SET(UHD_VERSION_PATCH ${_git_safe_branch}) + SET(UHD_VERSION_DEVEL TRUE) + ENDIF() + ELSE() + MESSAGE(STATUS "Could not determine git branch. Probably building from tarball.") + ENDIF() +ENDIF(GIT_FOUND) +######################################################################## +# Set up trimmed version numbers for DLL resource files and packages +######################################################################## FUNCTION(DEPAD_NUM input_num output_num) EXECUTE_PROCESS( WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} @@ -45,13 +86,14 @@ FUNCTION(DEPAD_NUM input_num output_num) ENDFUNCTION(DEPAD_NUM) DEPAD_NUM(${UHD_VERSION_MAJOR} TRIMMED_VERSION_MAJOR) -DEPAD_NUM(${UHD_VERSION_MINOR} TRIMMED_VERSION_MINOR) +DEPAD_NUM(${UHD_VERSION_API} TRIMMED_VERSION_API) +DEPAD_NUM(${UHD_VERSION_ABI} TRIMMED_VERSION_ABI) IF(UHD_VERSION_DEVEL) SET(TRIMMED_VERSION_PATCH ${UHD_VERSION_PATCH}) ELSE(UHD_VERSION_DEVEL) DEPAD_NUM(${UHD_VERSION_PATCH} TRIMMED_VERSION_PATCH) ENDIF(UHD_VERSION_DEVEL) -SET(TRIMMED_UHD_VERSION "${TRIMMED_VERSION_MAJOR}.${TRIMMED_VERSION_MINOR}.${TRIMMED_VERSION_PATCH}") +SET(TRIMMED_UHD_VERSION "${TRIMMED_VERSION_MAJOR}.${TRIMMED_VERSION_API}.${TRIMMED_VERSION_ABI}.${TRIMMED_VERSION_PATCH}") ######################################################################## # Version information discovery through git log @@ -112,7 +154,7 @@ ENDIF() ######################################################################## IF(TRIM_UHD_VERSION STREQUAL "True") - SET(UHD_VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_MINOR}.${UHD_VERSION_PATCH}-${UHD_GIT_HASH}") + SET(UHD_VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_API}.${UHD_VERSION_ABI}.${UHD_VERSION_PATCH}-${UHD_GIT_HASH}") ELSE() - SET(UHD_VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_MINOR}.${UHD_VERSION_PATCH}-${UHD_GIT_COUNT}-${UHD_GIT_HASH}") + SET(UHD_VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_API}.${UHD_VERSION_ABI}.${UHD_VERSION_PATCH}-${UHD_GIT_COUNT}-${UHD_GIT_HASH}") ENDIF() diff --git a/host/cmake/debian/changelog b/host/cmake/debian/changelog index f83d834a2..48732ca38 100644 --- a/host/cmake/debian/changelog +++ b/host/cmake/debian/changelog @@ -1,3 +1,27 @@ +uhd (3.10.0.0-0ubuntu1) trusty; urgency=low + + - Changed version string to quadruplets (Major.API.ABI.Patch) + - Minimum dependencies bumped for gcc, Boost, CMake, clang and Python. + - TwinRX: Added support. Includes LO API for multi_usrp. + - N230: Added support + - Added expert framework + - X300: Completely restructured to use RFNoC + - X300: FPGA builds include git hash, dual 10GigE receive is now supported + (allows 2x200 Msps receive over 2x10GigE connections), DMA FIFO (over DRAM) + now part of builds, added Aurora support + - WBX: Fixed bug that prevented LO locking with 50 MHz ref clock + - pkg-config: Added boost_system + - Utils: uhd_usrp_probe can query sensors, query_gpsdo_sensors: minor fixes, + and cleanup + - Examples: Bugfixes in tx_waveforms, benchmark_rate measures timeouts, + - USB subsystem: Cleanups and minor bugfixes + - Added devtest infrastructure + - Converters: Added s8 and s16 data types + - Added more aggressive optimization strategies for FPGA builds + - Xilinx IP tool upgrade scripts cleaned up + + -- Ettus Research <packages@ettus.com> Thu, 11 Aug 2016 04:48:49 -0800 + uhd (3.9.4-0ubuntu1) trusty; urgency=low - GPIO control: Fix address mismatch for RX and full duplex. diff --git a/host/cmake/debian/libuhd003.install b/host/cmake/debian/libuhd003.install index 3de3b10a4..b13e00bd7 100644 --- a/host/cmake/debian/libuhd003.install +++ b/host/cmake/debian/libuhd003.install @@ -1 +1,2 @@ usr/lib/*/*.so.* +usr/share/uhd diff --git a/host/cmake/msvc/inttypes.h b/host/cmake/msvc/inttypes.h deleted file mode 100644 index 1c2baa82e..000000000 --- a/host/cmake/msvc/inttypes.h +++ /dev/null @@ -1,301 +0,0 @@ -// ISO C9x compliant inttypes.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_INTTYPES_H_ // [ -#define _MSC_INTTYPES_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#include <stdint.h> - -// 7.8 Format conversion of integer types - -typedef struct { - intmax_t quot; - intmax_t rem; -} imaxdiv_t; - -// 7.8.1 Macros for format specifiers - -// The fprintf macros for signed integers are: -#define PRId8 "d" -#define PRIi8 "i" -#define PRIdLEAST8 "d" -#define PRIiLEAST8 "i" -#define PRIdFAST8 "d" -#define PRIiFAST8 "i" - -#define PRId16 "hd" -#define PRIi16 "hi" -#define PRIdLEAST16 "hd" -#define PRIiLEAST16 "hi" -#define PRIdFAST16 "hd" -#define PRIiFAST16 "hi" - -#define PRId32 "I32d" -#define PRIi32 "I32i" -#define PRIdLEAST32 "I32d" -#define PRIiLEAST32 "I32i" -#define PRIdFAST32 "I32d" -#define PRIiFAST32 "I32i" - -#define PRId64 "I64d" -#define PRIi64 "I64i" -#define PRIdLEAST64 "I64d" -#define PRIiLEAST64 "I64i" -#define PRIdFAST64 "I64d" -#define PRIiFAST64 "I64i" - -#define PRIdMAX "I64d" -#define PRIiMAX "I64i" - -#define PRIdPTR "Id" -#define PRIiPTR "Ii" - -// The fprintf macros for unsigned integers are: -#define PRIo8 "o" -#define PRIu8 "u" -#define PRIx8 "x" -#define PRIX8 "X" -#define PRIoLEAST8 "o" -#define PRIuLEAST8 "u" -#define PRIxLEAST8 "x" -#define PRIXLEAST8 "X" -#define PRIoFAST8 "o" -#define PRIuFAST8 "u" -#define PRIxFAST8 "x" -#define PRIXFAST8 "X" - -#define PRIo16 "ho" -#define PRIu16 "hu" -#define PRIx16 "hx" -#define PRIX16 "hX" -#define PRIoLEAST16 "ho" -#define PRIuLEAST16 "hu" -#define PRIxLEAST16 "hx" -#define PRIXLEAST16 "hX" -#define PRIoFAST16 "ho" -#define PRIuFAST16 "hu" -#define PRIxFAST16 "hx" -#define PRIXFAST16 "hX" - -#define PRIo32 "I32o" -#define PRIu32 "I32u" -#define PRIx32 "I32x" -#define PRIX32 "I32X" -#define PRIoLEAST32 "I32o" -#define PRIuLEAST32 "I32u" -#define PRIxLEAST32 "I32x" -#define PRIXLEAST32 "I32X" -#define PRIoFAST32 "I32o" -#define PRIuFAST32 "I32u" -#define PRIxFAST32 "I32x" -#define PRIXFAST32 "I32X" - -#define PRIo64 "I64o" -#define PRIu64 "I64u" -#define PRIx64 "I64x" -#define PRIX64 "I64X" -#define PRIoLEAST64 "I64o" -#define PRIuLEAST64 "I64u" -#define PRIxLEAST64 "I64x" -#define PRIXLEAST64 "I64X" -#define PRIoFAST64 "I64o" -#define PRIuFAST64 "I64u" -#define PRIxFAST64 "I64x" -#define PRIXFAST64 "I64X" - -#define PRIoMAX "I64o" -#define PRIuMAX "I64u" -#define PRIxMAX "I64x" -#define PRIXMAX "I64X" - -#define PRIoPTR "Io" -#define PRIuPTR "Iu" -#define PRIxPTR "Ix" -#define PRIXPTR "IX" - -// The fscanf macros for signed integers are: -#define SCNd8 "d" -#define SCNi8 "i" -#define SCNdLEAST8 "d" -#define SCNiLEAST8 "i" -#define SCNdFAST8 "d" -#define SCNiFAST8 "i" - -#define SCNd16 "hd" -#define SCNi16 "hi" -#define SCNdLEAST16 "hd" -#define SCNiLEAST16 "hi" -#define SCNdFAST16 "hd" -#define SCNiFAST16 "hi" - -#define SCNd32 "ld" -#define SCNi32 "li" -#define SCNdLEAST32 "ld" -#define SCNiLEAST32 "li" -#define SCNdFAST32 "ld" -#define SCNiFAST32 "li" - -#define SCNd64 "I64d" -#define SCNi64 "I64i" -#define SCNdLEAST64 "I64d" -#define SCNiLEAST64 "I64i" -#define SCNdFAST64 "I64d" -#define SCNiFAST64 "I64i" - -#define SCNdMAX "I64d" -#define SCNiMAX "I64i" - -#ifdef _WIN64 // [ -# define SCNdPTR "I64d" -# define SCNiPTR "I64i" -#else // _WIN64 ][ -# define SCNdPTR "ld" -# define SCNiPTR "li" -#endif // _WIN64 ] - -// The fscanf macros for unsigned integers are: -#define SCNo8 "o" -#define SCNu8 "u" -#define SCNx8 "x" -#define SCNX8 "X" -#define SCNoLEAST8 "o" -#define SCNuLEAST8 "u" -#define SCNxLEAST8 "x" -#define SCNXLEAST8 "X" -#define SCNoFAST8 "o" -#define SCNuFAST8 "u" -#define SCNxFAST8 "x" -#define SCNXFAST8 "X" - -#define SCNo16 "ho" -#define SCNu16 "hu" -#define SCNx16 "hx" -#define SCNX16 "hX" -#define SCNoLEAST16 "ho" -#define SCNuLEAST16 "hu" -#define SCNxLEAST16 "hx" -#define SCNXLEAST16 "hX" -#define SCNoFAST16 "ho" -#define SCNuFAST16 "hu" -#define SCNxFAST16 "hx" -#define SCNXFAST16 "hX" - -#define SCNo32 "lo" -#define SCNu32 "lu" -#define SCNx32 "lx" -#define SCNX32 "lX" -#define SCNoLEAST32 "lo" -#define SCNuLEAST32 "lu" -#define SCNxLEAST32 "lx" -#define SCNXLEAST32 "lX" -#define SCNoFAST32 "lo" -#define SCNuFAST32 "lu" -#define SCNxFAST32 "lx" -#define SCNXFAST32 "lX" - -#define SCNo64 "I64o" -#define SCNu64 "I64u" -#define SCNx64 "I64x" -#define SCNX64 "I64X" -#define SCNoLEAST64 "I64o" -#define SCNuLEAST64 "I64u" -#define SCNxLEAST64 "I64x" -#define SCNXLEAST64 "I64X" -#define SCNoFAST64 "I64o" -#define SCNuFAST64 "I64u" -#define SCNxFAST64 "I64x" -#define SCNXFAST64 "I64X" - -#define SCNoMAX "I64o" -#define SCNuMAX "I64u" -#define SCNxMAX "I64x" -#define SCNXMAX "I64X" - -#ifdef _WIN64 // [ -# define SCNoPTR "I64o" -# define SCNuPTR "I64u" -# define SCNxPTR "I64x" -# define SCNXPTR "I64X" -#else // _WIN64 ][ -# define SCNoPTR "lo" -# define SCNuPTR "lu" -# define SCNxPTR "lx" -# define SCNXPTR "lX" -#endif // _WIN64 ] - -// 7.8.2 Functions for greatest-width integer types - -// 7.8.2.1 The imaxabs function -#define imaxabs _abs64 - -// 7.8.2.2 The imaxdiv function - -// This is modified version of div() function from Microsoft's div.c found -// in %MSVC.NET%\crt\src\div.c -#ifdef STATIC_IMAXDIV // [ -static -#else // STATIC_IMAXDIV ][ -_inline -#endif // STATIC_IMAXDIV ] -imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) -{ - imaxdiv_t result; - - result.quot = numer / denom; - result.rem = numer % denom; - - if (numer < 0 && result.rem > 0) { - // did division wrong; must fix up - ++result.quot; - result.rem -= denom; - } - - return result; -} - -// 7.8.2.3 The strtoimax and strtoumax functions -#define strtoimax _strtoi64 -#define strtoumax _strtoui64 - -// 7.8.2.4 The wcstoimax and wcstoumax functions -#define wcstoimax _wcstoi64 -#define wcstoumax _wcstoui64 - - -#endif // _MSC_INTTYPES_H_ ] diff --git a/host/cmake/msvc/stdint.h b/host/cmake/msvc/stdint.h deleted file mode 100644 index 15333b467..000000000 --- a/host/cmake/msvc/stdint.h +++ /dev/null @@ -1,226 +0,0 @@ -// ISO C9x compliant stdint.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_STDINT_H_ // [ -#define _MSC_STDINT_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#include <limits.h> - -// For Visual Studio 6 in C++ mode wrap <wchar.h> include with 'extern "C++" {}' -// or compiler give many errors like this: -// error C2733: second C linkage of overloaded function 'wmemchr' not allowed -#if (_MSC_VER < 1300) && defined(__cplusplus) - extern "C++" { -#endif -# include <wchar.h> -#if (_MSC_VER < 1300) && defined(__cplusplus) - } -#endif - -// 7.18.1 Integer types - -// 7.18.1.1 Exact-width integer types -typedef __int8 int8_t; -typedef __int16 int16_t; -typedef __int32 int32_t; -typedef __int64 int64_t; -typedef unsigned __int8 uint8_t; -typedef unsigned __int16 uint16_t; -typedef unsigned __int32 uint32_t; -typedef unsigned __int64 uint64_t; - -// 7.18.1.2 Minimum-width integer types -typedef int8_t int_least8_t; -typedef int16_t int_least16_t; -typedef int32_t int_least32_t; -typedef int64_t int_least64_t; -typedef uint8_t uint_least8_t; -typedef uint16_t uint_least16_t; -typedef uint32_t uint_least32_t; -typedef uint64_t uint_least64_t; - -// 7.18.1.3 Fastest minimum-width integer types -typedef int8_t int_fast8_t; -typedef int16_t int_fast16_t; -typedef int32_t int_fast32_t; -typedef int64_t int_fast64_t; -typedef uint8_t uint_fast8_t; -typedef uint16_t uint_fast16_t; -typedef uint32_t uint_fast32_t; -typedef uint64_t uint_fast64_t; - -// 7.18.1.4 Integer types capable of holding object pointers -#ifdef _WIN64 // [ - typedef __int64 intptr_t; - typedef unsigned __int64 uintptr_t; -#else // _WIN64 ][ - typedef int intptr_t; - typedef unsigned int uintptr_t; -#endif // _WIN64 ] - -// 7.18.1.5 Greatest-width integer types -typedef int64_t intmax_t; -typedef uint64_t uintmax_t; - - -// 7.18.2 Limits of specified-width integer types - -#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 - -// 7.18.2.1 Limits of exact-width integer types -#define INT8_MIN ((int8_t)_I8_MIN) -#define INT8_MAX _I8_MAX -#define INT16_MIN ((int16_t)_I16_MIN) -#define INT16_MAX _I16_MAX -#define INT32_MIN ((int32_t)_I32_MIN) -#define INT32_MAX _I32_MAX -#define INT64_MIN ((int64_t)_I64_MIN) -#define INT64_MAX _I64_MAX -#define UINT8_MAX _UI8_MAX -#define UINT16_MAX _UI16_MAX -#define UINT32_MAX _UI32_MAX -#define UINT64_MAX _UI64_MAX - -// 7.18.2.2 Limits of minimum-width integer types -#define INT_LEAST8_MIN INT8_MIN -#define INT_LEAST8_MAX INT8_MAX -#define INT_LEAST16_MIN INT16_MIN -#define INT_LEAST16_MAX INT16_MAX -#define INT_LEAST32_MIN INT32_MIN -#define INT_LEAST32_MAX INT32_MAX -#define INT_LEAST64_MIN INT64_MIN -#define INT_LEAST64_MAX INT64_MAX -#define UINT_LEAST8_MAX UINT8_MAX -#define UINT_LEAST16_MAX UINT16_MAX -#define UINT_LEAST32_MAX UINT32_MAX -#define UINT_LEAST64_MAX UINT64_MAX - -// 7.18.2.3 Limits of fastest minimum-width integer types -#define INT_FAST8_MIN INT8_MIN -#define INT_FAST8_MAX INT8_MAX -#define INT_FAST16_MIN INT16_MIN -#define INT_FAST16_MAX INT16_MAX -#define INT_FAST32_MIN INT32_MIN -#define INT_FAST32_MAX INT32_MAX -#define INT_FAST64_MIN INT64_MIN -#define INT_FAST64_MAX INT64_MAX -#define UINT_FAST8_MAX UINT8_MAX -#define UINT_FAST16_MAX UINT16_MAX -#define UINT_FAST32_MAX UINT32_MAX -#define UINT_FAST64_MAX UINT64_MAX - -// 7.18.2.4 Limits of integer types capable of holding object pointers -#ifdef _WIN64 // [ -# define INTPTR_MIN INT64_MIN -# define INTPTR_MAX INT64_MAX -# define UINTPTR_MAX UINT64_MAX -#else // _WIN64 ][ -# define INTPTR_MIN INT32_MIN -# define INTPTR_MAX INT32_MAX -# define UINTPTR_MAX UINT32_MAX -#endif // _WIN64 ] - -// 7.18.2.5 Limits of greatest-width integer types -#define INTMAX_MIN INT64_MIN -#define INTMAX_MAX INT64_MAX -#define UINTMAX_MAX UINT64_MAX - -// 7.18.3 Limits of other integer types - -#ifdef _WIN64 // [ -# define PTRDIFF_MIN _I64_MIN -# define PTRDIFF_MAX _I64_MAX -#else // _WIN64 ][ -# define PTRDIFF_MIN _I32_MIN -# define PTRDIFF_MAX _I32_MAX -#endif // _WIN64 ] - -#define SIG_ATOMIC_MIN INT_MIN -#define SIG_ATOMIC_MAX INT_MAX - -#ifndef SIZE_MAX // [ -# ifdef _WIN64 // [ -# define SIZE_MAX _UI64_MAX -# else // _WIN64 ][ -# define SIZE_MAX _UI32_MAX -# endif // _WIN64 ] -#endif // SIZE_MAX ] - -// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h> -#ifndef WCHAR_MIN // [ -# define WCHAR_MIN 0 -#endif // WCHAR_MIN ] -#ifndef WCHAR_MAX // [ -# define WCHAR_MAX _UI16_MAX -#endif // WCHAR_MAX ] - -#define WINT_MIN 0 -#define WINT_MAX _UI16_MAX - -#endif // __STDC_LIMIT_MACROS ] - - -// 7.18.4 Limits of other integer types - -#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 - -// 7.18.4.1 Macros for minimum-width integer constants - -#define INT8_C(val) val##i8 -#define INT16_C(val) val##i16 -#define INT32_C(val) val##i32 -#define INT64_C(val) val##i64 - -#define UINT8_C(val) val##ui8 -#define UINT16_C(val) val##ui16 -#define UINT32_C(val) val##ui32 -#define UINT64_C(val) val##ui64 - -// 7.18.4.2 Macros for greatest-width integer constants -#ifndef INTMAX_C -#define INTMAX_C INT64_C -#endif -#ifndef UINTMAX_C -#define UINTMAX_C UINT64_C -#endif - -#endif // __STDC_CONSTANT_MACROS ] - - -#endif // _MSC_STDINT_H_ ] diff --git a/host/docs/CMakeLists.txt b/host/docs/CMakeLists.txt index 7cb047264..e60f6a35d 100644 --- a/host/docs/CMakeLists.txt +++ b/host/docs/CMakeLists.txt @@ -45,6 +45,10 @@ IF(ENABLE_MANUAL) SET(DOXYGEN_DEP_COMPONENT "manual") SET(DOXYGEN_FPGA_MANUAL_REFERENCE "<a href=\"http://files.ettus.com/manual/md_fpga.html\">Part III: FPGA Manual</a>") SET(DOXYGEN_STRIP_EXTRA "") + SET(DOXYGEN_EXCLUDE_DIRS "") + IF(NOT ENABLE_RFNOC) + SET(DOXYGEN_EXCLUDE_DIRS "${DOXYGEN_EXCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/include/uhd/rfnoc") + ENDIF(NOT ENABLE_RFNOC) # Now, check if we have the FPGA sources as well. # If so, pull them in: IF(HAS_FPGA_SUBMODULE) @@ -63,7 +67,6 @@ ENDIF(ENABLE_MANUAL) ######################################################################## # Setup API documentation (using Doxygen) ######################################################################## -MESSAGE(STATUS "") LIBUHD_REGISTER_COMPONENT("API/Doxygen" ENABLE_DOXYGEN ON "DOXYGEN_FOUND" OFF OFF) OPTION(ENABLE_DOXYGEN_FULL "Use Doxygen to document the entire source tree (not just API)" OFF) OPTION(ENABLE_DOXYGEN_DOT "Let Doxygen use dot (requires graphviz)" OFF) @@ -92,7 +95,6 @@ ENDIF(ENABLE_DOXYGEN) ######################################################################## # Run Doxygen (on code and/or manual, depending on CMake flags) ######################################################################## -MESSAGE(STATUS "") IF(ENABLE_MANUAL_OR_DOXYGEN) #generate the doxygen configuration file SET(CMAKE_CURRENT_BINARY_DIR_DOXYGEN ${CMAKE_CURRENT_BINARY_DIR}/doxygen) @@ -134,6 +136,7 @@ SET(man_page_sources uhd_cal_rx_iq_balance.1 uhd_cal_tx_dc_offset.1 uhd_cal_tx_iq_balance.1 + uhd_config_info.1 uhd_find_devices.1 uhd_image_loader.1 uhd_images_downloader.1 @@ -146,7 +149,6 @@ SET(man_page_sources ######################################################################## # Setup man pages ######################################################################## -MESSAGE(STATUS "") FIND_PACKAGE(GZip) # No elegant way in CMake to reverse a boolean diff --git a/host/docs/Doxyfile.in b/host/docs/Doxyfile.in index 1533f7edc..556f2f4b1 100644 --- a/host/docs/Doxyfile.in +++ b/host/docs/Doxyfile.in @@ -687,7 +687,7 @@ RECURSIVE = YES # run. EXCLUDE = @CMAKE_SOURCE_DIR@/include/uhd/transport/nirio \ - @CMAKE_SOURCE_DIR@/include/uhd/transport/nirio_zero_copy.hpp + @CMAKE_SOURCE_DIR@/include/uhd/transport/nirio_zero_copy.hpp @DOXYGEN_EXCLUDE_DIRS@ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/host/docs/build.dox b/host/docs/build.dox index 1390a8b6d..95f7bab85 100644 --- a/host/docs/build.dox +++ b/host/docs/build.dox @@ -26,8 +26,8 @@ follow the auxiliary download URL for the Windows installer (below). The following compilers are known to work and officially supported: -- GCC >= 4.4 -- Clang >= 3.1 +- GCC >= 4.8 +- Clang >= 3.3 - MSVC >= 2012; the free <a href="https://www.visualstudio.com/en-us/products/visual-studio-express-vs.aspx">Visual Studio Express Edition for Desktop</a> works. Other compilers (or lower versions) may work, but are unsupported. @@ -35,14 +35,14 @@ Other compilers (or lower versions) may work, but are unsupported. ### CMake - **Purpose:** generates project build files -- **Minimum Version:** 2.6 +- **Minimum Version:** 2.8 - **Usage:** build time (required) - **Download URL:** http://www.cmake.org/cmake/resources/software.html ### Boost - **Purpose:** C++ library -- **Minimum Version:** 1.46 +- **Minimum Version:** 1.53 - **Usage:** build time + runtime (required) - **Download URL:** http://www.boost.org/users/download/ - **Download URL (Windows installer):** http://sourceforge.net/projects/boost/files/boost-binaries/ @@ -58,7 +58,7 @@ Other compilers (or lower versions) may work, but are unsupported. ### Python - **Purpose:** used by mako and utility scripts -- **Minimum Version:** 2.6 +- **Minimum Version:** 2.7 - **Usage:** build time + runtime utility scripts (required) - **Download URL:** http://www.python.org/download/ @@ -99,6 +99,10 @@ You can install all the dependencies through the package manager: sudo yum -y install boost-devel libusb1-devel python-mako doxygen python-docutils cmake make gcc gcc-c++ +or + + sudo dnf -y install boost-devel libusb1-devel python-mako doxygen python-docutils cmake make gcc gcc-c++ + Your actual command may differ. \section build_get_source Getting the source code @@ -116,21 +120,21 @@ This will populate the `fpga-src` submodule inside the repository. You can also git submodule init git submodule update -Our source code repository contains two branches: +Our source code repository contains of two main branches: \li \b master: This is the main development branch, with updated new features and bug fixes. \li \b maint: This branch has all bugfixes since the last major release, but there are no new features. This is what you should be using if you need a stable release. We might also be publishing experimental feature branches which can then be found in the same repository. -All of our releases are associated with tags in the repository. +All of our versioned releases are associated with tags in the repository. \li <a href="https://github.com/EttusResearch/UHD/tags">Source archives for release tags</a> \section build_pybombs Using PyBOMBS -PyBOMBS is a command-line tool for Linuxes (and some Unixes) from the GNU Radio ecosystem and will do a source build of UHD, including setting up prerequisites/dependencies (regardless of the distribution) with the following command: +PyBOMBS is a command-line tool for Linuxes (and some Unixes) from the GNU Radio ecosystem and will do a source build of UHD, including setting up prerequisites/dependencies (regardless of the distribution). Assuming you have PyBOMBS set up, you can install UHD with the following command: - $ ./pybombs install uhd + $ pybombs install uhd Head to the <a href="https://github.com/gnuradio/pybombs/#installation">PyBOMBS Homepage</a> for more instructions. PyBOMBS can install UHD (as well as GNU Radio or similar projects) both into system directories as well as into user's home directories, omitting the requirement for superuser access. @@ -297,7 +301,7 @@ If your application uses CMake as a build system, the following command will setup up your build environment to link against UHD: \code{.cmake} -find_package(UHD "3.8.0") +find_package(UHD "3.10.0") \endcode This will set the CMake variable `UHD_INCLUDE_DIRS` and `UHD_LIBRARIES` diff --git a/host/docs/configuration.dox b/host/docs/configuration.dox index e16d1979e..4d6a6d504 100644 --- a/host/docs/configuration.dox +++ b/host/docs/configuration.dox @@ -27,9 +27,16 @@ and possible more options. fw | Provide alternative firmware | All USB Devices, X3x0 | fw=/path/to/fw.bin ignore-cal-file | Ignores existing device calibration files | All Devices with cal-file support| See \ref ignore_cal_file master_clock_rate | Master Clock Rate in Hz | X3x0, B2x0, B1x0, E3x0, E1x0 | master_clock_rate=16e6 + dboard_clock_rate | Daughterboard clock rate in Hz | X3x0 | dboard_clock_rate=50e6 mcr | Override master clock rate settings (see \ref usrp1_hw_extclk) | USRP1 | mcr=52e6 niusrprpc_port | RPC Port for NI USRP RIO | X3x0 | niusrprpc_port=5445 system_ref_rate | Reference Clock Rate in Hz | X3x0 | system_ref_rate=10e6 + self_cal_adc_delay | Run ADC transfer delay self-calibration. | X3x0 | self_cal_adc_delay=1 + ext_adc_self_test | Run an extended ADC self test (more than the usual) | X3x0 | ext_adc_self_test=1 + recover_mb_eeprom | Disable version checks. Can damage hardware. Only recommended for recovering devices with corrupted EEPROMs. | X3x0, N230 | recover_mb_eeprom=1 + skip_dram | Ignore DRAM FIFO block. Connect Tx streamers straight into DUC or radio. | X3x0 | skip_dram=1 + skip_ddc | Ignore DDC block. Connect Rx streamers straight into radio. | X3x0 | skip_ddc=1 + skip_duc | Ignore DUC block. Connect Rx streamers or DRAM straight into radio. | X3x0 | skip_duc=1 In addition, many of the streaming-related options can be set per-device at configuration time. diff --git a/host/docs/dboards.dox b/host/docs/dboards.dox index 99314b105..2ce6c4563 100644 --- a/host/docs/dboards.dox +++ b/host/docs/dboards.dox @@ -82,6 +82,8 @@ Sensors: The DBSRX2 board has 1 quadrature frontend. It defaults to direct conversion, but can use a low IF through `lo_offset` in uhd::tune_request_t. +Frequency Range: 800 MHz to 2.3 GHz + Receive Antennas: **J3** - **Frontend 0:** Complex baseband signal from antenna J3 @@ -93,7 +95,7 @@ Receive Gains: - **GC1**, Range: 0-73dB - **BBG**, Range: 0-15dB -Bandwidth (Hz): 8 MHz -80 MHz +Bandwidth (Hz): 8 MHz-80 MHz Sensors: @@ -184,6 +186,8 @@ Features: - Can be set to use Integer-N tuning for better spur performance with uhd::tune_request_t +Frequency Range: 50 MHz to 2.2 GHz + Transmit Antennas: **TX/RX** Receive Antennas: **TX/RX** or **RX2** @@ -219,6 +223,8 @@ Features: receive frequencies - Can be set to use Integer-N tuning for better spur performance with uhd::tune_request_t +Frequency Range: 400 MHz to 4.4 GHz + Transmit Antennas: **TX/RX** Receive Antennas: **TX/RX** or **RX2** @@ -261,6 +267,8 @@ Features: receive frequencies - Can be set to use Integer-N tuning for better spur performance with uhd::tune_request_t +Frequency Range: 1.2 GHz to 6 GHz + Transmit Antennas: **TX/RX** Receive Antennas: **TX/RX** or **RX2** @@ -303,6 +311,8 @@ Features: receive frequencies - Can be set to use Integer-N tuning for better spur performance with uhd::tune_request_t +Frequency Range: 10 MHz to 6 GHz + Transmit Antennas: **TX/RX** Receive Antennas: **TX/RX** or **RX2** @@ -352,6 +362,8 @@ Bandwidth: 6 MHz The TVRX2 board has 2 real-mode frontends. It is operated at a low IF. +Frequency Range: 50 MHz to 860 MHz + Receive Frontends: - **Frontend RX1:** real-mode baseband from antenna J100 diff --git a/host/docs/transport.dox b/host/docs/transport.dox index 72d59fb2a..ab163341d 100644 --- a/host/docs/transport.dox +++ b/host/docs/transport.dox @@ -95,6 +95,18 @@ values, run the following commands: : Set the values permanently by editing `/etc/sysctl.conf`. +It is also possible to tune the network interface controller (NIC) +by using ethtool. Increasing the number of descriptors for TX or RX can +dramatically boost performance on some hosts. + +To change the number of TX/RX descriptors, run the following command: + + sudo ethtool -G <interface> tx <N> rx <N> + +One can query the maximums and current settings with the following command: + + ethtool -g <interface> + \subsection transport_udp_windows Windows specific notes <b>UDP send fast-path:</b> It is important to change the default UDP diff --git a/host/docs/uhd_config_info.1 b/host/docs/uhd_config_info.1 new file mode 100644 index 000000000..edc1b7532 --- /dev/null +++ b/host/docs/uhd_config_info.1 @@ -0,0 +1,64 @@ +.TH "uhd_find_devices" 1 "3.9.1" UHD "User Commands" +.SH NAME +uhd_config_info \- USRP Hardware Driver Build Configuration Info +.SH DESCRIPTION +Print build information corresponding to this installation of the USRP +Hardware Driver (UHD). +.LP +The UHD package is the universal hardware driver for Ettus Research +products. The goal is to provide a host driver and API for +current and future Ettus Research products. Users will be able to use +the UHD driver standalone or with 3rd party applications. + +.SH SYNOPSIS +.B uhd_config_info [OPTIONS] + +.SH OPTIONS +.IP "Print date this build was compiled:" +--boost-version +.IP "Print Boost version used:" +--build-date +.IP "Print C compiler used:" +--c-compiler +.IP "Print C++ compiler used:" +--cxx-compiler +.IP "Print C compile flags:" +--c-flags +.IP "Print C++ compile flags:" +--cxx-flags +.IP "Print UHD components included in this build (comma-delimited):" +--enabled-components +.IP "Print default install prefix for this build:" +--install-prefix +.IP "Print libusb version used" +--libusb-version +.IP "Print all information listed above:" +--print-all +.IP "Print UHD version:" +--version +.IP "This help information:" +--help + +.SH SEE ALSO +UHD documentation: +.B http://files.ettus.com/manual/ +.LP +GR-UHD documentation: +.B http://gnuradio.org/doc/doxygen/page_uhd.html + +.SH AUTHOR +This manual page was written by Nicholas Corgan +for the Debian project (but may be used by others). + +.SH COPYRIGHT +Copyright (c) 2015 National Instruments Corp. +.LP +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +.LP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. diff --git a/host/docs/usrp_b200.dox b/host/docs/usrp_b200.dox index 248f07e85..c846e916f 100644 --- a/host/docs/usrp_b200.dox +++ b/host/docs/usrp_b200.dox @@ -214,7 +214,7 @@ Below is a table showing the B200/B210 external connections and respective power <td>J101</td> <td>GPS Antenna</td> <td>GPSDO will supply nominal voltage to antenna.</td> </tr> <tr> - <td>J100</td> <td>External 10 MHz Input</td> <td>+15 dBm max</td + <td>J100</td> <td>External 10 MHz Input</td> <td>+15 dBm max</td> </tr> <tr> <td>J800</td> <td>RF B: TX/RX</td> <td>TX power +20dBm max<br> @@ -249,7 +249,7 @@ Below is a table showing the B200mini external connections and respective power <td>J2</td> <td>RX2</td> <td>RX power -15dBm max</td> </tr> <tr> - <td>J3</td> <td>External 10MHz/PPS Reference</td> <td>+15 dBm max</td + <td>J3</td> <td>External 10MHz/PPS Reference</td> <td>+15 dBm max</td> </tr> </table> diff --git a/host/docs/usrp_x3x0.dox b/host/docs/usrp_x3x0.dox index a37cc6ff4..db19ca551 100644 --- a/host/docs/usrp_x3x0.dox +++ b/host/docs/usrp_x3x0.dox @@ -91,11 +91,11 @@ number, you will have to update the FPGA image before you can start using your U with UHD (see also \ref page_images). 2. Use the `uhd_image_loader` utility to update the FPGA image. On the command line, run: - uhd_image_loader --args="type=x300,addr=192.168.10.2,fpga=HGS" + uhd_image_loader --args="type=x300,addr=192.168.10.2,fpga=HG" If you have installed the images to a non-standard location, you might need to run (change the filename according to your device): - uhd_image_loader --args="type=x300,addr=192.168.10.2" --fpga-path="<path_to_images>/usrp_x310_fpga_HGS.bit" + uhd_image_loader --args="type=x300,addr=192.168.10.2" --fpga-path="<path_to_images>/usrp_x310_fpga_HG.bit" The process of updating the FPGA image will take several minutes. Make sure the process of flashing the image does not get interrupted. @@ -131,6 +131,23 @@ Installation instructions for this interface are available on the official Intel The LEDs on the front panel can be useful in debugging hardware and software issues (see \ref x3x0_hw_fpanel) +### Dual 10 Gigabit Ethernet + +In order to utilize the X-series USRP over dual 10 Gigabit Ethernet interfaces, ensure +either the XG image is installed (see \ref x3x0_load_fpga_imgs_fpga_flavours). +In addition to burning the prerequisite FPGA image, it may also be necessary +to tune the network interface card (NIC) to eliminate drops (Ds) and reduce overflows (Os). +This is done by increasing the number of RX descriptors (see \ref transport_udp_linux). + +The benchmark_rate tool can be used to test this capability. +Run the following commands to test the X-series USRP over both 10 Gigabit +Ethernet interfaces with the maximum rate of 200 Msps per channel: + + cd <install-path>/lib/uhd/examples + ./benchmark_rate --args="type=x300,addr=<Primary IP>,second_addr=<secondary IP>" --channels="0,1" --rx_rate 200e6 + +The second interface is specified by the extra argument <b>second_addr</b>. + \subsection x3x0_hw_pcie PCI Express (Desktop) <b>Important Note: The USRP X-Series provides PCIe connectivity over MXI cable. @@ -249,8 +266,12 @@ behavior of the above interfaces. |  FPGA Image Flavor |  SFP+ Port 0 Interface |  SFP+ Port 1 Interface | |---------------------|------------------------|------------------------| -|  HGS (Default) |  1 Gigabit Ethernet |  10 Gigabit Ethernet  | -|  XGS  |  10 Gigabit Ethernet |  10 Gigabit Ethernet  | +|  HG (Default)  |  1 Gigabit Ethernet |  10 Gigabit Ethernet  | +|  XG  |  10 Gigabit Ethernet |  10 Gigabit Ethernet  | +|  HA  |  1 Gigabit Ethernet |  Aurora   | +|  XA  |  10 Gigabit Ethernet |  Aurora   | + +Note: The Aurora images need to be built manually from the FPGA source code. FPGA images are shipped in 2 formats: @@ -259,7 +280,7 @@ FPGA images are shipped in 2 formats: To get the latest images, simply use the uhd_images_downloader script. On Unix systems, use this command: - sudo uhd_images_downloader + $ [sudo] uhd_images_downloader On Windows, use: @@ -313,7 +334,7 @@ images. uhd_image_loader --args="type=x300,addr=<IP address>" Automatic FPGA path, select image type: - uhd_image_loader --args="type=x300,addr=<IP address>,fpga=<HGS or XGS>" + uhd_image_loader --args="type=x300,addr=<IP address>,fpga=<HG or XG>" Manual FPGA path: uhd_image_loader --args="type=x300,addr=<IP address>" --fpga-path="<path to FPGA image>" @@ -324,7 +345,7 @@ images. uhd_image_loader --args="type=x300,resource=<NI-RIO resource>" Automatic FPGA path, select image type: - uhd_image_loader --args="type=x300,resource=<NI-RIO resource>,fpga=<HGS or XGS>" + uhd_image_loader --args="type=x300,resource=<NI-RIO resource>,fpga=<HG or XG>" Manual FPGA path: uhd_image_loader --args="type=x300,resource=<NI-RIO resource>" --fpga-path="<path to FPGA image>" @@ -354,9 +375,9 @@ device to enable communication, as shown in the following table:  Ethernet Interface | USRP Ethernet Port |  Default USRP IP Address |  Host Static IP Address | Host Static Subnet Mask | Address EEPROM key ---------------------|-------------------------|--------------------------|-------------------------|-------------------------|------------------- -  Gigabit |  Port 0 (HGS Image) |  192.168.10.2 | 192.168.10.1 | 255.255.255.0 | `ip-addr0` -  Ten Gigabit |  Port 0 (XGS Image) |  192.168.30.2 | 192.168.30.1 | 255.255.255.0 | `ip-addr2` -  Ten Gigabit |  Port 1 (HGS/XGS Image) |  192.168.40.2 | 192.168.40.1 | 255.255.255.0 | `ip-addr3` +  Gigabit |  Port 0 (HG Image) |  192.168.10.2 | 192.168.10.1 | 255.255.255.0 | `ip-addr0` +  Ten Gigabit |  Port 0 (XG Image) |  192.168.30.2 | 192.168.30.1 | 255.255.255.0 | `ip-addr2` +  Ten Gigabit |  Port 1 (HG/XG Image) |  192.168.40.2 | 192.168.40.1 | 255.255.255.0 | `ip-addr3` As you can see, the X300/X310 actually stores different IP addresses, which all address the device differently: Each combination of Ethernet port and interface type (i.e., Gigabit or Ten Gigabit) has its own IP address. As an example, when addressing the device through 1 Gigabit Ethernet on its first port (Port 0), the relevant IP address is the one stored in the EEPROM with key `ip-addr0`, or 192.168.10.2 by default. @@ -530,6 +551,18 @@ When there is network traffic arriving at the Ethernet port, LEDs will light up. You can use this to make sure the network connection is correctly set up, e.g. by pinging the USRP and making sure the LEDs start to blink. +\section x3x0_usage_problems Usage Problems + +\subsection x3x0_corrupt_eeprom Corrupt EEPROM + +This is a rare bug in which the X-Series device's on-board EEPROM becomes corrupt and reports an incorrect +firmware and FPGA version. In this situation, UHD cannot properly use the device. To fix this bug, use +the **usrp_burn_mb_eeprom** utility as follows: + + usrp_burn_mb_eeprom --args="type=x300,recover_mb_eeprom,disable_adc_self_test" --values="revision=(NUM HERE)" + +Afterward, power-cycle your X-Series device for the changes to take effect. + \section x3x0_hw_notes Hardware Notes \subsection x3x0_hw_fpanel Front Panel diff --git a/host/docs/usrp_x3xx_fpga_burner.1 b/host/docs/usrp_x3xx_fpga_burner.1 index f07e52401..6b4e8c322 100644 --- a/host/docs/usrp_x3xx_fpga_burner.1 +++ b/host/docs/usrp_x3xx_fpga_burner.1 @@ -16,7 +16,7 @@ of that type and model, or a custom FPGA image path. --resource=\fI"Resource"\fR . IP "RPC Port:" --rpc-port=\fI"Port"\fR (default=5444) -. IP "Image Type (1G, HGS, or XGS):" +. IP "Image Type (1G, HG, or XG):" --type=\fI"Type"\fR . IP "Custom FPGA path:" --fpga-path=\fI"Path"\fR @@ -33,7 +33,7 @@ of that type and model, or a custom FPGA image path. .sp usrp_x3xx_fpga_burner --addr=192.168.10.2 --type=1G .SS Burning a Hybrid image over PCIe -usrp_x3xx_fpga_burner --resource=RIO0 --type=HGS +usrp_x3xx_fpga_burner --resource=RIO0 --type=HG .SS Burning a custom FPGA image over Ethernet usrp_x3xx_fpga_burner --addr=192.168.10.2 --fpga=path="custom_image.bit" .ft diff --git a/host/examples/benchmark_rate.cpp b/host/examples/benchmark_rate.cpp index 0f01da035..b024c3a20 100644 --- a/host/examples/benchmark_rate.cpp +++ b/host/examples/benchmark_rate.cpp @@ -47,6 +47,8 @@ unsigned long long num_rx_samps = 0; unsigned long long num_tx_samps = 0; unsigned long long num_dropped_samps = 0; unsigned long long num_seq_errors = 0; +unsigned long long num_late_commands = 0; +unsigned long long num_timeouts = 0; /*********************************************************************** * Benchmark RX Rate @@ -84,10 +86,12 @@ void benchmark_rx_rate( const float burst_pkt_time = std::max(0.100, (2 * max_samps_per_packet/rate)); float recv_timeout = burst_pkt_time + INIT_DELAY; + bool stop_called = false; while (true) { - //if (burst_timer_elapsed.load(boost::memory_order_relaxed)) { - if (burst_timer_elapsed) { + //if (burst_timer_elapsed.load(boost::memory_order_relaxed) and not stop_called) { + if (burst_timer_elapsed and not stop_called) { rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + stop_called = true; } if (random_nsamps) { cmd.num_samps = rand() % max_samps_per_packet; @@ -110,6 +114,10 @@ void benchmark_rx_rate( had_an_overflow = false; num_dropped_samps += (md.time_spec - last_time).to_ticks(rate); } + if ((burst_timer_elapsed or stop_called) and md.end_of_burst) + { + return; + } break; // ERROR_CODE_OVERFLOW can indicate overflow or sequence error @@ -121,12 +129,23 @@ void benchmark_rx_rate( num_overflows++; break; + case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: + std::cerr << "Receiver error: " << md.strerror() << ", restart streaming..."<< std::endl; + num_late_commands++; + // Radio core will be in the idle state. Issue stream command to restart streaming. + cmd.time_spec = usrp->get_time_now() + uhd::time_spec_t(0.05); + cmd.stream_now = (buffs.size() == 1); + rx_stream->issue_stream_cmd(cmd); + break; + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: - // If we stopped the streamer, then we expect this at some point - //if (burst_timer_elapsed.load(boost::memory_order_relaxed)) { if (burst_timer_elapsed) { return; } + std::cerr << "Receiver error: " << md.strerror() << ", continuing..." << std::endl; + num_timeouts++; + break; + // Otherwise, it's an error default: std::cerr << "Receiver error: " << md.strerror() << std::endl; @@ -243,6 +262,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //variables to be set by po std::string args; + std::string rx_subdev, tx_subdev; double duration; double rx_rate, tx_rate; std::string rx_otw, tx_otw; @@ -258,6 +278,8 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("help", "help message") ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") ("duration", po::value<double>(&duration)->default_value(10.0), "duration for the test in seconds") + ("rx_subdev", po::value<std::string>(&rx_subdev), "specify the device subdev for RX") + ("tx_subdev", po::value<std::string>(&tx_subdev), "specify the device subdev for TX") ("rx_rate", po::value<double>(&rx_rate), "specify to perform a RX rate test (sps)") ("tx_rate", po::value<double>(&tx_rate), "specify to perform a TX rate test (sps)") ("rx_otw", po::value<std::string>(&rx_otw)->default_value("sc16"), "specify the over-the-wire sample mode for RX") @@ -310,6 +332,15 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ } std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //always select the subdevice first, the channel mapping affects the other settings + if (vm.count("rx_subdev")) { + usrp->set_rx_subdev_spec(rx_subdev); + } + if (vm.count("tx_subdev")) { + usrp->set_tx_subdev_spec(tx_subdev); + } + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; int num_mboards = usrp->get_num_mboards(); @@ -406,7 +437,10 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //sleep for the required duration const long secs = long(duration); const long usecs = long((duration - secs)*1e6); - boost::this_thread::sleep(boost::posix_time::seconds(secs) + boost::posix_time::microseconds(usecs)); + boost::this_thread::sleep(boost::posix_time::seconds(secs) + + boost::posix_time::microseconds(usecs) + + boost::posix_time::milliseconds( (channel_nums.size() == 1) ? 0 : (INIT_DELAY * 1000)) + ); //interrupt and join the threads //burst_timer_elapsed.store(true, boost::memory_order_relaxed); @@ -422,7 +456,13 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ " Num transmitted samples: %u\n" " Num sequence errors: %u\n" " Num underflows detected: %u\n" - ) % num_rx_samps % num_dropped_samps % num_overflows % num_tx_samps % num_seq_errors % num_underflows << std::endl; + " Num late commands: %u\n" + " Num timeouts: %u\n" + ) % num_rx_samps % num_dropped_samps + % num_overflows % num_tx_samps + % num_seq_errors % num_underflows + % num_late_commands % num_timeouts + << std::endl; //finished std::cout << std::endl << "Done!" << std::endl << std::endl; diff --git a/host/examples/init_usrp/CMakeLists.txt b/host/examples/init_usrp/CMakeLists.txt index 8705b4a8d..4ce51125f 100644 --- a/host/examples/init_usrp/CMakeLists.txt +++ b/host/examples/init_usrp/CMakeLists.txt @@ -59,7 +59,7 @@ SET(CMAKE_BUILD_TYPE "Release") MESSAGE(STATUS "******************************************************************************") MESSAGE(STATUS "* NOTE: When building your own app, you probably need all kinds of different ") MESSAGE(STATUS "* compiler flags. This is just an example, so it's unlikely these settings ") -MESSAGE(STATUS "* exactly matchh what you require. Make sure to double-check compiler and ") +MESSAGE(STATUS "* exactly match what you require. Make sure to double-check compiler and ") MESSAGE(STATUS "* linker flags to make sure your specific requirements are included. ") MESSAGE(STATUS "******************************************************************************") diff --git a/host/examples/tx_bursts.cpp b/host/examples/tx_bursts.cpp index bb71d4581..5ee00d5cd 100644 --- a/host/examples/tx_bursts.cpp +++ b/host/examples/tx_bursts.cpp @@ -142,7 +142,13 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ size_t num_acc_samps = 0; //number of accumulated samples while(num_acc_samps < total_num_samps){ - size_t samps_to_send = std::min(total_num_samps - num_acc_samps, spb); + size_t samps_to_send = total_num_samps - num_acc_samps; + if (samps_to_send > spb) + { + samps_to_send = spb; + } else { + md.end_of_burst = true; + } //send a single packet size_t num_tx_samps = tx_stream->send( @@ -152,25 +158,37 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ md.has_time_spec = false; md.start_of_burst = false; - if (num_tx_samps < samps_to_send) std::cerr << "Send timeout..." << std::endl; - if(verbose) std::cout << boost::format("Sent packet: %u samples") % num_tx_samps << std::endl; + if (num_tx_samps < samps_to_send) + { + std::cerr << "Send timeout..." << std::endl; + if (stop_signal_called) + { + exit(EXIT_FAILURE); + } + } + + if(verbose) + { + std::cout << boost::format("Sent packet: %u samples") % num_tx_samps << std::endl; + } num_acc_samps += num_tx_samps; } - md.end_of_burst = true; - tx_stream->send(buffs, 0, md, timeout); - time_to_send += rep_rate; std::cout << std::endl << "Waiting for async burst ACK... " << std::flush; uhd::async_metadata_t async_md; - bool got_async_burst_ack = false; - //loop through all messages for the ACK packet (may have underflow messages in queue) - while (not got_async_burst_ack and tx_stream->recv_async_msg(async_md, seconds_in_future)){ - got_async_burst_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK); + size_t acks = 0; + //loop through all messages for the ACK packets (may have underflow messages in queue) + while (acks < channel_nums.size() and tx_stream->recv_async_msg(async_md, seconds_in_future)) + { + if (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK) + { + acks++; + } } - std::cout << (got_async_burst_ack? "success" : "fail") << std::endl; + std::cout << (acks == channel_nums.size() ? "success" : "fail") << std::endl; } while (not stop_signal_called and repeat); //finished diff --git a/host/examples/tx_samples_c.c b/host/examples/tx_samples_c.c index c052d80ed..3c3fcc8fe 100644 --- a/host/examples/tx_samples_c.c +++ b/host/examples/tx_samples_c.c @@ -21,6 +21,7 @@ #include <math.h> #include <signal.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -39,6 +40,7 @@ void print_help(void){ " -f (frequency in Hz)\n" " -r (sample rate in Hz)\n" " -g (gain)\n" + " -n (number of samples to transmit)\n" " -v (enable verbose prints)\n" " -h (print this help message)\n"); } @@ -57,12 +59,13 @@ int main(int argc, char* argv[]){ double gain = 0; char* device_args = ""; size_t channel = 0; + uint64_t total_num_samps = 0; bool verbose = false; int return_code = EXIT_SUCCESS; char error_string[512]; // Process options - while((option = getopt(argc, argv, "a:f:r:g:vh")) != -1){ + while((option = getopt(argc, argv, "a:f:r:g:n:vh")) != -1){ switch(option){ case 'a': device_args = strdup(optarg); @@ -80,6 +83,10 @@ int main(int argc, char* argv[]){ gain = atof(optarg); break; + case 'n': + total_num_samps = atoll(optarg); + break; + case 'v': verbose = true; break; @@ -198,11 +205,19 @@ int main(int argc, char* argv[]){ fprintf(stderr, "Press Ctrl+C to stop streaming...\n"); // Actual streaming - size_t num_samps_sent = 0; - while(!stop_signal_called){ + uint64_t num_acc_samps = 0; + uint64_t num_samps_sent = 0; + + while(1) { + if (stop_signal_called) break; + if (total_num_samps > 0 && num_acc_samps >= total_num_samps) break; + EXECUTE_OR_GOTO(free_tx_streamer, uhd_tx_streamer_send(tx_streamer, buffs_ptr, samps_per_buff, &md, 0.1, &num_samps_sent) ) + + num_acc_samps += num_samps_sent; + if(verbose){ fprintf(stderr, "Sent %zu samples\n", num_samps_sent); } diff --git a/host/examples/tx_waveforms.cpp b/host/examples/tx_waveforms.cpp index af8f92607..b2a8f944c 100644 --- a/host/examples/tx_waveforms.cpp +++ b/host/examples/tx_waveforms.cpp @@ -28,6 +28,7 @@ #include <boost/thread.hpp> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> +#include <stdint.h> #include <iostream> #include <csignal> @@ -47,7 +48,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //variables to be set by po std::string args, wave_type, ant, subdev, ref, pps, otw, channel_list; - size_t spb; + uint64_t total_num_samps, spb; double rate, freq, gain, wave_freq, bw; float ampl; @@ -56,7 +57,8 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ desc.add_options() ("help", "help message") ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") - ("spb", po::value<size_t>(&spb)->default_value(0), "samples per buffer, 0 for default") + ("spb", po::value<uint64_t>(&spb)->default_value(0), "samples per buffer, 0 for default") + ("nsamps", po::value<uint64_t>(&total_num_samps)->default_value(0), "total number of samples to transmit") ("rate", po::value<double>(&rate), "rate of outgoing samples") ("freq", po::value<double>(&freq), "RF center frequency in Hz") ("ampl", po::value<float>(&l)->default_value(float(0.3)), "amplitude of the waveform [0 to 0.7]") @@ -211,20 +213,22 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //Check Ref and LO Lock detect std::vector<std::string> sensor_names; - sensor_names = usrp->get_tx_sensor_names(0); + const size_t tx_sensor_chan = channel_list.empty() ? 0 : boost::lexical_cast<size_t>(channel_list[0]); + sensor_names = usrp->get_tx_sensor_names(tx_sensor_chan); if (std::find(sensor_names.begin(), sensor_names.end(), "lo_locked") != sensor_names.end()) { - uhd::sensor_value_t lo_locked = usrp->get_tx_sensor("lo_locked",0); + uhd::sensor_value_t lo_locked = usrp->get_tx_sensor("lo_locked", tx_sensor_chan); std::cout << boost::format("Checking TX: %s ...") % lo_locked.to_pp_string() << std::endl; UHD_ASSERT_THROW(lo_locked.to_bool()); } - sensor_names = usrp->get_mboard_sensor_names(0); + const size_t mboard_sensor_idx = 0; + sensor_names = usrp->get_mboard_sensor_names(mboard_sensor_idx); if ((ref == "mimo") and (std::find(sensor_names.begin(), sensor_names.end(), "mimo_locked") != sensor_names.end())) { - uhd::sensor_value_t mimo_locked = usrp->get_mboard_sensor("mimo_locked",0); + uhd::sensor_value_t mimo_locked = usrp->get_mboard_sensor("mimo_locked", mboard_sensor_idx); std::cout << boost::format("Checking TX: %s ...") % mimo_locked.to_pp_string() << std::endl; UHD_ASSERT_THROW(mimo_locked.to_bool()); } if ((ref == "external") and (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end())) { - uhd::sensor_value_t ref_locked = usrp->get_mboard_sensor("ref_locked",0); + uhd::sensor_value_t ref_locked = usrp->get_mboard_sensor("ref_locked", mboard_sensor_idx); std::cout << boost::format("Checking TX: %s ...") % ref_locked.to_pp_string() << std::endl; UHD_ASSERT_THROW(ref_locked.to_bool()); } @@ -241,14 +245,22 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ md.time_spec = usrp->get_time_now() + uhd::time_spec_t(0.1); //send data until the signal handler gets called - while(not stop_signal_called){ + //or if we accumulate the number of samples specified (unless it's 0) + uint64_t num_acc_samps = 0; + while(true){ + + if (stop_signal_called) break; + if (total_num_samps > 0 and num_acc_samps >= total_num_samps) break; + //fill the buffer with the waveform for (size_t n = 0; n < buff.size(); n++){ buff[n] = wave_table(index += step); } //send the entire contents of the buffer - tx_stream->send(buffs, buff.size(), md); + num_acc_samps += tx_stream->send( + buffs, buff.size(), md + ); md.start_of_burst = false; md.has_time_spec = false; diff --git a/host/include/config.h.in b/host/include/config.h.in index bd690299e..8931d6580 100644 --- a/host/include/config.h.in +++ b/host/include/config.h.in @@ -1,5 +1,5 @@ /* - * Copyright 2015 Ettus Research LLC + * Copyright 2015,2016 Ettus Research LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,8 @@ /* Version macros */ #cmakedefine UHD_VERSION_MAJOR ${TRIMMED_VERSION_MAJOR} -#cmakedefine UHD_VERSION_MINOR ${TRIMMED_VERSION_MINOR} +#cmakedefine UHD_VERSION_API ${TRIMMED_VERSION_API} +#cmakedefine UHD_VERSION_ABI ${TRIMMED_VERSION_ABI} #cmakedefine UHD_VERSION_PATCH ${TRIMMED_VERSION_PATCH} +#cmakedefine ENABLE_USB #cmakedefine UHD_VERSION @UHD_VERSION_ADDED@ diff --git a/host/include/uhd/CMakeLists.txt b/host/include/uhd/CMakeLists.txt index 083ec4951..e31ff80a0 100644 --- a/host/include/uhd/CMakeLists.txt +++ b/host/include/uhd/CMakeLists.txt @@ -15,6 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # +ADD_SUBDIRECTORY(rfnoc) ADD_SUBDIRECTORY(transport) ADD_SUBDIRECTORY(types) ADD_SUBDIRECTORY(usrp) @@ -27,6 +28,7 @@ CONFIGURE_FILE( ) UHD_INSTALL(FILES + build_info.hpp config.hpp convert.hpp deprecated.hpp @@ -41,6 +43,14 @@ UHD_INSTALL(FILES COMPONENT headers ) +IF(ENABLE_RFNOC) + UHD_INSTALL(FILES + device3.hpp + DESTINATION ${INCLUDE_DIR}/uhd + COMPONENT headers + ) +ENDIF(ENABLE_RFNOC) + IF(ENABLE_C_API) UHD_INSTALL(FILES config.h diff --git a/host/include/uhd/build_info.hpp b/host/include/uhd/build_info.hpp new file mode 100644 index 000000000..2e6571ad0 --- /dev/null +++ b/host/include/uhd/build_info.hpp @@ -0,0 +1,55 @@ +// +// Copyright 2015 National Instruments Corp. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_BUILD_INFO_HPP +#define INCLUDED_UHD_BUILD_INFO_HPP + +#include <uhd/config.hpp> + +#include <string> + +namespace uhd { namespace build_info { + + //! Return the version of Boost this build was built with. + UHD_API const std::string boost_version(); + + //! Return the date and time (GMT) this UHD build was built. + UHD_API const std::string build_date(); + + //! Return the C compiler used for this build. + UHD_API const std::string c_compiler(); + + //! Return the C++ compiler used for this build. + UHD_API const std::string cxx_compiler(); + + //! Return the C flags passed into this build. + UHD_API const std::string c_flags(); + + //! Return the C++ flags passed into this build. + UHD_API const std::string cxx_flags(); + + //! Return the UHD components enabled for this build, comma-delimited. + UHD_API const std::string enabled_components(); + + //! Return the default CMake install prefix for this build. + UHD_API const std::string install_prefix(); + + //! Return the version of libusb this build was built with. + UHD_API const std::string libusb_version(); +}} + +#endif /* INCLUDED_UHD_BUILD_INFO_HPP */ diff --git a/host/include/uhd/config.h b/host/include/uhd/config.h index 1677c80ec..a22dea424 100644 --- a/host/include/uhd/config.h +++ b/host/include/uhd/config.h @@ -43,7 +43,14 @@ typedef ptrdiff_t ssize_t; #define UHD_DEPRECATED __declspec(deprecated) #define UHD_ALIGNED(x) __declspec(align(x)) #define UHD_UNUSED(x) x __attribute__((unused)) -#elif defined(__GNUG__) && __GNUG__ >= 4 +#elif defined(__GNUC__) && __GNUC__ >= 4 + #define UHD_EXPORT __attribute__((visibility("default"))) + #define UHD_IMPORT __attribute__((visibility("default"))) + #define UHD_INLINE inline __attribute__((always_inline)) + #define UHD_DEPRECATED __attribute__((deprecated)) + #define UHD_ALIGNED(x) __attribute__((aligned(x))) + #define UHD_UNUSED(x) x __attribute__((unused)) +#elif defined(__clang__) #define UHD_EXPORT __attribute__((visibility("default"))) #define UHD_IMPORT __attribute__((visibility("default"))) #define UHD_INLINE inline __attribute__((always_inline)) diff --git a/host/include/uhd/config.hpp b/host/include/uhd/config.hpp index 00466501e..cef7d3bb4 100644 --- a/host/include/uhd/config.hpp +++ b/host/include/uhd/config.hpp @@ -36,7 +36,7 @@ # pragma warning(disable: 4275) // non dll-interface class ... used as base for dll-interface class ... //# pragma warning(disable: 4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data //# pragma warning(disable: 4511) // 'class' : copy constructor could not be generated -//# pragma warning(disable: 4250) // 'class' : inherits 'method' via dominance +# pragma warning(disable: 4250) // 'class' : inherits 'method' via dominance # pragma warning(disable: 4200) // nonstandard extension used : zero-sized array in struct/union // define logical operators @@ -53,6 +53,7 @@ typedef ptrdiff_t ssize_t; #define UHD_EXPORT __declspec(dllexport) #define UHD_IMPORT __declspec(dllimport) #define UHD_INLINE __forceinline + #define UHD_FORCE_INLINE __forceinline #define UHD_DEPRECATED __declspec(deprecated) #define UHD_ALIGNED(x) __declspec(align(x)) #define UHD_UNUSED(x) x @@ -60,6 +61,7 @@ typedef ptrdiff_t ssize_t; #define UHD_EXPORT __declspec(dllexport) #define UHD_IMPORT __declspec(dllimport) #define UHD_INLINE inline + #define UHD_FORCE_INLINE inline #define UHD_DEPRECATED __declspec(deprecated) #define UHD_ALIGNED(x) __declspec(align(x)) #define UHD_UNUSED(x) x __attribute__((unused)) @@ -67,6 +69,15 @@ typedef ptrdiff_t ssize_t; #define UHD_EXPORT __attribute__((visibility("default"))) #define UHD_IMPORT __attribute__((visibility("default"))) #define UHD_INLINE inline __attribute__((always_inline)) + #define UHD_FORCE_INLINE inline __attribute__((always_inline)) + #define UHD_DEPRECATED __attribute__((deprecated)) + #define UHD_ALIGNED(x) __attribute__((aligned(x))) + #define UHD_UNUSED(x) x __attribute__((unused)) +#elif defined(__clang__) + #define UHD_EXPORT __attribute__((visibility("default"))) + #define UHD_IMPORT __attribute__((visibility("default"))) + #define UHD_INLINE inline __attribute__((always_inline)) + #define UHD_FORCE_INLINE inline __attribute__((always_inline)) #define UHD_DEPRECATED __attribute__((deprecated)) #define UHD_ALIGNED(x) __attribute__((aligned(x))) #define UHD_UNUSED(x) x __attribute__((unused)) @@ -74,6 +85,7 @@ typedef ptrdiff_t ssize_t; #define UHD_EXPORT #define UHD_IMPORT #define UHD_INLINE inline + #define UHD_FORCE_INLINE inline #define UHD_DEPRECATED #define UHD_ALIGNED(x) #define UHD_UNUSED(x) x @@ -85,6 +97,12 @@ typedef ptrdiff_t ssize_t; #else #define UHD_API UHD_IMPORT #endif // UHD_DLL_EXPORTS +#ifdef UHD_RFNOC_ENABLED + #define UHD_RFNOC_API UHD_API +#else + #define UHD_RFNOC_API +#endif // UHD_RFNOC_ENABLED + // Platform defines for conditional parts of headers: // Taken from boost/config/select_platform_config.hpp, diff --git a/host/include/uhd/device3.hpp b/host/include/uhd/device3.hpp new file mode 100644 index 000000000..da23bb263 --- /dev/null +++ b/host/include/uhd/device3.hpp @@ -0,0 +1,146 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_DEVICE3_HPP +#define INCLUDED_UHD_DEVICE3_HPP + +#include <uhd/device.hpp> +#include <uhd/rfnoc/graph.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/units/detail/utility.hpp> +#include <vector> + +namespace uhd { + +/*! + * \brief Extends uhd::device for third-generation USRP devices. + * + * Generation-3 devices are characterized by the following traits: + * - They support RFNoC (RF Network-on-Chip). + * - Data transport uses the compressed VITA (CVITA/CHDR) data format. + */ +class UHD_API device3 : public uhd::device { + + public: + typedef boost::shared_ptr<device3> sptr; + + //! Same as uhd::device::make(), but will fail if not actually a device3 + static sptr make(const device_addr_t &hint, const size_t which = 0); + + virtual rfnoc::graph::sptr create_graph(const std::string &name="") = 0; + + /*! Reset blocks after a stream. + * + * TODO write docs + */ + void clear(); + + /*! \brief Checks if an RFNoC block exists on the device. + * + * \param block_id Canonical block name (e.g. "0/FFT_1"). + * \return true if a block with the specified id exists + */ + bool has_block(const rfnoc::block_id_t &block_id) const; + + /*! Same as has_block(), but with a type check. + * + * \return true if a block of type T with the specified id exists + */ + template <typename T> + bool has_block(const rfnoc::block_id_t &block_id) const + { + if (has_block(block_id)) { + return bool(boost::dynamic_pointer_cast<T>(get_block_ctrl(block_id))); + } else { + return false; + } + } + + /*! \brief Returns a block controller class for an RFNoC block. + * + * If the given block ID is not valid (i.e. such a block does not exist + * on this device), it will throw a uhd::lookup_error. + * + * \param block_id Canonical block name (e.g. "0/FFT_1"). + */ + rfnoc::block_ctrl_base::sptr get_block_ctrl(const rfnoc::block_id_t &block_id) const; + + /*! Same as get_block_ctrl(), but with a type cast. + * + * If you have a block controller class that is derived from block_ctrl_base, + * use this function to access its specific methods. + * If the given block ID is not valid (i.e. such a block does not exist + * on this device) or if the type does not match, it will throw a uhd::lookup_error. + * + * \code{.cpp} + * // Assume DEV is a device3::sptr + * uhd::rfnoc::my_block_ctrl::sptr block_controller = get_block_ctrl<my_block_ctrl>("0/MyBlock_0"); + * block_controller->my_own_block_method(); + * \endcode + */ + template <typename T> + boost::shared_ptr<T> get_block_ctrl(const rfnoc::block_id_t &block_id) const + { + boost::shared_ptr<T> blk = boost::dynamic_pointer_cast<T>(get_block_ctrl(block_id)); + if (blk) { + return blk; + } else { + throw uhd::lookup_error(str(boost::format("This device does not have a block of type %s with ID: %s") + % boost::units::detail::demangle(typeid(T).name()) + % block_id.to_string())); + } + } + + /*! Returns the block ids of all blocks that match the specified hint + * Uses block_ctrl_base::match() internally. + * If no matching block is found, it returns an empty vector. + * + * To access specialized block controller classes (i.e. derived from block_ctrl_base), + * use the templated version of this function, e.g. + * \code{.cpp} + * // Assume DEV is a device3::sptr + * null_block_ctrl::sptr null_block = DEV->find_blocks<null_block_ctrl>("NullSrcSink"); + * \endcode + */ + std::vector<rfnoc::block_id_t> find_blocks(const std::string &block_id_hint) const; + + /*! Type-cast version of find_blocks(). + */ + template <typename T> + std::vector<rfnoc::block_id_t> find_blocks(const std::string &block_id_hint) const + { + std::vector<rfnoc::block_id_t> all_block_ids = find_blocks(block_id_hint); + std::vector<rfnoc::block_id_t> filt_block_ids; + for (size_t i = 0; i < all_block_ids.size(); i++) { + if (has_block<T>(all_block_ids[i])) { + filt_block_ids.push_back(all_block_ids[i]); + } + } + return filt_block_ids; + } + + protected: + //! List of *all* RFNoC blocks available on this device. + // It is the responsibility of the deriving class to make + // sure this gets correctly populated. + std::vector< rfnoc::block_ctrl_base::sptr > _rfnoc_block_ctrl; +}; + +} //namespace uhd + +#endif /* INCLUDED_UHD_DEVICE3_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/error.h b/host/include/uhd/error.h index f0ac41d1f..77216dc72 100644 --- a/host/include/uhd/error.h +++ b/host/include/uhd/error.h @@ -158,7 +158,7 @@ extern "C" { * strings into a buffer that can be queried with this function. Functions that * do take in UHD structs/handles will place their error strings in both locations. */ -uhd_error uhd_get_last_error( +UHD_API uhd_error uhd_get_last_error( char* error_out, size_t strbuffer_len ); diff --git a/host/include/uhd/exception.hpp b/host/include/uhd/exception.hpp index 7eddf5f26..98982b01f 100644 --- a/host/include/uhd/exception.hpp +++ b/host/include/uhd/exception.hpp @@ -141,6 +141,13 @@ namespace uhd{ virtual void dynamic_throw(void) const; }; + struct UHD_API syntax_error : exception{ + syntax_error(const std::string &what); + virtual unsigned code(void) const; + virtual syntax_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + /*! * Create a formatted string with throw-site information. * Fills in the function name, file name, and line number. diff --git a/host/include/uhd/property_tree.hpp b/host/include/uhd/property_tree.hpp index a92654ba2..93353568a 100644 --- a/host/include/uhd/property_tree.hpp +++ b/host/include/uhd/property_tree.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011,2014 Ettus Research LLC +// Copyright 2011,2014-2016 Ettus Research // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,8 +27,51 @@ namespace uhd{ /*! - * A templated property interface for holding a value + * A templated property interface for holding the state + * associated with a property in a uhd::property_tree * and registering callbacks when that value changes. + * + * A property is defined to have two separate vales: + * - Desired value: Value requested by the user + * - Coerced value: Value that was actually possible + * given HW and other requirements + * + * By default, the desired and coerced values are + * identical as long as the property is not coerced. + * A property can be coerced in two way: + * 1. Using a coercer: A callback function that takes + * in a desired value and produces a coerced value. + * A property must have *exactly one* coercer. + * 2. Manual coercion: Manually calling the set_coerced + * API fnction to coerce the value of the propery. In + * order to use manual coercion, the propery must be + * created with the MANUAL_COERCE mode. + * If the coerce mode for a property is AUTO_COERCE then + * it always has a coercer. If the set_coercer API is + * never used, then the default coercer is used which + * simply set the coerced value to the desired value. + * + * It is possible to get notified every time the desired + * or coerced values of a property potentially change + * using subscriber callbacks. Every property can have + * zero or more desired and coerced subscribers. + * + * If storing the property readback state in software is + * not appropriate (for example if it needs to be queried + * from hardware) then it is possible to use a publisher + * callback to get the value of the property. Calling + * get on the property will always call the publisher and + * the cached desired and coerced values are updated only + * using set* calls. A preprty must have *at most one* + * publisher. It is legal to have both a coercer + * and publisher for a property but the only way to access + * the desired and coerced values in that case would be by + * notification using the desired and coerced subscribers. + * Publishers are useful for creating read-only properties. + * + * Requirements for the template type T: + * - T must have a copy constructor + * - T must have an assignment operator */ template <typename T> class property : boost::noncopyable{ public: @@ -40,60 +83,107 @@ public: /*! * Register a coercer into the property. - * A coercer is a special subscriber that coerces the value. + * A coercer is a callback function that updates the + * coerced value of a property. + * * Only one coercer may be registered per property. - * Registering a coercer replaces the previous coercer. * \param coercer the coercer callback function * \return a reference to this property for chaining + * \throws uhd::assertion_error if called more than once */ - virtual property<T> &coerce(const coercer_type &coercer) = 0; + virtual property<T> &set_coercer(const coercer_type &coercer) = 0; /*! * Register a publisher into the property. - * A publisher is a special callback the provides the value. - * Publishers are useful for creating read-only properties. + * A publisher is a callback function the provides the value + * for a property. + * * Only one publisher may be registered per property. - * Registering a publisher replaces the previous publisher. * \param publisher the publisher callback function * \return a reference to this property for chaining + * \throws uhd::assertion_error if called more than once */ - virtual property<T> &publish(const publisher_type &publisher) = 0; + virtual property<T> &set_publisher(const publisher_type &publisher) = 0; /*! * Register a subscriber into the property. - * All subscribers are called when the value changes. + * All desired subscribers are called when the desired value + * potentially changes. + * * Once a subscriber is registered, it cannot be unregistered. * \param subscriber the subscriber callback function * \return a reference to this property for chaining */ - virtual property<T> &subscribe(const subscriber_type &subscriber) = 0; + virtual property<T> &add_desired_subscriber(const subscriber_type &subscriber) = 0; + + /*! + * Register a subscriber into the property. + * All coerced subscribers are called when the coerced value + * potentially changes. + * + * Once a subscriber is registered, it cannot be unregistered. + * \param subscriber the subscriber callback function + * \return a reference to this property for chaining + */ + virtual property<T> &add_coerced_subscriber(const subscriber_type &subscriber) = 0; /*! * Update calls all subscribers w/ the current value. + * * \return a reference to this property for chaining + * \throws uhd::assertion_error */ virtual property<T> &update(void) = 0; /*! - * Set the new value and call all subscribers. - * The coercer (when provided) is called initially, - * and the coerced value is used to set the subscribers. + * Set the new value and call all the necessary subscribers. + * Order of operations: + * - The desired value of the property is updated + * - All desired subscribers are called + * - If coerce mode is AUTO then the coercer is called + * - If coerce mode is AUTO then all coerced subscribers are called + * * \param value the new value to set on this property * \return a reference to this property for chaining + * \throws uhd::assertion_error */ virtual property<T> &set(const T &value) = 0; /*! + * Set a coerced value and call all subscribers. + * The coercer is bypassed, and the specified value is + * used as the coerced value. All coerced subscribers + * are called. This function can only be used when the + * coerce mode is set to MANUAL_COERCE. + * + * \param value the new value to set on this property + * \return a reference to this property for chaining + * \throws uhd::assertion_error + */ + virtual property<T> &set_coerced(const T &value) = 0; + + /*! * Get the current value of this property. * The publisher (when provided) yields the value, - * otherwise an internal shadow is used for the value. + * otherwise an internal coerced value is returned. + * * \return the current value in the property + * \throws uhd::assertion_error */ - virtual T get(void) const = 0; + virtual const T get(void) const = 0; + + /*! + * Get the current desired value of this property. + * + * \return the current desired value in the property + * \throws uhd::assertion_error + */ + virtual const T get_desired(void) const = 0; /*! * A property is empty if it has never been set. * A property with a publisher is never empty. + * * \return true if the property is empty */ virtual bool empty(void) const = 0; @@ -129,6 +219,8 @@ class UHD_API property_tree : boost::noncopyable{ public: typedef boost::shared_ptr<property_tree> sptr; + enum coerce_mode_t { AUTO_COERCE, MANUAL_COERCE }; + virtual ~property_tree(void) = 0; //! Create a new + empty property tree @@ -147,7 +239,9 @@ public: virtual std::vector<std::string> list(const fs_path &path) const = 0; //! Create a new property entry in the tree - template <typename T> property<T> &create(const fs_path &path); + template <typename T> property<T> &create( + const fs_path &path, + coerce_mode_t coerce_mode = AUTO_COERCE); //! Get access to a property in the tree template <typename T> property<T> &access(const fs_path &path); diff --git a/host/include/uhd/property_tree.ipp b/host/include/uhd/property_tree.ipp index 93962c963..6ed1056e6 100644 --- a/host/include/uhd/property_tree.ipp +++ b/host/include/uhd/property_tree.ipp @@ -1,5 +1,5 @@ // -// Copyright 2011,2014 Ettus Research LLC +// Copyright 2011,2014-2016 Ettus Research // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,6 +20,7 @@ #include <uhd/exception.hpp> #include <boost/foreach.hpp> +#include <boost/scoped_ptr.hpp> #include <vector> /*********************************************************************** @@ -29,23 +30,38 @@ namespace uhd{ namespace /*anon*/{ template <typename T> class property_impl : public property<T>{ public: + property_impl<T>(property_tree::coerce_mode_t mode) : _coerce_mode(mode){ + if (_coerce_mode == property_tree::AUTO_COERCE) { + _coercer = DEFAULT_COERCER; + } + } ~property_impl<T>(void){ /* NOP */ } - property<T> &coerce(const typename property<T>::coercer_type &coercer){ + property<T> &set_coercer(const typename property<T>::coercer_type &coercer){ + if (not _coercer.empty()) uhd::assertion_error("cannot register more than one coercer for a property"); + if (_coerce_mode == property_tree::MANUAL_COERCE) uhd::assertion_error("cannot register coercer for a manually coerced property"); + _coercer = coercer; return *this; } - property<T> &publish(const typename property<T>::publisher_type &publisher){ + property<T> &set_publisher(const typename property<T>::publisher_type &publisher){ + if (not _publisher.empty()) uhd::assertion_error("cannot register more than one publisher for a property"); + _publisher = publisher; return *this; } - property<T> &subscribe(const typename property<T>::subscriber_type &subscriber){ - _subscribers.push_back(subscriber); + property<T> &add_desired_subscriber(const typename property<T>::subscriber_type &subscriber){ + _desired_subscribers.push_back(subscriber); + return *this; + } + + property<T> &add_coerced_subscriber(const typename property<T>::subscriber_type &subscriber){ + _coerced_subscribers.push_back(subscriber); return *this; } @@ -54,17 +70,49 @@ public: return *this; } + void _set_coerced(const T &value){ + init_or_set_value(_coerced_value, value); + BOOST_FOREACH(typename property<T>::subscriber_type &csub, _coerced_subscribers){ + csub(get_value_ref(_coerced_value)); //let errors propagate + } + } + property<T> &set(const T &value){ - _value = boost::shared_ptr<T>(new T(_coercer.empty()? value : _coercer(value))); - BOOST_FOREACH(typename property<T>::subscriber_type &subscriber, _subscribers){ - subscriber(*_value); //let errors propagate + init_or_set_value(_value, value); + BOOST_FOREACH(typename property<T>::subscriber_type &dsub, _desired_subscribers){ + dsub(get_value_ref(_value)); //let errors propagate } + if (not _coercer.empty()) { + _set_coerced(_coercer(get_value_ref(_value))); + } else { + if (_coerce_mode == property_tree::AUTO_COERCE) uhd::assertion_error("coercer missing for an auto coerced property"); + } + return *this; + } + + property<T> &set_coerced(const T &value){ + if (_coerce_mode == property_tree::AUTO_COERCE) uhd::assertion_error("cannot set coerced value an auto coerced property"); + _set_coerced(value); return *this; } - T get(void) const{ - if (empty()) throw uhd::runtime_error("Cannot get() on an empty property"); - return _publisher.empty()? *_value : _publisher(); + const T get(void) const{ + if (empty()) { + throw uhd::runtime_error("Cannot get() on an uninitialized (empty) property"); + } + if (not _publisher.empty()) { + return _publisher(); + } else { + if (_coerced_value.get() == NULL and _coerce_mode == property_tree::MANUAL_COERCE) + throw uhd::runtime_error("uninitialized coerced value for manually coerced attribute"); + return get_value_ref(_coerced_value); + } + } + + const T get_desired(void) const{ + if (_value.get() == NULL) throw uhd::runtime_error("Cannot get_desired() on an uninitialized (empty) property"); + + return get_value_ref(_value); } bool empty(void) const{ @@ -72,10 +120,30 @@ public: } private: - std::vector<typename property<T>::subscriber_type> _subscribers; - typename property<T>::publisher_type _publisher; - typename property<T>::coercer_type _coercer; - boost::shared_ptr<T> _value; + static T DEFAULT_COERCER(const T& value) { + return value; + } + + static void init_or_set_value(boost::scoped_ptr<T>& scoped_value, const T& init_val) { + if (scoped_value.get() == NULL) { + scoped_value.reset(new T(init_val)); + } else { + *scoped_value = init_val; + } + } + + static const T& get_value_ref(const boost::scoped_ptr<T>& scoped_value) { + if (scoped_value.get() == NULL) throw uhd::assertion_error("Cannot use uninitialized property data"); + return *scoped_value.get(); + } + + const property_tree::coerce_mode_t _coerce_mode; + std::vector<typename property<T>::subscriber_type> _desired_subscribers; + std::vector<typename property<T>::subscriber_type> _coerced_subscribers; + typename property<T>::publisher_type _publisher; + typename property<T>::coercer_type _coercer; + boost::scoped_ptr<T> _value; + boost::scoped_ptr<T> _coerced_value; }; }} //namespace uhd::/*anon*/ @@ -85,8 +153,8 @@ private: **********************************************************************/ namespace uhd{ - template <typename T> property<T> &property_tree::create(const fs_path &path){ - this->_create(path, typename boost::shared_ptr<property<T> >(new property_impl<T>())); + template <typename T> property<T> &property_tree::create(const fs_path &path, coerce_mode_t coerce_mode){ + this->_create(path, typename boost::shared_ptr<property<T> >(new property_impl<T>(coerce_mode))); return this->access<T>(path); } diff --git a/host/include/uhd/rfnoc/CMakeLists.txt b/host/include/uhd/rfnoc/CMakeLists.txt new file mode 100644 index 000000000..15a92ae8f --- /dev/null +++ b/host/include/uhd/rfnoc/CMakeLists.txt @@ -0,0 +1,49 @@ +# +# Copyright 2014-2016 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +IF(ENABLE_RFNOC) + UHD_INSTALL(FILES + # Infrastructure + block_ctrl_base.hpp + block_ctrl.hpp + blockdef.hpp + block_id.hpp + constants.hpp + graph.hpp + node_ctrl_base.hpp + node_ctrl_base.ipp + rate_node_ctrl.hpp + scalar_node_ctrl.hpp + sink_block_ctrl_base.hpp + sink_node_ctrl.hpp + source_block_ctrl_base.hpp + source_node_ctrl.hpp + stream_sig.hpp + terminator_node_ctrl.hpp + tick_node_ctrl.hpp + # Block controllers + ddc_block_ctrl.hpp + duc_block_ctrl.hpp + radio_ctrl.hpp + DESTINATION ${INCLUDE_DIR}/uhd/rfnoc + COMPONENT headers + ) +ENDIF(ENABLE_RFNOC) + +ADD_SUBDIRECTORY(blocks) +#ADD_SUBDIRECTORY(components) + diff --git a/host/include/uhd/rfnoc/block_ctrl.hpp b/host/include/uhd/rfnoc/block_ctrl.hpp new file mode 100644 index 000000000..b32192d86 --- /dev/null +++ b/host/include/uhd/rfnoc/block_ctrl.hpp @@ -0,0 +1,47 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_BLOCK_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_BLOCK_CTRL_HPP + +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief This is the default implementation of a block_ctrl_base. + * + * For most blocks, this will be a sufficient implementation. All registers + * can be set by sr_write(). The default behaviour of functions is documented + * in uhd::rfnoc::block_ctrl_base. + */ +class UHD_RFNOC_API block_ctrl : public source_block_ctrl_base, public sink_block_ctrl_base +{ +public: + // Required macro in RFNoC block classes + UHD_RFNOC_BLOCK_OBJECT(block_ctrl) + + // Nothing else here -- all function definitions are in block_ctrl_base, + // source_block_ctrl_base and sink_block_ctrl_base + +}; /* class block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_BLOCK_CTRL_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/block_ctrl_base.hpp b/host/include/uhd/rfnoc/block_ctrl_base.hpp new file mode 100644 index 000000000..fa3ceadc5 --- /dev/null +++ b/host/include/uhd/rfnoc/block_ctrl_base.hpp @@ -0,0 +1,431 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_BLOCK_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_BLOCK_CTRL_BASE_HPP + +#include <uhd/property_tree.hpp> +#include <uhd/stream.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/block_id.hpp> +#include <uhd/rfnoc/stream_sig.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> +#include <stdint.h> + +namespace uhd { + namespace rfnoc { + namespace nocscript { + // Forward declaration + class block_iface; + } + + +// TODO: Move this out of public section +struct make_args_t +{ + make_args_t(const std::string &key="") : + device_index(0), + is_big_endian(true), + block_name(""), + block_key(key) + {} + + //! A valid interface that allows us to do peeks and pokes + std::map<size_t, uhd::wb_iface::sptr> ctrl_ifaces; + //! This block's base address (address of block port 0) + uint32_t base_address; + //! The device index (or motherboard index). + size_t device_index; + //! A property tree for this motherboard. Example: If the root a device's + // property tree is /mboards/0, pass a subtree starting at /mboards/0 + // to the constructor. + uhd::property_tree::sptr tree; + bool is_big_endian; + //! The name of the block as it will be addressed + std::string block_name; + //! The key of the block, i.e. how it was registered + std::string block_key; +}; + +//! This macro must be put in the public section of an RFNoC +// block class +#define UHD_RFNOC_BLOCK_OBJECT(class_name) \ + typedef boost::shared_ptr< class_name > sptr; + +//! Shorthand for block constructor +#define UHD_RFNOC_BLOCK_CONSTRUCTOR(CLASS_NAME) \ + CLASS_NAME##_impl( \ + const make_args_t &make_args \ + ) : block_ctrl_base(make_args) + +//! This macro must be placed inside a block implementation file +// after the class definition +#define UHD_RFNOC_BLOCK_REGISTER(CLASS_NAME, BLOCK_NAME) \ + block_ctrl_base::sptr CLASS_NAME##_make( \ + const make_args_t &make_args \ + ) { \ + return block_ctrl_base::sptr(new CLASS_NAME##_impl(make_args)); \ + } \ + UHD_STATIC_BLOCK(register_rfnoc_##CLASS_NAME) \ + { \ + uhd::rfnoc::block_ctrl_base::register_block(&CLASS_NAME##_make, BLOCK_NAME); \ + } + +/*! \brief Base class for all RFNoC block controller objects. + * + * For RFNoC, block controller objects must be derived from + * uhd::rfnoc::block_ctrl_base. This class provides all functions + * that a block *must* provide. Typically, you would not derive + * a block controller class directly from block_ctrl_base, but + * from a class such as uhd::usrp::rfnoc::source_block_ctrl_base or + * uhd::usrp::rfnoc::sink_block_ctrl_base which extends its functionality. + */ +class UHD_RFNOC_API block_ctrl_base; +class block_ctrl_base : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<block_ctrl_base> sptr; + typedef boost::function<sptr(const make_args_t &)> make_t; + + /*********************************************************************** + * Factory functions + **********************************************************************/ + + /*! Register a block controller class into the discovery and factory system. + * + * Note: It is not recommended to call this function directly. + * Rather, use the UHD_RFNOC_BLOCK_REGISTER() macro, which will set up + * the discovery and factory system correctly. + * + * \param make A factory function that makes a block controller object + * \param name A unique block name, e.g. 'FFT'. If a block has this block name, + * it will use \p make to generate the block controller class. + */ + static void register_block(const make_t &make, const std::string &name); + + /*! + * \brief Create a block controller class given a NoC-ID or a block name. + * + * If a block name is given in \p make_args, it will directly try to + * generate a block of this type. If no block name is given, it will + * look up a name using the NoC-ID and use that. + * If it can't find a suitable block controller class, it will generate + * a uhd::rfnoc::block_ctrl. However, if a block name *is* specified, + * it will throw a uhd::runtime_error if this block type is not registered. + * + * \param make_args Valid make args. + * \param noc_id The 64-Bit NoC-ID. + * \return a shared pointer to a new device instance + */ + static sptr make(const make_args_t &make_args, boost::uint64_t noc_id = ~0); + + /*********************************************************************** + * Block Communication and Control + * + * These functions do not require communication with the FPGA. + **********************************************************************/ + + /*! Returns the 16-Bit address for this block. + */ + boost::uint32_t get_address(size_t block_port=0); + + /*! Returns the unique block ID for this block (e.g. "0/FFT_1"). + */ + block_id_t get_block_id() const { return _block_id; }; + + /*! Shorthand for get_block_id().to_string() + */ + std::string unique_id() const { return _block_id.to_string(); }; + + /*********************************************************************** + * FPGA control & communication + **********************************************************************/ + + /*! Returns a list of valid ports that can be used for sr_write(), sr_read() etc. + */ + std::vector<size_t> get_ctrl_ports() const; + + /*! Allows setting one register on the settings bus. + * + * Note: There is no address translation ("memory mapping") necessary. + * Register 0 is 0, 1 is 1 etc. + * + * \param reg The settings register to write to. + * \param data New value of this register. + */ + void sr_write(const boost::uint32_t reg, const boost::uint32_t data, const size_t port = 0); + + /*! Allows setting one register on the settings bus. + * + * Like sr_write(), but takes a register name as argument. + * + * \param reg The settings register to write to. + * \param data New value of this register. + * \param port Port on which to write + * \throw uhd::key_error if \p reg is not a valid register name + * + */ + void sr_write(const std::string ®, const boost::uint32_t data, const size_t port = 0); + + /*! Allows reading one register on the settings bus (64-Bit version). + * + * \param reg The settings register to be read. + * \param port Port on which to read + * + * Returns the readback value. + */ + boost::uint64_t sr_read64(const settingsbus_reg_t reg, const size_t port = 0); + + /*! Allows reading one register on the settings bus (32-Bit version). + * + * \param reg The settings register to be read. + * \param port Port on which to read + * + * Returns the readback value. + */ + boost::uint32_t sr_read32(const settingsbus_reg_t reg, const size_t port = 0); + + /*! Allows reading one user-defined register (64-Bit version). + * + * This is a shorthand for setting the requested address + * through sr_write() and then reading SR_READBACK_REG_USER + * with sr_read64(). + * + * \param addr The user register address. + * \param port Port on which to read + * \returns the readback value. + */ + boost::uint64_t user_reg_read64(const boost::uint32_t addr, const size_t port = 0); + + /*! Allows reading one user-defined register (64-Bit version). + * + * Identical to user_reg_read64(), but takes a register name + * instead of a numeric address. The register name must be + * defined in the block definition file. + * + * \param addr The user register address. + * \param port Port on which to read + * \returns the readback value. + * \throws uhd::key_error if \p reg is not a valid register name + */ + boost::uint64_t user_reg_read64(const std::string ®, const size_t port = 0); + + /*! Allows reading one user-defined register (32-Bit version). + * + * This is a shorthand for setting the requested address + * through sr_write() and then reading SR_READBACK_REG_USER + * with sr_read32(). + * + * \param addr The user register address. + * \param port Port on which to read + * \returns the readback value. + */ + boost::uint32_t user_reg_read32(const boost::uint32_t addr, const size_t port = 0); + + /*! Allows reading one user-defined register (32-Bit version). + * + * Identical to user_reg_read32(), but takes a register name + * instead of a numeric address. The register name must be + * defined in the block definition file. + * + * \param reg The user register name. + * \returns the readback value. + * \throws uhd::key_error if \p reg is not a valid register name + */ + boost::uint32_t user_reg_read32(const std::string ®, const size_t port = 0); + + + /*! Sets a command time for all future command packets. + * + * \throws uhd::assertion_error if the underlying interface does not + * actually support timing. + */ + void set_command_time(const time_spec_t &time_spec, const size_t port = ANY_PORT); + + /*! Returns the current command time for all future command packets. + * + * \returns the command time as a time_spec_t. + */ + time_spec_t get_command_time(const size_t port = 0); + + /*! Sets a tick rate for the command timebase. + * + * \param the tick rate in Hz + * \port port Port + */ + void set_command_tick_rate(const double tick_rate, const size_t port = ANY_PORT); + + /*! Resets the command time. + * Any command packet after this call will no longer have a time associated + * with it. + * + * \throws uhd::assertion_error if the underlying interface does not + * actually support timing. + */ + void clear_command_time(const size_t port); + + /*! Reset block after streaming operation. + * + * This does the following: + * - Reset flow control (sequence numbers etc.) + * - Clear the list of connected blocks + * + * Internally, rfnoc::node_ctrl_base::clear() and _clear() are called + * (in that order). + * + * Between runs, it can be necessary to call this method, + * or blocks might be left hanging in a streaming state, and can get + * confused when a new application starts. + * + * For custom behaviour, overwrite _clear(). If you do so, you must take + * take care of resetting flow control yourself. + * + * TODO: Find better name (it disconnects, clears FC...) + */ + void clear(const size_t port = 0/* reserved, currently not used */); + + /*********************************************************************** + * Argument handling + **********************************************************************/ + /*! Set multiple block args. Calls set_arg() for all individual items. + * + * Note that this function will silently ignore any keys in \p args that + * aren't already registered as block arguments. + */ + void set_args(const uhd::device_addr_t &args, const size_t port = 0); + + //! Set a specific block argument. \p val is converted to the corresponding + // data type using by looking up its type in the block definition. + void set_arg(const std::string &key, const std::string &val, const size_t port = 0); + + //! Direct access to set a block argument. + template <typename T> + void set_arg(const std::string &key, const T &val, const size_t port = 0) { + _tree->access<T>(get_arg_path(key, port) / "value").set(val); + } + + //! Return all block arguments as a device_addr_t. + uhd::device_addr_t get_args(const size_t port = 0) const; + + //! Return a single block argument in string format. + std::string get_arg(const std::string &key, const size_t port = 0) const; + + //! Direct access to get a block argument. + template <typename T> + T get_arg(const std::string &key, const size_t port = 0) const { + return _tree->access<T>(get_arg_path(key, port) / "value").get(); + } + + std::string get_arg_type(const std::string &key, const size_t port = 0) const; + +protected: + /*********************************************************************** + * Structors + **********************************************************************/ + block_ctrl_base(void) {}; // To allow pure virtual (interface) sub-classes + virtual ~block_ctrl_base(); + + /*! Constructor. This is only called from the internal block factory! + * + * \param make_args All arguments to this constructor are passed in this object. + * Its details are subject to change. Use the UHD_RFNOC_BLOCK_CONSTRUCTOR() + * macro to set up your block's constructor in a portable fashion. + */ + block_ctrl_base( + const make_args_t &make_args + ); + + /*********************************************************************** + * Helpers + **********************************************************************/ + stream_sig_t _resolve_port_def(const blockdef::port_t &port_def) const; + + //! Return the property tree path to a block argument \p key on \p port + uhd::fs_path get_arg_path(const std::string &key, size_t port = 0) const { + return _root_path / "args" / port / key; + }; + + //! Get a control interface object for block port \p block_port + wb_iface::sptr get_ctrl_iface(const size_t block_port); + + + /*********************************************************************** + * Hooks & Derivables + **********************************************************************/ + + //! Override this function if your block does something else + // than reset register SR_CLEAR_TX_FC. + virtual void _clear(const size_t port = 0); + + /*********************************************************************** + * Protected members + **********************************************************************/ + + //! Property sub-tree + uhd::property_tree::sptr _tree; + + //! Root node of this block's properties + uhd::fs_path _root_path; + + //! Endianness of underlying transport (for data transport) + bool _transport_is_big_endian; + + //! Block definition (stores info about the block such as ports) + blockdef::sptr _block_def; + +private: + //! Helper function to initialize the port definition nodes in the prop tree + void _init_port_defs( + const std::string &direction, + blockdef::ports_t ports, + const size_t first_port_index=0 + ); + + //! Helper function to initialize the block args (used by ctor only) + void _init_block_args(); + + /*********************************************************************** + * Private members + **********************************************************************/ + //! Objects to actually send and receive the commands + std::map<size_t, wb_iface::sptr> _ctrl_ifaces; + + //! The base address of this block (the address of block port 0) + uint32_t _base_address; + + //! The (unique) block ID. + block_id_t _block_id; + + //! Interface to NocScript parser + boost::shared_ptr<nocscript::block_iface> _nocscript_iface; +}; /* class block_ctrl_base */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_BLOCK_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/block_id.hpp b/host/include/uhd/rfnoc/block_id.hpp new file mode 100644 index 000000000..a8f2aec5a --- /dev/null +++ b/host/include/uhd/rfnoc/block_id.hpp @@ -0,0 +1,211 @@ +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_BLOCK_ID_HPP +#define INCLUDED_UHD_TYPES_BLOCK_ID_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> +#include <iostream> +#include <string> + +namespace uhd { + struct fs_path; + + namespace rfnoc { + + /*! + * Identifies an RFNoC block. + * + * An RFNoC block ID is a string such as: 0/FFT_1 + * + * The rules for formatting such a string are: + * + * DEVICE/BLOCKNAME_COUNTER + * + * DEVICE: Identifies the device (usually the motherboard index) + * BLOCKNAME: A name given to this block + * COUNTER: If is are more than one block with a BLOCKNAME, this counts up. + * + * So, 0/FFT_1 means we're addressing the second block called FFT + * on the first device. + * + * This class can represent these block IDs. + */ + class UHD_RFNOC_API block_id_t + { + public: + block_id_t(); + block_id_t(const std::string &block_str); + //! \param device_no Device number + //! \param block_name Block name + //! \param block_ctr Which block of this type is this on this device? + block_id_t(const size_t device_no, const std::string &block_name, const size_t block_ctr=0); + + //! Return a string like this: "0/FFT_1" (includes all components, if set) + std::string to_string() const; + + //! Check if a given string is valid as a block name. + // + // Note: This only applies to the block *name*, not the entire block ID. + // Examples: + // * is_valid_blockname("FFT") will return true. + // * is_valid_blockname("FIR_Filter") will return false, because an underscore + // is not allowed in a block name. + // + // Internally, this matches the string with uhd::rfnoc::VALID_BLOCKNAME_REGEX. + static bool is_valid_blockname(const std::string &block_name); + + //! Check if a given string is valid as a block ID. + // + // Note: This does necessary require a complete complete ID. If this returns + // true, then it is a valid input for block_id_t::match(). + // + // Examples: + // * is_valid_block_id("FFT") will return true. + // * is_valid_block_id("0/Filter_1") will return true. + // * is_valid_block_id("0/Filter_Foo") will return false. + // + // Internally, this matches the string with uhd::rfnoc::VALID_BLOCKID_REGEX. + static bool is_valid_block_id(const std::string &block_id); + + //! Check if block_str matches this block. + // + // A match is a less strict version of equality. + // Less specific block IDs will match more specific ones, + // e.g. "FFT" will match "0/FFT_1", "1/FFT_2", etc. + // "FFT_1" will only match the former, etc. + bool match(const std::string &block_str); + + // Getters + + //! Short for to_string() + std::string get() const { return to_string(); }; + + //! Like get(), but only returns the local part ("FFT_1") + std::string get_local() const; + + //! Returns the property tree root for this block (e.g. "/mboards/0/xbar/FFT_1/") + uhd::fs_path get_tree_root() const; + + //! Return device number + size_t get_device_no() const { return _device_no; }; + + //! Return block count + size_t get_block_count() const { return _block_ctr; }; + + //! Return block name + std::string get_block_name() const { return _block_name; }; + + // Setters + + //! Set from string such as "0/FFT_1", "FFT_0", ... + // Returns true if successful (i.e. if string valid) + bool set(const std::string &new_name); + + //! Sets from individual compontents, like calling set_device_no(), set_block_name() + // and set_block_count() one after another, only if \p block_name is invalid, stops + // and returns false before chaning anything + bool set(const size_t device_no, const std::string &block_name, const size_t block_ctr=0); + + //! Set the device number + void set_device_no(size_t device_no) { _device_no = device_no; }; + + //! Set the block name. Will return false if invalid block string. + bool set_block_name(const std::string &block_name); + + //! Set the block count. + void set_block_count(size_t count) { _block_ctr = count; }; + + // Overloaded operators + + //! Assignment: Works like set(std::string) + block_id_t operator = (const std::string &new_name) { + set(new_name); + return *this; + } + + bool operator == (const block_id_t &block_id) const { + return (_device_no == block_id.get_device_no()) + and (_block_name == block_id.get_block_name()) + and (_block_ctr == block_id.get_block_count()); + } + + bool operator != (const block_id_t &block_id) const { + return not (*this == block_id); + } + + bool operator < (const block_id_t &block_id) const { + return ( + _device_no < block_id.get_device_no() + or (_device_no == block_id.get_device_no() and _block_name < block_id.get_block_name()) + or (_device_no == block_id.get_device_no() and _block_name == block_id.get_block_name() and _block_ctr < block_id.get_block_count()) + ); + } + + bool operator > (const block_id_t &block_id) const { + return ( + _device_no > block_id.get_device_no() + or (_device_no == block_id.get_device_no() and _block_name > block_id.get_block_name()) + or (_device_no == block_id.get_device_no() and _block_name == block_id.get_block_name() and _block_ctr > block_id.get_block_count()) + ); + } + + //! Check if a string matches the entire block ID (not like match()) + bool operator == (const std::string &block_id_str) const { + return get() == block_id_str; + } + + //! Check if a string matches the entire block ID (not like match()) + bool operator == (const char *block_id_str) const { + std::string comp = std::string(block_id_str); + return *this == comp; + } + + //! Type-cast operator does the same as to_string() + operator std::string() const { + return to_string(); + } + + //! Increment the block count ("FFT_1" -> "FFT_2") + block_id_t operator++() { + _block_ctr++; + return *this; + } + + //! Increment the block count ("FFT_1" -> "FFT_2") + block_id_t operator++(int) { + _block_ctr++; + return *this; + } + + private: + size_t _device_no; + std::string _block_name; + size_t _block_ctr; + }; + + //! Shortcut for << block_id.to_string() + inline std::ostream& operator<< (std::ostream& out, block_id_t block_id) { + out << block_id.to_string(); + return out; + } + +}} //namespace uhd::rfnoc + +#endif /* INCLUDED_UHD_TYPES_BLOCK_ID_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/blockdef.hpp b/host/include/uhd/rfnoc/blockdef.hpp new file mode 100644 index 000000000..fc3505d3c --- /dev/null +++ b/host/include/uhd/rfnoc/blockdef.hpp @@ -0,0 +1,126 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_BLOCKDEF_HPP +#define INCLUDED_LIBUHD_RFNOC_BLOCKDEF_HPP + +#include <boost/cstdint.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <uhd/config.hpp> +#include <uhd/types/device_addr.hpp> +#include <vector> +#include <set> + +namespace uhd { namespace rfnoc { + +/*! Reads and stores block definitions for blocks and components. + */ +class UHD_RFNOC_API blockdef : public boost::enable_shared_from_this<blockdef> +{ +public: + typedef boost::shared_ptr<blockdef> sptr; + + //! Describes port options for a block definition. + // + // This is not the same as a uhd::rfnoc::stream_sig_t. This is used + // to describe which ports are defined in a block definition, and + // to describe what kind of connection is allowed for this port. + // + // All the keys listed in PORT_ARGS will be available in this class. + class port_t : public uhd::dict<std::string, std::string> { + public: + //! A list of args a port can have. + static const device_addr_t PORT_ARGS; + + port_t(); + + //! Checks if the value at \p key is a variable (e.g. '$fftlen') + bool is_variable(const std::string &key) const; + //! Checks if the value at \p key is a keyword (e.g. '%vlen') + bool is_keyword(const std::string &key) const; + //! Basic validity check of this port definition. Variables and + // keywords are not resolved. + bool is_valid() const; + //! Returns a string with the most important keys + std::string to_string() const; + }; + typedef std::vector<port_t> ports_t; + + //! Describes arguments in a block definition. + class arg_t : public uhd::dict<std::string, std::string> { + public: + //! A list of args an argument can have. + static const device_addr_t ARG_ARGS; + static const std::set<std::string> VALID_TYPES; + + arg_t(); + + //! Basic validity check of this argument definition. + bool is_valid() const; + //! Returns a string with the most important keys + std::string to_string() const; + + }; + typedef std::vector<arg_t> args_t; + + typedef uhd::dict<std::string, size_t> registers_t; + + /*! Create a block definition object for a NoC block given + * a NoC ID. This cannot be used for components. + * + * Note: If nothing is found, returns an + * empty sptr. Does not throw. + */ + static sptr make_from_noc_id(boost::uint64_t noc_id); + + //! Returns true if this represents a NoC block + virtual bool is_block() const = 0; + + //! Returns true if this represents a component + virtual bool is_component() const = 0; + + //! Returns block key (i.e. what is used for the registry) + virtual std::string get_key() const = 0; + + //! For blocks, returns the block name. For components, returns it's canonical name. + virtual std::string get_name() const = 0; + + //! Return the one NoC that is valid for this block + virtual boost::uint64_t noc_id() const = 0; + + virtual ports_t get_input_ports() = 0; + virtual ports_t get_output_ports() = 0; + + //! Returns the full list of port numbers used + virtual std::vector<size_t> get_all_port_numbers() = 0; + + //! Returns the args for this block. Checks if args are valid. + // + // \throws uhd::runtime_error if args are invalid. + virtual args_t get_args() = 0; + + //! Returns a list of settings registers by name. + virtual registers_t get_settings_registers() = 0; + + //! Returns a list of readback (user) registers by name. + virtual registers_t get_readback_registers() = 0; +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_BLOCKDEF_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/blocks/CMakeLists.txt b/host/include/uhd/rfnoc/blocks/CMakeLists.txt new file mode 100644 index 000000000..341db3366 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright 2014-2016 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +FILE(GLOB xml_files "*.xml") + +# We always need this, even when RFNoC is 'disabled' +UHD_INSTALL( + FILES ${xml_files} + DESTINATION ${PKG_DATA_DIR}/rfnoc/blocks + COMPONENT headers # TODO: Different component +) diff --git a/host/include/uhd/rfnoc/blocks/addsub.xml b/host/include/uhd/rfnoc/blocks/addsub.xml new file mode 100644 index 000000000..2412e5022 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/addsub.xml @@ -0,0 +1,50 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <!--The Adder & Subtractor takes inputs from Block Ports 0 & 1 and--> + <!--outputs the addition / subtraction of the values on Block Ports 0 & 1.--> + <!--- Block Port 0 + Block Port 1 => Block Port 0--> + <!--- Block Port 0 - Block Port 1 => Block Port 1--> + <name>Adder & Subtractor</name> + <blockname>AddSub</blockname> + <ids> + <id revision="0">ADD0</id> + </ids> + <!--Order matters. The first listed port is port 0, etc.--> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + <port>0</port> + </sink> + <sink> + <name>in1</name> + <type>sc16</type> + <port>1</port> + </sink> + <source> + <name>sum</name> + <type>sc16</type> + </source> + <source> + <name>diff</name> + <type>sc16</type> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/block.xml b/host/include/uhd/rfnoc/blocks/block.xml new file mode 100644 index 000000000..dfe616c45 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/block.xml @@ -0,0 +1,17 @@ +<!--Default XML file--> +<nocblock> + <name>Block</name> + <blockname>Block</blockname> + <ids> + <id revision="0">FFFFFFFFFFFFFFFF</id> + </ids> + <!--One input, one output. If this is used, better have all the info the C++ file.--> + <ports> + <sink> + <name>in</name> + </sink> + <source> + <name>out</name> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/ddc.xml b/host/include/uhd/rfnoc/blocks/ddc.xml new file mode 100644 index 000000000..a88616117 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/ddc.xml @@ -0,0 +1,154 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Rx DSP (DDC/CORDIC)</name> + <blockname>DDC</blockname> + <key>DDC</key> + <!--There can be several of these:--> + <ids> + <id revision="0">DDC0</id> + </ids> + <!-- Registers --> + <registers> + <!-- AXI rate change block registers --> + <setreg> + <name>N</name> + <address>128</address> + </setreg> + <setreg> + <name>M</name> + <address>129</address> + </setreg> + <setreg> + <!-- 1 bit, enable clear user --> + <name>CONFIG</name> + <address>130</address> + </setreg> + <!-- DDC block registers --> + <setreg> + <!-- CORDIC phase increment word --> + <name>CORDIC_FREQ</name> + <address>132</address> + </setreg> + <setreg> + <!-- Scaling factor to compensate for gain through filters and CORDIC --> + <name>SCALE_IQ</name> + <address>133</address> + </setreg> + <setreg> + <!-- DDC control word, 10 bits total, 2 bits for Halfbands, 8 bits for CIC rate --> + <name>DECIM_WORD</name> + <address>134</address> + </setreg> + <setreg> + <!-- Real mode, swap IQ --> + <name>MODE</name> + <address>135</address> + </setreg> + <setreg> + <!-- Filter coefficients reload --> + <name>RELOAD</name> + <address>136</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>0</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>input_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($input_rate, 0.0)</check> + <check_message>The input rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>output_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($output_rate, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>fullscale</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($fullscale, 0.0)</check> + </arg> + <arg> + <name>scalar_correction</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + </arg> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>1</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>input_rate</name> + <type>double</type> + <value>1.0</value> + <port>1</port> + <check>GE($input_rate, 0.0)</check> + <check_message>The input rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>output_rate</name> + <type>double</type> + <value>1.0</value> + <port>1</port> + <check>GE($output_rate, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>fullscale</name> + <type>double</type> + <value>1.0</value> + <port>1</port> + <check>GE($fullscale, 0.0)</check> + </arg> + <arg> + <name>scalar_correction</name> + <type>double</type> + <value>1.0</value> + <port>1</port> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + </sink> + <sink> + <name>in1</name> + <type>sc16</type> + </sink> + <source> + <name>out0</name> + <type>sc16</type> + </source> + <source> + <name>out1</name> + <type>sc16</type> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/ddc_single.xml b/host/include/uhd/rfnoc/blocks/ddc_single.xml new file mode 100644 index 000000000..581487388 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/ddc_single.xml @@ -0,0 +1,117 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Rx DSP (DDC/CORDIC)</name> + <blockname>DDC</blockname> + <key>DDC</key> + <!--There can be several of these:--> + <ids> + <id revision="0">DDC0000000000001</id> + </ids> + <!-- Registers --> + <registers> + <!-- AXI rate change block registers --> + <setreg> + <name>N</name> + <address>128</address> + </setreg> + <setreg> + <name>M</name> + <address>129</address> + </setreg> + <setreg> + <!-- 1 bit, enable clear user --> + <name>CONFIG</name> + <address>130</address> + </setreg> + <!-- DDC block registers --> + <setreg> + <!-- CORDIC phase increment word --> + <name>CORDIC_FREQ</name> + <address>132</address> + </setreg> + <setreg> + <!-- Scaling factor to compensate for gain through filters and CORDIC --> + <name>SCALE_IQ</name> + <address>133</address> + </setreg> + <setreg> + <!-- DDC control word, 10 bits total, 2 bits for Halfbands, 8 bits for CIC rate --> + <name>DECIM_WORD</name> + <address>134</address> + </setreg> + <setreg> + <!-- Real mode, swap IQ --> + <name>MODE</name> + <address>135</address> + </setreg> + <setreg> + <!-- Filter coefficients reload --> + <name>RELOAD</name> + <address>136</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>0</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>input_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($input_rate, 0.0)</check> + <check_message>The input rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>output_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($output_rate, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>fullscale</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($fullscale, 0.0)</check> + </arg> + <arg> + <name>scalar_correction</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + </arg> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>1</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + </sink> + <source> + <name>out0</name> + <type>sc16</type> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/dma_fifo.xml b/host/include/uhd/rfnoc/blocks/dma_fifo.xml new file mode 100644 index 000000000..fb30d58fe --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/dma_fifo.xml @@ -0,0 +1,64 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>DMA FIFO</name> + <blockname>DmaFIFO</blockname> + <key>DmaFIFO</key> + <!--There can be several of these:--> + <ids> + <id revision="0">F1F0D000</id> + </ids> + <!-- Registers --> + <registers> + </registers> + <!-- Args --> + <args> + <arg> + <name>base_addr</name> + <type>int</type> + <!--<value>0</value>--> + <port>0</port> + <check>EQUAL($base_addr, 0) OR IS_PWR_OF_2($base_addr)</check> + <check_message>The base address must be 0 or a positive power of 2.</check_message> + </arg> + <arg> + <name>depth</name> + <type>int</type> + <!--<value>33554432</value>--> + <port>0</port> + <check>IS_PWR_OF_2($depth)</check> + <check_message>The FIFO depth must be a positive power of 2.</check_message> + </arg> + <arg> + <name>base_addr</name> + <type>int</type> + <!--<value>33554432</value>--> + <port>1</port> + <check>EQUAL($base_addr, 0) OR IS_PWR_OF_2($base_addr)</check> + <check_message>The base address must be 0 or a positive power of 2.</check_message> + </arg> + <arg> + <name>depth</name> + <type>int</type> + <!--<value>33554432</value>--> + <port>1</port> + <check>IS_PWR_OF_2($depth)</check> + <check_message>The FIFO depth must be a positive power of 2.</check_message> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in0</name> + </sink> + <sink> + <name>in1</name> + </sink> + <source> + <name>out0</name> + </source> + <source> + <name>out1</name> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/duc.xml b/host/include/uhd/rfnoc/blocks/duc.xml new file mode 100644 index 000000000..62f005372 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/duc.xml @@ -0,0 +1,96 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Tx DSP (DUC/CORDIC)</name> + <blockname>DUC</blockname> + <key>DUC</key> + <!--There can be several of these:--> + <ids> + <id revision="0">D0C0</id> + </ids> + <!-- Registers --> + <registers> + <!-- AXI rate change block registers --> + <setreg> + <name>N</name> + <address>128</address> + </setreg> + <setreg> + <name>M</name> + <address>129</address> + </setreg> + <setreg> + <!-- 1 bit, enable clear user --> + <name>CONFIG</name> + <address>130</address> + </setreg> + <!-- DUC block registers --> + <setreg> + <name>INTERP_WORD</name> <!--Includes the half-bands and the CIC--> + <address>131</address> + </setreg> + <setreg> + <name>CORDIC_FREQ</name> + <address>132</address> + </setreg> + <setreg> + <name>SCALE_IQ</name> + <address>133</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>0</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>input_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($input_rate, 0.0)</check> + <check_message>The input rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>output_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($output_rate, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>fullscale</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($fullscale, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>scalar_correction</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + </sink> + <source> + <name>out</name> + <type>sc16</type> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/fft.xml b/host/include/uhd/rfnoc/blocks/fft.xml new file mode 100644 index 000000000..7dd2eff46 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fft.xml @@ -0,0 +1,116 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>FFT</name> + <blockname>FFT</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">FF70</id> + </ids> + <!-- Registers --> + <registers> + <!--Note: AXI config bus uses 129 & 130--> + <setreg> + <name>FFT_RESET</name> + <address>131</address> + </setreg> + <setreg> + <name>FFT_SIZE_LOG2</name> + <address>132</address> + </setreg> + <setreg> + <name>MAGNITUDE_OUT</name> + <address>133</address> + </setreg> + <readback> + <name>RB_FFT_RESET</name> + <address>0</address> + </readback> + <readback> + <name>RB_MAGNITUDE_OUT</name> + <address>1</address> + </readback> + </registers> + <!-- Args --> + <args> + <arg> + <!--This controls only the fft shift part, so remember to also set the ctrl_word--> + <name>spp</name> + <type>int</type> + <value>256</value> + <check>GE($spp, 16) AND LE($spp, 4096) AND IS_PWR_OF_2($spp)</check> + <check_message>FFT size must be in [16, 4096] and a power of two.</check_message> + <action>SR_WRITE("FFT_SIZE_LOG2", LOG2($spp)) AND SR_WRITE("AXIS_CONFIG_BUS", ADD(873472, LOG2($spp)))</action> + </arg> + <arg> + <name>ctrl_word</name> + <type>int</type> + <value>873472</value> + <!--<check>EQUAL($otype, "sc16")</check>--> + <!--<check_message>Output data type must be sc16.</check_message>--> + <!--TODO: Check against mag-out value (requires GET() function) --> + <action>SR_WRITE("AXIS_CONFIG_BUS", ADD($ctrl_word, LOG2($spp)))</action> + </arg> + <arg> + <name>otype</name> + <type>string</type> + <value>sc16</value> + <check>EQUAL($otype, "sc16")</check> + <check_message>Output data type must be sc16.</check_message> + <!--TODO: Check against mag-out value (requires GET() function) --> + </arg> + <arg> + <name>reset</name> + <type>int</type> + <value>1</value> + <action> + IF(NOT(EQUAL($reset, 0)), SR_WRITE("FFT_RESET", 1) AND SR_WRITE("FFT_RESET", 0)) + </action> + <!--TODO: Set to zero after setting, add publisher--> + </arg> + <arg> + <name>magnitude_out</name> + <type>string</type> + <value>COMPLEX</value> + <check>EQUAL($magnitude_out, "COMPLEX") OR EQUAL($magnitude_out, "MAGNITUDE") OR EQUAL($magnitude_out, "MAGNITUDE_SQUARED")</check> + <check_message>Output format must be one of: COMPLEX, MAGNITUDE, MAGNITUDE_SQUARED.</check_message> + <action> + IF(EQUAL($magnitude_out, "COMPLEX"), SR_WRITE("MAGNITUDE_OUT", 0)) OR + IF(EQUAL($magnitude_out, "MAGNITUDE"), SR_WRITE("MAGNITUDE_OUT", 1)) OR + IF(EQUAL($magnitude_out, "MAGNITUDE_SQUARED"), SR_WRITE("MAGNITUDE_OUT", 2)) + </action> + <!--TODO: add publisher--> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>$otype</type> <!--TODO make this dependent on the output type --> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/fifo.xml b/host/include/uhd/rfnoc/blocks/fifo.xml new file mode 100644 index 000000000..9e8900b89 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fifo.xml @@ -0,0 +1,34 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>FIFO</name> + <blockname>FIFO</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">F1F00000</id> + </ids> + <ports> + <sink> + <name>in0</name> + </sink> + <source> + <name>out0</name> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/fir.xml b/host/include/uhd/rfnoc/blocks/fir.xml new file mode 100644 index 000000000..9a97e3a84 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fir.xml @@ -0,0 +1,19 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>FIR Filter</name> + <blockname>FIR</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">F112</id> + </ids> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + </sink> + <source> + <name>out</name> + <type>sc16</type> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/fosphor.xml b/host/include/uhd/rfnoc/blocks/fosphor.xml new file mode 100644 index 000000000..762b7c1bd --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fosphor.xml @@ -0,0 +1,157 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>fosphor</name> + <blockname>fosphor</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">666F</id> + </ids> + <!-- Registers --> + <registers> + <setreg> + <name>DECIM</name> + <address>129</address> + </setreg> + <setreg> + <name>OFFSET</name> + <address>130</address> + </setreg> + <setreg> + <name>SCALE</name> + <address>131</address> + </setreg> + <setreg> + <name>TRISE</name> + <address>132</address> + </setreg> + <setreg> + <name>TDECAY</name> + <address>133</address> + </setreg> + <setreg> + <name>ALPHA</name> + <address>134</address> + </setreg> + <setreg> + <name>EPSILON</name> + <address>135</address> + </setreg> + <setreg> + <name>RANDOM</name> + <address>136</address> + </setreg> + <setreg> + <name>CLEAR</name> + <address>137</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>1024</value> + </arg> + <arg> + <name>decim</name> + <type>int</type> + <value>2</value> + <check>GE($decim, 2) AND LE($decim, 1024)</check> + <check_message>fosphor decim constant must be within [2, 1024]</check_message> + <action>SR_WRITE("DECIM", ADD($decim, -2))</action> + </arg> + <arg> + <name>offset</name> + <type>int</type> + <value>0</value> + <check>GE($offset, 0) AND LE($offset, 65536)</check> + <check_message>"fosphor offset value must be within [0, 65535]"</check_message> + <action>SR_WRITE("OFFSET", $offset)</action> + </arg> + <arg> + <name>scale</name> + <type>int</type> + <value>256</value> + <check>GE($scale, 0) AND LE($scale, 65536)</check> + <check_message>"fosphor scale value must be within [0, 65535]"</check_message> + <action>SR_WRITE("SCALE", $scale)</action> + </arg> + <arg> + <name>trise</name> + <type>int</type> + <value>4096</value> + <check>GE($trise, 0) AND LE($trise, 65536)</check> + <check_message>"fosphor trise value must be within [0, 65535]"</check_message> + <action>SR_WRITE("TRISE", $trise)</action> + </arg> + <arg> + <name>tdecay</name> + <type>int</type> + <value>16384</value> + <check>GE($tdecay, 0) AND LE($tdecay, 65536)</check> + <check_message>"fosphor tdecay value must be within [0, 65535]"</check_message> + <action>SR_WRITE("TDECAY", $tdecay)</action> + </arg> + <arg> + <name>alpha</name> + <type>int</type> + <value>65280</value> + <check>GE($alpha, 0) AND LE($alpha, 65536)</check> + <check_message>"fosphor alpha value must be within [0, 65535]"</check_message> + <action>SR_WRITE("ALPHA", $alpha)</action> + </arg> + <arg> + <name>epsilon</name> + <type>int</type> + <value>1</value> + <check>GE($epsilon, 0) AND LE($epsilon, 65536)</check> + <check_message>"fosphor epsilon value must be within [0, 65535]"</check_message> + <action>SR_WRITE("EPSILON", $epsilon)</action> + </arg> + <arg> + <name>random</name> + <type>int</type> + <value>1</value> + <check>GE($random, 0) AND LE($random, 3)</check> + <check_message>"fosphor random value must be within [0, 65535]"</check_message> + <action>SR_WRITE("RANDOM", $random)</action> + </arg> + <arg> + <name>clear</name> + <type>int</type> + <action>IF(NOT(EQUAL($clear, 0)), SR_WRITE("CLEAR", $clear))</action> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>u8</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/keep_one_in_n.xml b/host/include/uhd/rfnoc/blocks/keep_one_in_n.xml new file mode 100644 index 000000000..5a99685de --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/keep_one_in_n.xml @@ -0,0 +1,55 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Keep One in N</name> + <blockname>KeepOneInN</blockname> + <doc> + Block controller for the Keep One in N RFNoC block. + + For every N packets received, this block will output a single packet. + - One input / output block port + - N up to 65535 + </doc> + <!--There can be several of these:--> + <ids> + <id revision="0">0246</id> + </ids> + <registers> + <setreg> + <name>SR_N</name> + <address>129</address> + </setreg> + </registers> + <args> + <arg> + <name>n</name> + <type>int</type> + <value>256</value> + <action>SR_WRITE("SR_N", $n)</action> + </arg> + </args> + <ports> + <sink> + <name>in</name> + </sink> + <source> + <name>out</name> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/logpwr.xml b/host/include/uhd/rfnoc/blocks/logpwr.xml new file mode 100644 index 000000000..9307446e3 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/logpwr.xml @@ -0,0 +1,49 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Log Power</name> + <blockname>LogPwr</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">4C50</id> + </ids> + <!-- Args --> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>256</value> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/nullblock.xml b/host/include/uhd/rfnoc/blocks/nullblock.xml new file mode 100644 index 000000000..f1ed3bbd2 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/nullblock.xml @@ -0,0 +1,30 @@ +<nocblock> + <name>Null Source/Sink</name> + <blockname>NullSrcSink</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">0000000000000000</id> + </ids> + <!-- Args --> + <args> + <arg> + <name>line_rate</name> + <type>int</type> + <value>65535</value> + </arg> + <arg> + <name>bpp</name> + <type>int</type> + <value>256</value> + </arg> + </args> + <!-- Ports --> + <ports> + <sink> + <name>dump</name> + </sink> + <source> + <name>src</name> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/ofdmeq.xml b/host/include/uhd/rfnoc/blocks/ofdmeq.xml new file mode 100644 index 000000000..50218e976 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/ofdmeq.xml @@ -0,0 +1,31 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>OFDM Equalizer</name> + <blockname>OFDMEq</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">FF42</id> + </ids> + <!-- Args --> + <args> + <arg> + <name>fftsize</name> + <type>int</type> + <value>64</value> + </arg> + </args> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$fftsize</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>sc16</type> + <vlen>$fftsize</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/packetresizer.xml b/host/include/uhd/rfnoc/blocks/packetresizer.xml new file mode 100644 index 000000000..306218318 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/packetresizer.xml @@ -0,0 +1,55 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Packet Resizer</name> + <blockname>PacketResizer</blockname> + <doc> + Block controller for the Packet Resizer RFNoC block. + + The Packet Resizer RFNoC block changes the packet length of a stream. It can break large + packets into smaller packets or combine small packets into a single larger packet. + </doc> + <ids> + <id revision="0">12E5</id> + </ids> + <registers> + <setreg> + <name>SR_PKT_SIZE</name> + <address>129</address> + </setreg> + </registers> + <args> + <arg> + <name>pkt_size</name> + <type>int</type> + <value>32</value> + <check>GT($pkt_size, 0)</check> + <check_message>Packet size must be positive, non-zero.</check_message> + <action>SR_WRITE("SR_PKT_SIZE", $pkt_size)</action> + </arg> + </args> + <ports> + <sink> + <name>in</name> + </sink> + <source> + <name>out</name> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/radio_x300.xml b/host/include/uhd/rfnoc/blocks/radio_x300.xml new file mode 100644 index 000000000..4130522a5 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/radio_x300.xml @@ -0,0 +1,60 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Radio (X300)</name> + <blockname>Radio</blockname> + <key>X300Radio</key> + <!--There can be several of these:--> + <ids> + <id revision="0">12AD100000000001</id> + </ids> + <!-- Registers --> + <registers> + <!--<setreg>--> + <!--<name>FFT_RESET</name>--> + <!--<address>131</address>--> + <!--</setreg>--> + <!--<readback>--> + <!--<name>RB_MAGNITUDE_OUT</name>--> + <!--<address>1</address>--> + <!--</readback>--> + </registers> + <!-- Args --> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>364</value> + <!--<value>256</value>--> + <!--<check>GE($spp, 16) AND LE($spp, 4096) AND IS_PWR_OF_2($spp)</check>--> + <!--<check_message>FFT size must be in [16, 4096] and a power of two.</check_message>--> + <!--<action>SR_WRITE("FFT_SIZE_LOG2", LOG2($spp)) AND SR_WRITE("AXIS_CONFIG_BUS", ADD(873472, LOG2($spp)))</action>--> + </arg> + </args> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + <!--<vlen>$spp</vlen>--> + <!--<pkt_size>%vlen</pkt_size>--> + </sink> + <sink> + <name>in1</name> + <type>sc16</type> + <!--<vlen>$spp</vlen>--> + <!--<pkt_size>%vlen</pkt_size>--> + </sink> + <source> + <name>out0</name> + <type>sc16</type> + <!--<vlen>$spp</vlen>--> + <!--<pkt_size>%vlen</pkt_size>--> + </source> + <source> + <name>out1</name> + <type>sc16</type> + <!--<vlen>$spp</vlen>--> + <!--<pkt_size>%vlen</pkt_size>--> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/siggen.xml b/host/include/uhd/rfnoc/blocks/siggen.xml new file mode 100644 index 000000000..2850e6804 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/siggen.xml @@ -0,0 +1,116 @@ +<nocblock> + <name>Signal Generator</name> + <blockname>SigGen</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">5166311000000000</id> + </ids> + <!-- Registers --> + <registers> + <!-- Reg 128 used for FREQ --> + <setreg> + <name>FREQ</name> + <address>129</address> + </setreg> + <setreg> + <name>CARTESIAN</name> + <address>130</address> + </setreg> + <setreg> + <name>ENABLE</name> + <address>132</address> + </setreg> + <setreg> + <name>CONSTANT</name> + <address>138</address> + </setreg> + <setreg> + <name>GAIN</name> + <address>139</address> + </setreg> + <setreg> + <name>PKT_SIZE</name> + <address>140</address> + </setreg> + <setreg> + <name>WAVEFORM</name> + <address>142</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>enable</name> + <type>int</type> + <value>0</value> + <check>EQUAL($enable, 0) OR EQUAL($enable, 1)</check> + <check_message>Enable is either 0 or 1.</check_message> + <action>SR_WRITE("ENABLE", $enable)</action> + </arg> + <arg> + <name>spp</name> + <type>int</type> + <value>256</value> + <action>SR_WRITE("PKT_SIZE", $spp)</action> + </arg> + <!-- Overall Gain --> + <arg> + <name>gain</name> + <type>double</type> + <value>1.0</value> + <check>GE($gain, 0.0) AND LE($gain, 1.0)</check> + <check_message>Invalid gain.</check_message> + <action> + SR_WRITE("GAIN", IROUND(MULT(32767.0,$gain))) + </action> + </arg> + <!-- Sine Wave, Constant I / Q --> + <arg> + <name>amplitude_i</name> + <type>double</type> + <value>1.0</value> + <check>GE($amplitude_i, -1.0) AND LE($amplitude_i, 1.0)</check> + <check_message>Invalid amplitude.</check_message> + </arg> + <arg> + <name>amplitude_q</name> + <type>double</type> + <value>1.0</value> + <check>GE($amplitude_q, -1.0) AND LE($amplitude_q, 1.0)</check> + <check_message>Invalid amplitude.</check_message> + <action> + SR_WRITE("CONSTANT", ADD(MULT(65536,IROUND(MULT(32767.0, $amplitude_i))),IROUND(MULT(32767.0, $amplitude_q)))) + </action> + </arg> + <arg> + <name>frequency</name> + <type>double</type> + <value>0.1</value> + <check>GE($frequency, -1.0) AND LE($frequency, 1.0)</check> + <check_message>Invalid frequency.</check_message> + <action>SR_WRITE("FREQ", IROUND(MULT(-8192.0, $frequency)))</action> + </arg> + <arg> + <name>waveform</name> + <type>string</type> + <value>CONSTANT</value> + <check>EQUAL($waveform, "CONSTANT") OR EQUAL($waveform, "SINE_WAVE") OR EQUAL($waveform, "NOISE")</check> + <check_message>Waveform type should be one of: CONSTANT, SINE WAVE, NOISE.</check_message> + <action> + IF(EQUAL($waveform, "CONSTANT"), SR_WRITE("WAVEFORM", 0) AND SR_WRITE("CONSTANT", ADD(MULT(65536,IROUND(MULT(32767.0, $amplitude_i))),IROUND(MULT(32767.0, $amplitude_q))))) OR + IF(EQUAL($waveform, "SINE_WAVE"), SR_WRITE("WAVEFORM", 1) AND SR_WRITE("CARTESIAN", MULT(65536,28000))) OR + IF(EQUAL($waveform, "NOISE"), SR_WRITE("WAVEFORM", 2)) + </action> + </arg> + </args> + <!-- Ports --> + <ports> + <sink> + <name>dump</name> + </sink> + <source> + <name>src</name> + <type>sc16</type> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/window.xml b/host/include/uhd/rfnoc/blocks/window.xml new file mode 100644 index 000000000..df36f4b4f --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/window.xml @@ -0,0 +1,47 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Window</name> + <blockname>Window</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">D053</id> + </ids> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>256</value> + </arg> + </args> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/constants.hpp b/host/include/uhd/rfnoc/constants.hpp new file mode 100644 index 000000000..14e0da55c --- /dev/null +++ b/host/include/uhd/rfnoc/constants.hpp @@ -0,0 +1,108 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_CONSTANTS_HPP +#define INCLUDED_LIBUHD_RFNOC_CONSTANTS_HPP + +#include <uhd/types/dict.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/cstdint.hpp> +#include <string> + +namespace uhd { + namespace rfnoc { + +// All these configure the XML reader +//! Where the RFNoC block/component definition files lie, relative to UHD_PKG_DIR +static const std::string XML_DEFAULT_PATH = "share/uhd/rfnoc"; +//! The name of the environment variable storing the bath to the block definition files +static const std::string XML_PATH_ENV = "UHD_RFNOC_DIR"; + +//! If the block name can't be automatically detected, this name is used +static const std::string DEFAULT_BLOCK_NAME = "Block"; +static const boost::uint64_t DEFAULT_NOC_ID = 0xFFFFFFFFFFFFFFFF; + +static const size_t MAX_PACKET_SIZE = 8000; // bytes +static const size_t DEFAULT_PACKET_SIZE = 1456; // bytes + +// One line in FPGA is 64 Bits +static const size_t BYTES_PER_LINE = 8; + +//! For flow control within a single crossbar +static const size_t DEFAULT_FC_XBAR_PKTS_PER_ACK = 2; +//! For flow control when data is flowing from device to host (rx) +static const size_t DEFAULT_FC_RX_RESPONSE_FREQ = 64; // ACKs per flow control window +//! For flow control when data is flowing from host to device (tx) +static const size_t DEFAULT_FC_TX_RESPONSE_FREQ = 8; // ACKs per flow control window +//! On the receive side, how full do we want the buffers? +// Why not 100% full? Because we need to have some headroom to account for the inaccuracy +// when computing the window size. We compute the flow control window based on the frame +// size but the buffer can have overhead due to things like UDP headers, page alignment, +// housekeeping info, etc. This number has to be transport agnostic so 20% of headroom is safe. +static const double DEFAULT_FC_RX_SW_BUFF_FULL_FACTOR = 0.80; + +// Common settings registers. +static const boost::uint32_t SR_FLOW_CTRL_CYCS_PER_ACK = 0; +static const boost::uint32_t SR_FLOW_CTRL_PKTS_PER_ACK = 1; +static const boost::uint32_t SR_FLOW_CTRL_WINDOW_SIZE = 2; +static const boost::uint32_t SR_FLOW_CTRL_WINDOW_EN = 3; +static const boost::uint32_t SR_ERROR_POLICY = 4; +static const boost::uint32_t SR_BLOCK_SID = 5; // TODO rename to SRC_SID +static const boost::uint32_t SR_NEXT_DST_SID = 6; +static const boost::uint32_t SR_RESP_IN_DST_SID = 7; +static const boost::uint32_t SR_RESP_OUT_DST_SID = 8; + +static const boost::uint32_t SR_READBACK_ADDR = 124; +static const boost::uint32_t SR_READBACK = 127; + +static const boost::uint32_t SR_CLEAR_RX_FC = 125; +static const boost::uint32_t SR_CLEAR_TX_FC = 126; + +//! Settings register readback +enum settingsbus_reg_t { + SR_READBACK_REG_ID = 0, + SR_READBACK_REG_GLOBAL_PARAMS = 1, + SR_READBACK_REG_FIFOSIZE = 2, // fifo size + SR_READBACK_REG_MTU = 3, + SR_READBACK_REG_BLOCKPORT_SIDS = 4, + SR_READBACK_REG_USER = 5 + /* 6 currently unused */ +}; + +// AXI stream configuration bus (output master bus of axi wrapper) registers +static const boost::uint32_t AXI_WRAPPER_BASE = 128; +static const boost::uint32_t AXIS_CONFIG_BUS = AXI_WRAPPER_BASE+1; // tdata with tvalid asserted +static const boost::uint32_t AXIS_CONFIG_BUS_TLAST = AXI_WRAPPER_BASE+2; // tdata with tvalid & tlast asserted + +// Named settings registers +static const uhd::dict<std::string, boost::uint32_t> DEFAULT_NAMED_SR = boost::assign::map_list_of + ("AXIS_CONFIG_BUS", AXIS_CONFIG_BUS) + ("AXIS_CONFIG_BUS_TLAST", AXIS_CONFIG_BUS_TLAST) +; + +// Block ports +static const size_t ANY_PORT = size_t(~0); +static const size_t MAX_NUM_PORTS = 16; + +// Regular expressions +static const std::string VALID_BLOCKNAME_REGEX = "[A-Za-z][A-Za-z0-9]*"; +static const std::string VALID_BLOCKID_REGEX = "(?:(\\d+)(?:/))?([A-Za-z][A-Za-z0-9]*)(?:(?:_)(\\d\\d?))?"; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_CONSTANTS_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/ddc_block_ctrl.hpp b/host/include/uhd/rfnoc/ddc_block_ctrl.hpp new file mode 100644 index 000000000..d9dab3e71 --- /dev/null +++ b/host/include/uhd/rfnoc/ddc_block_ctrl.hpp @@ -0,0 +1,50 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_DDC_BLOCK_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_DDC_BLOCK_CTRL_HPP + +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief DDC block controller + * + * This block provides DSP for Rx operations. + * Its main component is a DDC chain, which can decimate over a wide range + * of decimation rates (using a CIC and halfband filters). + * + * It also includes a CORDIC component to shift signals in frequency. + */ +class UHD_RFNOC_API ddc_block_ctrl : + public source_block_ctrl_base, + public sink_block_ctrl_base, + public rate_node_ctrl, + public scalar_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(ddc_block_ctrl) + +}; /* class ddc_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_DDC_BLOCK_CTRL_HPP */ diff --git a/host/include/uhd/rfnoc/dma_fifo_block_ctrl.hpp b/host/include/uhd/rfnoc/dma_fifo_block_ctrl.hpp new file mode 100644 index 000000000..fe55fc678 --- /dev/null +++ b/host/include/uhd/rfnoc/dma_fifo_block_ctrl.hpp @@ -0,0 +1,55 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_DMA_FIFO_BLOCK_HPP +#define INCLUDED_LIBUHD_RFNOC_DMA_FIFO_BLOCK_HPP + +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Block controller for a DMA FIFO block. + * + * The DMA FIFO block has the following features: + * - One input- and output-port (type agnostic) + * - Configurable base address and FIFO depth + * - The base storage for the FIFO can be device + * specific. Usually it will be an off-chip SDRAM + * bank. + * + */ +class UHD_RFNOC_API dma_fifo_block_ctrl : public source_block_ctrl_base, public sink_block_ctrl_base +{ +public: + UHD_RFNOC_BLOCK_OBJECT(dma_fifo_block_ctrl) + + //! Configure the base address and depth of the FIFO (in bytes). + virtual void resize(const uint32_t base_addr, const uint32_t depth, const size_t chan) = 0; + + //! Returns the base address of the FIFO (in bytes). + uint32_t get_base_addr(const size_t chan) const; + + //! Returns the depth of the FIFO (in bytes). + uint32_t get_depth(const size_t chan) const; + +}; /* class dma_fifo_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_DMA_FIFO_BLOCK_HPP */ diff --git a/host/include/uhd/rfnoc/duc_block_ctrl.hpp b/host/include/uhd/rfnoc/duc_block_ctrl.hpp new file mode 100644 index 000000000..38c54aa31 --- /dev/null +++ b/host/include/uhd/rfnoc/duc_block_ctrl.hpp @@ -0,0 +1,51 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_DUC_BLOCK_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_DUC_BLOCK_CTRL_HPP + +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief DUC block controller + * + * This block provides DSP for Tx operations. + * Its main component is a DUC chain, which can interpolate over a wide range + * of interpolation rates (using a CIC and halfband filters). + * + * It also includes a CORDIC component to shift signals in frequency. + */ +class UHD_RFNOC_API duc_block_ctrl : + public source_block_ctrl_base, + public sink_block_ctrl_base, + public rate_node_ctrl, + public scalar_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(duc_block_ctrl) + +}; /* class duc_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_DUC_BLOCK_CTRL_HPP */ + diff --git a/host/include/uhd/rfnoc/graph.hpp b/host/include/uhd/rfnoc/graph.hpp new file mode 100644 index 000000000..8f9005d12 --- /dev/null +++ b/host/include/uhd/rfnoc/graph.hpp @@ -0,0 +1,62 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_GRAPH_HPP +#define INCLUDED_LIBUHD_RFNOC_GRAPH_HPP + +#include <boost/noncopyable.hpp> +#include <uhd/rfnoc/block_id.hpp> + +namespace uhd { namespace rfnoc { + +class graph : boost::noncopyable +{ +public: + typedef boost::shared_ptr<uhd::rfnoc::graph> sptr; + + /*! Connect a RFNOC block with block ID \p src_block to another with block ID \p dst_block. + * + * This will: + * - Check if this connection is valid (IO signatures, see if types match) + * - Configure the flow control for the blocks + * - Configure SID for the upstream block + * - Register the upstream block in the downstream block + */ + virtual void connect( + const block_id_t &src_block, + size_t src_block_port, + const block_id_t &dst_block, + size_t dst_block_port, + const size_t pkt_size = 0 + ) = 0; + + /*! Shorthand for connect(). + * + * Using default ports for both source and destination. + */ + virtual void connect( + const block_id_t &src_block, + const block_id_t &dst_block + ) = 0; + + virtual std::string get_name() const = 0; +}; + +}}; /* name space uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_GRAPH_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/node_ctrl_base.hpp b/host/include/uhd/rfnoc/node_ctrl_base.hpp new file mode 100644 index 000000000..82e095b1d --- /dev/null +++ b/host/include/uhd/rfnoc/node_ctrl_base.hpp @@ -0,0 +1,244 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_NODE_CTRL_BASE_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/utils/log.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/function.hpp> +#include <map> +#include <set> + +namespace uhd { + namespace rfnoc { + +#define UHD_RFNOC_BLOCK_TRACE() UHD_LOGV(never) << "[" << unique_id() << "] " + +/*! \brief Abstract base class for streaming nodes. + * + */ +class UHD_RFNOC_API node_ctrl_base; +class node_ctrl_base : boost::noncopyable, public boost::enable_shared_from_this<node_ctrl_base> +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<node_ctrl_base> sptr; + typedef boost::weak_ptr<node_ctrl_base> wptr; + typedef std::map< size_t, wptr > node_map_t; + typedef std::pair< size_t, wptr > node_map_pair_t; + + /*********************************************************************** + * Node control + **********************************************************************/ + //! Returns a unique string that identifies this block. + virtual std::string unique_id() const; + + /*********************************************************************** + * Connections + **********************************************************************/ + /*! Clears the list of connected nodes. + */ + virtual void clear(); + + node_map_t list_downstream_nodes() { return _downstream_nodes; }; + node_map_t list_upstream_nodes() { return _upstream_nodes; }; + + // TODO we need a more atomic connect procedure, this is too error-prone. + + /*! For an existing connection, store the remote port number. + * + * \throws uhd::value_error if \p this_port is not connected. + */ + void set_downstream_port(const size_t this_port, const size_t remote_port); + + /*! Return the remote port of a connection on a given port. + * + * \throws uhd::value_error if \p this_port is not connected. + */ + size_t get_downstream_port(const size_t this_port); + + /*! For an existing connection, store the remote port number. + * + * \throws uhd::value_error if \p this_port is not connected. + */ + void set_upstream_port(const size_t this_port, const size_t remote_port); + + /*! Return the remote port of a connection on a given port. + * + * \throws uhd::value_error if \p this_port is not connected. + */ + size_t get_upstream_port(const size_t this_port); + + /*! Find nodes downstream that match a predicate. + * + * Uses a non-recursive breadth-first search algorithm. + * On every branch, the search stops if a block matches. + * See this example: + * <pre> + * A -> B -> C -> C + * </pre> + * Say node A searches for nodes of type C. It will only find the + * first 'C' block, not the second. + * + * Returns blocks that are of type T. + * + * Search only goes downstream. + */ + template <typename T> + UHD_INLINE std::vector< boost::shared_ptr<T> > find_downstream_node() + { + return _find_child_node<T, true>(); + } + + /*! Same as find_downstream_node(), but only search upstream. + */ + template <typename T> + UHD_INLINE std::vector< boost::shared_ptr<T> > find_upstream_node() + { + return _find_child_node<T, false>(); + } + + /*! Checks if downstream nodes share a common, unique property. + * + * This will use find_downstream_node() to find all nodes downstream of + * this that are of type T. Then it will use \p get_property to return a + * property from all of them. If all these properties are identical, it will + * return that property. Otherwise, it will throw a uhd::runtime_error. + * + * \p get_property A functor to return the property from a node + * \p null_value If \p get_property returns this value, that node is skipped. + * \p explored_nodes A list of nodes to exclude from the search. This is typically + * to avoid recursion loops. + */ + template <typename T, typename value_type> + UHD_INLINE value_type find_downstream_unique_property( + boost::function<value_type(boost::shared_ptr<T> node, size_t port)> get_property, + value_type null_value, + const std::set< boost::shared_ptr<T> > &exclude_nodes=std::set< boost::shared_ptr<T> >() + ) { + return _find_unique_property<T, value_type, true>(get_property, null_value, exclude_nodes); + } + + /*! Like find_downstream_unique_property(), but searches upstream. + */ + template <typename T, typename value_type> + UHD_INLINE value_type find_upstream_unique_property( + boost::function<value_type(boost::shared_ptr<T> node, size_t port)> get_property, + value_type null_value, + const std::set< boost::shared_ptr<T> > &exclude_nodes=std::set< boost::shared_ptr<T> >() + ) { + return _find_unique_property<T, value_type, false>(get_property, null_value, exclude_nodes); + } + +protected: + /*********************************************************************** + * Structors + **********************************************************************/ + node_ctrl_base(void) {}; + virtual ~node_ctrl_base() {}; + + /*********************************************************************** + * Protected members + **********************************************************************/ + + //! Stores default arguments + uhd::device_addr_t _args; + + // TODO make these private + + //! List of upstream nodes + node_map_t _upstream_nodes; + + //! List of downstream nodes + node_map_t _downstream_nodes; + + /*********************************************************************** + * Connections + **********************************************************************/ + /*! Registers another node as downstream of this node, connected to a given port. + * + * This implies that this node is a source node, and the downstream node is + * a sink node. + * See also uhd::rfnoc::source_node_ctrl::_register_downstream_node(). + */ + virtual void _register_downstream_node( + node_ctrl_base::sptr downstream_node, + size_t port + ); + + /*! Registers another node as upstream of this node, connected to a given port. + * + * This implies that this node is a sink node, and the upstream node is + * a source node. + * See also uhd::rfnoc::sink_node_ctrl::_register_upstream_node(). + */ + virtual void _register_upstream_node( + node_ctrl_base::sptr upstream_node, + size_t port + ); + +private: + /*! Implements the search algorithm for find_downstream_node() and + * find_upstream_node(). + * + * Depending on \p downstream, "child nodes" are either defined as + * nodes connected downstream or upstream. + * + * \param downstream Set to true if search goes downstream, false for upstream. + */ + template <typename T, bool downstream> + std::vector< boost::shared_ptr<T> > _find_child_node(); + + /*! Implements the search algorithm for find_downstream_unique_property() and + * find_upstream_unique_property(). + * + * Depending on \p downstream, "child nodes" are either defined as + * nodes connected downstream or upstream. + * + * \param downstream Set to true if search goes downstream, false for upstream. + */ + template <typename T, typename value_type, bool downstream> + value_type _find_unique_property( + boost::function<value_type(boost::shared_ptr<T>, size_t)> get_property, + value_type NULL_VALUE, + const std::set< boost::shared_ptr<T> > &exclude_nodes + ); + + /*! Stores the remote port number of a downstream connection. + */ + std::map<size_t, size_t> _upstream_ports; + + /*! Stores the remote port number of a downstream connection. + */ + std::map<size_t, size_t> _downstream_ports; + +}; /* class node_ctrl_base */ + +}} /* namespace uhd::rfnoc */ + +#include <uhd/rfnoc/node_ctrl_base.ipp> + +#endif /* INCLUDED_LIBUHD_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/node_ctrl_base.ipp b/host/include/uhd/rfnoc/node_ctrl_base.ipp new file mode 100644 index 000000000..136354cd2 --- /dev/null +++ b/host/include/uhd/rfnoc/node_ctrl_base.ipp @@ -0,0 +1,128 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Implements templated functions from node_ctrl_base.hpp + +#ifndef INCLUDED_LIBUHD_NODE_CTRL_BASE_IPP +#define INCLUDED_LIBUHD_NODE_CTRL_BASE_IPP + +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/shared_ptr.hpp> +#include <vector> + +namespace uhd { + namespace rfnoc { + + template <typename T, bool downstream> + std::vector< boost::shared_ptr<T> > node_ctrl_base::_find_child_node() + { + typedef boost::shared_ptr<T> T_sptr; + static const size_t MAX_ITER = 20; + size_t iters = 0; + // List of return values: + std::set< T_sptr > results_s; + // To avoid cycles: + std::set< sptr > explored; + // Initialize our search queue with ourself: + std::set< sptr > search_q; + search_q.insert(shared_from_this()); + std::set< sptr > next_q; + + while (iters++ < MAX_ITER) { + next_q.clear(); + BOOST_FOREACH(const sptr &this_node, search_q) { + // Add this node to the list of explored nodes + explored.insert(this_node); + // Create set of all child nodes of this_node that are not in explored: + std::set< sptr > next_nodes; + { + node_map_t all_next_nodes = downstream ? this_node->list_downstream_nodes() : this_node->list_upstream_nodes(); + for ( + node_map_t::iterator it = all_next_nodes.begin(); + it != all_next_nodes.end(); + ++it + ) { + sptr one_next_node = it->second.lock(); + if (not one_next_node or explored.count(one_next_node)) { + continue; + } + T_sptr next_node_sptr = boost::dynamic_pointer_cast<T>(one_next_node); + if (next_node_sptr) { + results_s.insert(next_node_sptr); + } else { + next_nodes.insert(one_next_node); + } + } + } + // Add all of these nodes to the next search queue + next_q.insert(next_nodes.begin(), next_nodes.end()); + } + // If next_q is empty, we've exhausted our graph + if (next_q.empty()) { + break; + } + // Re-init the search queue + search_q = next_q; + } + + std::vector< T_sptr > results(results_s.begin(), results_s.end()); + return results; + } + + template <typename T, typename value_type, bool downstream> + value_type node_ctrl_base::_find_unique_property( + boost::function<value_type(boost::shared_ptr<T>, size_t)> get_property, + value_type NULL_VALUE, + const std::set< boost::shared_ptr<T> > &exclude_nodes + ) { + std::vector< boost::shared_ptr<T> > descendant_rate_nodes = _find_child_node<T, downstream>(); + value_type ret_val = NULL_VALUE; + std::string first_node_id; + BOOST_FOREACH(const boost::shared_ptr<T> &node, descendant_rate_nodes) { + if (exclude_nodes.count(node)) { + continue; + } + // FIXME we need to know the port!!! + size_t port = ANY_PORT; // NOOO! this is wrong!!!! FIXME + value_type this_property = get_property(node, port); + if (this_property == NULL_VALUE) { + continue; + } + // We use the first property we find as reference + if (ret_val == NULL_VALUE) { + ret_val = this_property; + first_node_id = node->unique_id(); + continue; + } + // In all subsequent finds, we make sure the property is equal to the reference + if (this_property != ret_val) { + throw uhd::runtime_error( + str( + boost::format("Node %1% specifies %2%, node %3% specifies %4%") + % first_node_id % ret_val % node->unique_id() % this_property + ) + ); + } + } + return ret_val; + } + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_NODE_CTRL_BASE_IPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/radio_ctrl.hpp b/host/include/uhd/rfnoc/radio_ctrl.hpp new file mode 100644 index 000000000..1d7842051 --- /dev/null +++ b/host/include/uhd/rfnoc/radio_ctrl.hpp @@ -0,0 +1,205 @@ +// +// Copyright 2015-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_HPP + +#include <uhd/types/direction.hpp> +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <uhd/rfnoc/terminator_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Block controller for all RFNoC-based radio blocks + */ +class UHD_RFNOC_API radio_ctrl : + public source_block_ctrl_base, + public sink_block_ctrl_base, + public rate_node_ctrl, + public tick_node_ctrl, + public terminator_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(radio_ctrl) + + virtual ~radio_ctrl(){} + + /************************************************************************ + * API calls + ***********************************************************************/ + /*! Return the tick rate on all channels (rx and tx). + * + * \return The tick rate. + */ + virtual double get_rate() const = 0; + + /*! Set the tick/sample rate on all channels (rx and tx). + * + * Will coerce to the nearest possible rate and return the actual value. + */ + virtual double set_rate(double rate) = 0; + + /*! Return the selected TX antenna for channel \p chan. + * + * \return The selected antenna. + */ + virtual std::string get_tx_antenna(const size_t chan) /* const */ = 0; + + /*! Select RX antenna \p for channel \p chan. + * + * \throws uhd::value_error if \p ant is not a valid value. + */ + virtual void set_tx_antenna(const std::string &ant, const size_t chan) = 0; + + /*! Return the selected RX antenna for channel \p chan. + * + * \return The selected antenna. + */ + virtual std::string get_rx_antenna(const size_t chan) /* const */ = 0; + + /*! Select RX antenna \p for channel \p chan. + * + * \throws uhd::value_error if \p ant is not a valid value. + */ + virtual void set_rx_antenna(const std::string &ant, const size_t chan) = 0; + + /*! Return the current transmit LO frequency on channel \p chan. + * + * Note that the AD9361 only has one LO for all TX channels, and the + * \p chan parameter is thus only for API compatibility. + * + * \return The current LO frequency. + */ + virtual double get_tx_frequency(const size_t chan) /* const */ = 0; + + /*! Tune the TX LO for channel \p chan. + * + * This function will attempt to tune as close as possible, and return a + * coerced value of the actual tuning result. + * + * \param freq Frequency in Hz + * \param chan Channel to tune + * + * \return The actual LO frequency. + */ + virtual double set_tx_frequency(const double freq, size_t chan) = 0; + + /*! Return the current receive LO frequency on channel \p chan. + * + * \return The current LO frequency. + */ + virtual double get_rx_frequency(const size_t chan) /* const */ = 0; + + /*! Tune the RX LO for channel \p. + * + * This function will attempt to tune as close as possible, and return a + * coerced value of the actual tuning result. + * + * \return The actual LO frequency. + */ + virtual double set_rx_frequency(const double freq, const size_t chan) = 0; + + /*! Return the transmit gain on channel \p chan + * + * \return The actual gain value + */ + virtual double get_tx_gain(const size_t chan) = 0; + + /*! Set the transmit gain on channel \p chan + * + * This function will attempt to set the gain as close as possible, + * and return a coerced value of the actual gain value. + * + * \return The actual gain value + */ + virtual double set_tx_gain(const double gain, const size_t chan) = 0; + + /*! Return the transmit gain on channel \p chan + * + * \return The actual gain value + */ + virtual double get_rx_gain(const size_t chan) = 0; + + /*! Set the transmit gain on channel \p chan + * + * This function will attempt to set the gain as close as possible, + * and return a coerced value of the actual gain value. + * + * \return The actual gain value + */ + virtual double set_rx_gain(const double gain, const size_t chan) = 0; + + /*! Sets the time in the radio's timekeeper to the given value. + * + * Note that there is a non-deterministic delay between calling this + * function and the valung written to the register. For setting the + * time in alignment with a certain reference time, use + * set_time_next_pps(). + */ + virtual void set_time_now(const time_spec_t &time_spec) = 0; + + /*! Set the time registers at the next pps tick. + * + * The values will not be latched in until the pulse occurs. + * It is recommended that the user sleep(1) after calling to ensure + * that the time registers will be in a known state prior to use. + * + * Note: Because this call sets the time on the "next" pps, + * the seconds in the time spec should be current seconds + 1. + * + * \param time_spec the time to latch into the timekeeper + */ + virtual void set_time_next_pps(const time_spec_t &time_spec) = 0; + + /*! Get the current time in the timekeeper registers. + * + * Note that there is a non-deterministic delay between the time the + * register is read and the time the function value is returned. + * To get the time with respect to a tick edge, use get_time_last_pps(). + * + * \return A timespec representing current radio time + */ + virtual time_spec_t get_time_now() = 0; + + /*! Get the time when the last PPS pulse occurred. + * + * \return A timespec representing the last PPS + */ + virtual time_spec_t get_time_last_pps() = 0; + + /*! Given a frontend name, return the channel mapping. + * + * E.g.: For a TwinRX board, there's two frontends, '0' and '1', which + * map to channels 0 and 1 respectively. A BasicRX boards has alphabetical + * frontends (A, B) which map to channels differently. + */ + virtual size_t get_chan_from_dboard_fe(const std::string &fe, const uhd::direction_t dir) = 0; + + /*! The inverse function to get_chan_from_dboard_fe() + */ + virtual std::string get_dboard_fe_from_chan(const size_t chan, const uhd::direction_t dir) = 0; + +}; /* class radio_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_HPP */ diff --git a/host/include/uhd/rfnoc/rate_node_ctrl.hpp b/host/include/uhd/rfnoc/rate_node_ctrl.hpp new file mode 100644 index 000000000..580f86fe2 --- /dev/null +++ b/host/include/uhd/rfnoc/rate_node_ctrl.hpp @@ -0,0 +1,67 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RATE_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_RATE_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Sampling-rate-aware node control + * + * A "rate" node is a streaming node is a point in the flow graph + * that is aware of sampling rates. Such nodes include: + * - Radio Controls (these actually set a sampling rate) + * - Decimating FIR filters (their output rates depend on both + * incoming rates and their own settings, i.e. the decimation) + * - Streaming terminators (these need to know the sampling rates + * to configure the connected streamers) + */ +class UHD_RFNOC_API rate_node_ctrl; +class rate_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<rate_node_ctrl> sptr; + //! This value is used by rate nodes that don't actually set a rate themselves + static const double RATE_UNDEFINED; + + /*********************************************************************** + * Rate controls + **********************************************************************/ + /*! Returns the sampling rate this block expects at its input. + * + * A radio will simply return the sampling rate it is set to. + * A decimating FIR filter will ask downstream for the input sampling rate + * and then return that value multiplied by the decimation factor. + * + */ + virtual double get_input_samp_rate(size_t port=ANY_PORT); + virtual double get_output_samp_rate(size_t port=ANY_PORT); + +}; /* class rate_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RATE_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: + diff --git a/host/include/uhd/rfnoc/scalar_node_ctrl.hpp b/host/include/uhd/rfnoc/scalar_node_ctrl.hpp new file mode 100644 index 000000000..1b29f959e --- /dev/null +++ b/host/include/uhd/rfnoc/scalar_node_ctrl.hpp @@ -0,0 +1,74 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_SCALAR_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_SCALAR_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Scaling node control + * + * A "scalar" node is a streaming node in which a scaling takes + * place, usually for the conversion between fixed point and floating + * point (the latter usually being normalized between -1 and 1). + * + * Such blocks include: + * - Radio Controls + * - Potentially FFTs or FIRs, if they affect scaling + */ +class UHD_RFNOC_API scalar_node_ctrl; +class scalar_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<scalar_node_ctrl> sptr; + //! Undefined scaling + static const double SCALE_UNDEFINED; + + /*********************************************************************** + * Scaling controls + **********************************************************************/ + /*! Returns the scaling factor for this block on input. + * + * A DUC block will return the scaling factor as determined by the duc + * stage. + * + * \param port Port Number + */ + virtual double get_input_scale_factor(size_t port=ANY_PORT); + + /*! Returns the scaling factor for this block on output. + * + * A DDC block will return the scaling factor as determined by the ddc + * stage. + * + * \param port Port Number + */ + virtual double get_output_scale_factor(size_t port=ANY_PORT); + +}; /* class scalar_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_SCALAR_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/sink_block_ctrl_base.hpp b/host/include/uhd/rfnoc/sink_block_ctrl_base.hpp new file mode 100644 index 000000000..ebc2370f2 --- /dev/null +++ b/host/include/uhd/rfnoc/sink_block_ctrl_base.hpp @@ -0,0 +1,126 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TX_BLOCK_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_TX_BLOCK_CTRL_BASE_HPP + +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Extends block_ctrl_base with input capabilities. + * + * A sink block is an RFNoC block that can receive data at an input. + * We can use this block to transmit data (In RFNoC nomenclature, a + * transmit operation means streaming data to the device from the host). + * + * Every input is defined by a port definition (port_t). + */ +class UHD_RFNOC_API sink_block_ctrl_base; +class sink_block_ctrl_base : virtual public block_ctrl_base, virtual public sink_node_ctrl +{ +public: + typedef boost::shared_ptr<sink_block_ctrl_base> sptr; + + /*********************************************************************** + * Stream signatures + **********************************************************************/ + /*! Return the input stream signature for a given block port. + * + * The actual signature is determined by the current configuration + * and the block definition file. The value returned here is calculated + * on-the-fly and is only valid as long as the configuration does not + * change. + * + * \returns The stream signature for port \p block_port + * \throws uhd::runtime_error if \p block_port is not a valid port + */ + stream_sig_t get_input_signature(size_t block_port=0) const; + + /*! Return a list of valid input ports. + */ + std::vector<size_t> get_input_ports() const; + + /*********************************************************************** + * FPGA Configuration + **********************************************************************/ + /*! Return the size of input buffer on a given block port. + * + * This is necessary for setting up flow control, among other things. + * Note: This does not query the block's settings register. The FIFO size + * is queried once during construction and cached. + * + * If the block port is not defined, it will return 0, and not throw. + * + * \param block_port The block port (0 through 15). + * + * Returns the size of the buffer in bytes. + */ + size_t get_fifo_size(size_t block_port=0) const; + + /*! Configure flow control for incoming streams. + * + * If flow control is enabled for incoming streams, this block will periodically + * send out ACKs, telling the upstream block which packets have been consumed, + * so the upstream block can increase his flow control credit. + * + * In the default implementation, this just sets registers + * SR_FLOW_CTRL_CYCS_PER_ACK and SR_FLOW_CTRL_PKTS_PER_ACK accordingly. + * + * Override this function if your block has port-specific flow control settings. + * + * \param cycles Send an ACK after this many clock cycles. + * Setting this to zero disables this type of flow control acknowledgement. + * \param packets Send an ACK after this many packets have been consumed. + * Setting this to zero disables this type of flow control acknowledgement. + * \param block_port Set up flow control for a stream coming in on this particular block port. + */ + virtual void configure_flow_control_in( + size_t cycles, + size_t packets, + size_t block_port=0 + ); + + /*! Configure the behaviour for errors on incoming packets + * (e.g. sequence errors). + * + * + */ + virtual void set_error_policy( + const std::string &policy + ); + +protected: + /*********************************************************************** + * Hooks + **********************************************************************/ + /*! Like sink_node_ctrl::_request_input_port(), but also checks + * the port has an input signature. + */ + virtual size_t _request_input_port( + const size_t suggested_port, + const uhd::device_addr_t &args + ) const; + +}; /* class sink_block_ctrl_base */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_TX_BLOCK_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/sink_node_ctrl.hpp b/host/include/uhd/rfnoc/sink_node_ctrl.hpp new file mode 100644 index 000000000..5142a269e --- /dev/null +++ b/host/include/uhd/rfnoc/sink_node_ctrl.hpp @@ -0,0 +1,145 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_SINK_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_SINK_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <boost/thread.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Abstract class for sink nodes. + * + * Sink nodes can have upstream blocks. + */ +class UHD_RFNOC_API sink_node_ctrl; +class sink_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<sink_node_ctrl> sptr; + typedef std::map< size_t, boost::weak_ptr<sink_node_ctrl> > node_map_t; + typedef std::pair< size_t, boost::weak_ptr<sink_node_ctrl> > node_map_pair_t; + + /*********************************************************************** + * Sink block controls + **********************************************************************/ + /*! Connect another node upstream of this node. + * + * *Note:* If additional settings are required to make this connection work, + * e.g. configure flow control, these need to be done separately. + * + * If the requested connection is not possible, this function will throw. + * + * \p upstream_node Pointer to the node class to connect + * \p port Suggested port number on this block to connect the upstream + * block to. + * \p args Any arguments that can be useful for determining the port number. + * + * \returns The actual port number used. + */ + size_t connect_upstream( + node_ctrl_base::sptr upstream_node, + size_t port=ANY_PORT, + const uhd::device_addr_t &args=uhd::device_addr_t() + ); + + /*! Call this function to notify a node about its streamer activity. + * + * When \p active is set to true, this means this block is now part of + * an active tx streamer chain. Conversely, when set to false, this means + * the node has been removed from an tx streamer chain. + */ + virtual void set_tx_streamer(bool active, const size_t port); + + +protected: + + /*! For every input port, store tx streamer activity. + * + * If _tx_streamer_active[0] == true, this means that an active tx + * streamer is operating on port 0. If it is false, or if the entry + * does not exist, there is no streamer. + * Values are toggled by set_tx_streamer(). + */ + std::map<size_t, bool> _tx_streamer_active; + + /*! Ask for a port number to connect an upstream block to. + * + * Typically, this will be overridden for custom behaviour. + * The default is to return the suggested port, disregarding + * \p args, unless \p port == ANY_PORT, in which case the first + * unused input port is returned. + * + * When deriving this function for custom behaviour, consider: + * - The result is used to call register_upstream_node(), which + * has its own checks in place. + * - This function may throw if the arguments can't be resolved. + * The exception will propagate to the user space. + * - Alternatively, the function may return ANY_PORT to signify + * failure. + * - \p args and \p suggested_port should be treated as strong + * suggestions, but there's no reason to just return any valid + * port. + * + * *Note:* For reasons of thread safety, it is recommended to + * never, ever call this function directly. It will be used by + * connect_upstream() which will handle the connection process + * in a thread-safe manner. + * + * \param suggested_port Try and connect here. + * \param args When deciding on a port number, these arguments may be used. + * + * \returns A valid input port, or ANY_PORT on failure. + */ + virtual size_t _request_input_port( + const size_t suggested_port, + const uhd::device_addr_t &args + ) const; + +private: + /*! Makes connecting something to the input thread-safe. + */ + boost::mutex _input_mutex; + + /*! Register a node upstream of this one (i.e., a node that can send data to this node). + * + * By definition, the upstream node must of type source_node_ctrl. + * + * This saves a *weak pointer* to the upstream node and checks the port is + * available. Will throw otherwise. + * + * \param upstream_node A pointer to the node instantiation + * \param port Port number the upstream node is connected to + */ + void _register_upstream_node( + node_ctrl_base::sptr upstream_node, + size_t port + ); + +}; /* class sink_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_SINK_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/source_block_ctrl_base.hpp b/host/include/uhd/rfnoc/source_block_ctrl_base.hpp new file mode 100644 index 000000000..02882307c --- /dev/null +++ b/host/include/uhd/rfnoc/source_block_ctrl_base.hpp @@ -0,0 +1,140 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RX_BLOCK_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_RX_BLOCK_CTRL_BASE_HPP + +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Extends block_ctrl_base with receive capabilities. + * + * In RFNoC nomenclature, a receive operation means streaming + * data from the device (the crossbar) to the host. + * If a block has receive capabilities, this means we can receive + * data *from* this block. + */ +class UHD_RFNOC_API source_block_ctrl_base; +class source_block_ctrl_base : virtual public block_ctrl_base, virtual public source_node_ctrl +{ +public: + typedef boost::shared_ptr<source_block_ctrl_base> sptr; + + /*********************************************************************** + * Streaming operations + **********************************************************************/ + /*! Issue a stream command for this block. + * + * There is no guaranteed action for this command. The default implementation + * is to send this command to the next upstream block, or issue a warning if + * there is no upstream block registered. + * + * However, implementations of block_ctrl_base might choose to do whatever seems + * appropriate, including throwing exceptions. This may also be true for some + * stream commands and not for others (i.e. STREAM_MODE_START_CONTINUOUS may be + * implemented, and STREAM_MODE_NUM_SAMPS_AND_DONE may be not). + * + * This function does not check for infinite loops. Example: Say you have two blocks, + * which are both registered as upstream from one another. If they both use + * block_ctrl_base::issue_stream_cmd(), then the stream command will be passed from + * one block to another indefinitely. This will not happen if one the block's + * controller classes overrides this function and actually handles it. + * + * See also register_upstream_block(). + * + * \param stream_cmd The stream command. + */ + virtual void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t chan=0); + + /*********************************************************************** + * Stream signatures + **********************************************************************/ + /*! Return the output stream signature for a given block port. + * + * The actual signature is determined by the current configuration + * and the block definition file. The value returned here is calculated + * on-the-fly and is only valid as long as the configuration does not + * change. + * + * \returns The stream signature for port \p block_port + * \throws uhd::runtime_error if \p block_port is not a valid port + */ + stream_sig_t get_output_signature(size_t block_port=0) const; + + /*! Return a list of valid output ports. + */ + std::vector<size_t> get_output_ports() const; + + /*********************************************************************** + * FPGA Configuration + **********************************************************************/ + /*! Configures data flowing from port \p output_block_port to go to \p next_address + * + * \param next_address Address of the downstream block + * \param output_block_port Port for which this is valid + * + * In the default implementation, this will write the value in \p next_address + * to register SR_NEXT_DST of this blocks settings bus. The value will also + * have bit 16 set to 1, since some blocks require this to respect this value. + */ + virtual void set_destination( + boost::uint32_t next_address, + size_t output_block_port = 0 + ); + + /*! Configure flow control for outgoing streams. + * + * In the default implementation, this just sets registers SR_FLOW_CTRL_BUF_SIZE + * and SR_FLOW_CTRL_ENABLE accordingly; \b block_port and \p sid are ignored. + * + * Override this function if your block has port-specific flow control settings. + * + * \param buf_size_pkts The size of the downstream block's input FIFO size in number of packets. Setting + * this to zero disables flow control. The block will then produce data as fast as it can. + * \b Warning: This can cause head-of-line blocking, and potentially lock up your device! + * \param Specify on which outgoing port this setting is valid. + * \param sid The SID for which this is valid. This is meant for cases where the outgoing block port is + * not sufficient to set the flow control, and as such is rarely used. + */ + virtual void configure_flow_control_out( + size_t buf_size_pkts, + size_t block_port=0, + const uhd::sid_t &sid=uhd::sid_t() + ); + + +protected: + /*********************************************************************** + * Hooks + **********************************************************************/ + /*! Like source_node_ctrl::_request_output_port(), but also checks if + * the port has an output signature. + */ + virtual size_t _request_output_port( + const size_t suggested_port, + const uhd::device_addr_t &args + ) const; + +}; /* class source_block_ctrl_base */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RX_BLOCK_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/source_node_ctrl.hpp b/host/include/uhd/rfnoc/source_node_ctrl.hpp new file mode 100644 index 000000000..a351f6c8e --- /dev/null +++ b/host/include/uhd/rfnoc/source_node_ctrl.hpp @@ -0,0 +1,136 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_SOURCE_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_SOURCE_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <boost/thread.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Abstract class for source nodes. + * + * Source nodes can have downstream blocks. + */ +class UHD_RFNOC_API source_node_ctrl; +class source_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<source_node_ctrl> sptr; + typedef std::map< size_t, boost::weak_ptr<source_node_ctrl> > node_map_t; + typedef std::pair< size_t, boost::weak_ptr<source_node_ctrl> > node_map_pair_t; + + /*********************************************************************** + * Source block controls + **********************************************************************/ + /*! Issue a stream command for this block. + * \param stream_cmd The stream command. + * \param chan Channel Index + */ + virtual void issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd, + const size_t chan=0 + ) = 0; + + /*! Connect another node downstream of this node. + * + * *Note:* If additional settings are required to make this connection work, + * e.g. configure flow control, these need to be done separately. + * + * If the requested connection is not possible, this function will throw. + * + * \p downstream_node Pointer to the node class to connect + * \p port Suggested port number on this block to connect the downstream + * block to. + * \p args Any arguments that can be useful for determining the port number. + * + * \returns The actual port number used. + */ + size_t connect_downstream( + node_ctrl_base::sptr downstream_node, + size_t port=ANY_PORT, + const uhd::device_addr_t &args=uhd::device_addr_t() + ); + + /*! Call this function to notify a node about its streamer activity. + * + * When \p active is set to true, this means this block is now part of + * an active rx streamer chain. Conversely, when set to false, this means + * the node has been removed from an rx streamer chain. + */ + virtual void set_rx_streamer(bool active, const size_t port); + +protected: + + /*! For every output port, store rx streamer activity. + * + * If _rx_streamer_active[0] == true, this means that an active rx + * streamer is operating on port 0. If it is false, or if the entry + * does not exist, there is no streamer. + * Values are toggled by set_rx_streamer(). + */ + std::map<size_t, bool> _rx_streamer_active; + + /*! Ask for a port number to connect a downstream block to. + * + * See sink_node_ctrl::_request_input_port(). This is the same + * for output. + * + * \param suggested_port Try and connect here. + * \param args When deciding on a port number, these arguments may be used. + * + * \returns A valid input port, or ANY_PORT on failure. + */ + virtual size_t _request_output_port( + const size_t suggested_port, + const uhd::device_addr_t &args + ) const; + + +private: + /*! Makes connecting something to the output thread-safe. + */ + boost::mutex _output_mutex; + + /*! Register a node downstream of this one (i.e., a node that receives data from this node). + * + * By definition, the upstream node must of type sink_node_ctrl. + * + * This saves a *weak pointer* to the downstream node and checks + * the port is available. Will throw otherwise. + * + * \param downstream_node A pointer to the node instantiation + * \param port Port number the downstream node is connected to + */ + void _register_downstream_node( + node_ctrl_base::sptr downstream_node, + size_t port + ); + +}; /* class source_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_SOURCE_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/stream_sig.hpp b/host/include/uhd/rfnoc/stream_sig.hpp new file mode 100644 index 000000000..28f2efbe7 --- /dev/null +++ b/host/include/uhd/rfnoc/stream_sig.hpp @@ -0,0 +1,88 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_STREAMSIG_HPP +#define INCLUDED_LIBUHD_RFNOC_STREAMSIG_HPP + +#include <iostream> +#include <uhd/config.hpp> + +namespace uhd { namespace rfnoc { + +/*! Describes a stream signature for data going to or coming from + * RFNoC ports. + * + * The stream signature may depend on a block's configuration. Even + * so, some attributes may be left undefined (e.g., a FIFO block + * works for any item type, so it doesn't need to set it). + */ +class UHD_RFNOC_API stream_sig_t { + public: + /*********************************************************************** + * Structors + ***********************************************************************/ + stream_sig_t(); + + /*********************************************************************** + * The stream signature attributes + ***********************************************************************/ + //! The data type of the individual items (e.g. 'sc16'). If undefined, set + // to empty. + std::string item_type; + + //! The vector length in multiples of items. If undefined, set to zero. + size_t vlen; + + //! Packet size in bytes. If undefined, set to zero. + size_t packet_size; + + bool is_bursty; + + /*********************************************************************** + * Helpers + ***********************************************************************/ + //! Compact string representation + std::string to_string(); + //! Pretty-print string representation + std::string to_pp_string(); + + //! Returns the number of bytes necessary to store one item. + // Note: The vector length is *not* considered here. + // + // \returns Number of bytes per item or 0 if the item type is + // undefined. + // \throws uhd::key_error if the item type is invalid. + size_t get_bytes_per_item() const; + + /*! Check if an output with signature \p output_sig could + * stream to an input signature \p input_sig. + * + * \return true if streams are compatible + */ + static bool is_compatible(const stream_sig_t &output_sig, const stream_sig_t &input_sig); +}; + +//! Shortcut for << stream_sig.to_string() +UHD_INLINE std::ostream& operator<< (std::ostream& out, stream_sig_t stream_sig) { + out << stream_sig.to_string().c_str(); + return out; +} + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_STREAMSIG_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/terminator_node_ctrl.hpp b/host/include/uhd/rfnoc/terminator_node_ctrl.hpp new file mode 100644 index 000000000..3cc4d0cb3 --- /dev/null +++ b/host/include/uhd/rfnoc/terminator_node_ctrl.hpp @@ -0,0 +1,53 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TERMINATOR_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_TERMINATOR_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Abstract class for terminator nodes (i.e. nodes that terminate + * the flow graph). + * + * Terminator nodes have the following properties: + * - Data flowing into such a node is not propagated to any other node, and + * data coming out of this node originates in this node. + * - Chain commands are not propagated past this node. + * + * A block may be a terminator node, but have both upstream and downstream + * nodes. An example is the radio block, which can be used for Rx and Tx. + * Even if it's used for both, the data going into the radio block is not + * the data coming out. + */ +class UHD_RFNOC_API terminator_node_ctrl; +class terminator_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<terminator_node_ctrl> sptr; + +}; /* class terminator_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_TERMINATOR_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/tick_node_ctrl.hpp b/host/include/uhd/rfnoc/tick_node_ctrl.hpp new file mode 100644 index 000000000..3f52bbad7 --- /dev/null +++ b/host/include/uhd/rfnoc/tick_node_ctrl.hpp @@ -0,0 +1,70 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TICK_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_TICK_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Tick-rate-aware node control + * + * A "rate" node is a streaming node is a point in the flow graph + * that is aware of tick rates (time base). Such nodes include: + * - Radio Controls + * - Data generating blocks that add time stamps + */ +class UHD_RFNOC_API tick_node_ctrl; +class tick_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<tick_node_ctrl> sptr; + + /*********************************************************************** + * Constants + **********************************************************************/ + //! This value is used by rate nodes that don't actually set a rate themselves + static const double RATE_UNDEFINED; + + /*********************************************************************** + * Rate controls + **********************************************************************/ + /*! Return a tick rate. + * + * This might be either a tick rate defined by this block (see also _get_tick_rate()) + * or it's a tick rate defined by an adjacent block. + * In that case, performs a graph search to figure out the tick rate. + */ + double get_tick_rate( + const std::set< node_ctrl_base::sptr > &_explored_nodes=std::set< node_ctrl_base::sptr >() + ); + +protected: + virtual double _get_tick_rate() { return RATE_UNDEFINED; }; + +}; /* class tick_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_TICK_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/transport/muxed_zero_copy_if.hpp b/host/include/uhd/transport/muxed_zero_copy_if.hpp new file mode 100644 index 000000000..17213885e --- /dev/null +++ b/host/include/uhd/transport/muxed_zero_copy_if.hpp @@ -0,0 +1,72 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_MUXED_ZERO_COPY_IF_HPP +#define INCLUDED_LIBUHD_TRANSPORT_MUXED_ZERO_COPY_IF_HPP + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/config.hpp> +#include <boost/function.hpp> +#include <boost/noncopyable.hpp> +#include <stdint.h> + +namespace uhd { namespace transport { + +/*! + * Implements a software muxed-demuxed zero-copy transport + * This is a wrapper around a base transport that allows + * creation of virtual zero_copy_if streams that are + * indistinguishable from physical transport streams. + * This class handles demuxing receive streams into the + * appropriate virtual streams with the given classifier + * function. A worker therad is spawned to handle the demuxing. + */ +class muxed_zero_copy_if : private boost::noncopyable { +public: + typedef boost::shared_ptr<muxed_zero_copy_if> sptr; + + /*! + * Function to classify the stream based on the payload. + * The classifier must return a stream number, an arbitrary + * identifier for a virtual stream that is consistent with + * the stream number used in the make_stream and remove_stream + * fuctions + * \param buff a pointer to the payload of the frame + * \param size number of bytes in the frame payload + * \return stream number + */ + typedef boost::function<uint32_t(void* buff, size_t size)> stream_classifier_fn; + + //! virtual dtor + virtual ~muxed_zero_copy_if() {} + + //! Make a virtual transport for the specified stream number + virtual zero_copy_if::sptr make_stream(const uint32_t stream_num) = 0; + + //! Unregister the stream number. All packets destined to the stream will be dropped. + virtual void remove_stream(const uint32_t stream_num) = 0; + + //! Get number of frames dropped due to unregistered streams + virtual size_t get_num_dropped_frames() const = 0; + + //! Make a new demuxer from a transport and parameters + static sptr make(zero_copy_if::sptr base_xport, stream_classifier_fn classify_fn, size_t max_streams); +}; + +}} //namespace uhd::transport + +#endif /* INCLUDED_LIBUHD_TRANSPORT_MUXED_ZERO_COPY_IF_HPP */ diff --git a/host/include/uhd/transport/usb_control.hpp b/host/include/uhd/transport/usb_control.hpp index 23f35d7e8..4576d6e92 100644 --- a/host/include/uhd/transport/usb_control.hpp +++ b/host/include/uhd/transport/usb_control.hpp @@ -26,7 +26,7 @@ class UHD_API usb_control : boost::noncopyable { public: typedef boost::shared_ptr<usb_control> sptr; - virtual ~usb_control(void) = 0; + virtual ~usb_control(void); /*! * Create a new USB control transport: @@ -36,7 +36,7 @@ public: * \param handle a device handle that uniquely identifies a USB device * \param interface the USB interface number for the control transport */ - static sptr make(usb_device_handle::sptr handle, const size_t interface); + static sptr make(usb_device_handle::sptr handle, const int interface); /*! * Submit a USB device request: @@ -56,13 +56,13 @@ public: * \param timeout 4-byte (timeout, default is infinite wait) * \return number of bytes submitted or error code */ - virtual ssize_t submit(boost::uint8_t request_type, - boost::uint8_t request, - boost::uint16_t value, - boost::uint16_t index, - unsigned char *buff, - boost::uint16_t length, - boost::int32_t timeout = 0) = 0; + virtual int submit(boost::uint8_t request_type, + boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length, + boost::uint32_t timeout = 0) = 0; }; }} //namespace diff --git a/host/include/uhd/transport/usb_device_handle.hpp b/host/include/uhd/transport/usb_device_handle.hpp index bf122f549..a8bbfc965 100644 --- a/host/include/uhd/transport/usb_device_handle.hpp +++ b/host/include/uhd/transport/usb_device_handle.hpp @@ -43,6 +43,8 @@ public: typedef boost::shared_ptr<usb_device_handle> sptr; typedef std::pair<boost::uint16_t, boost::uint16_t> vid_pid_pair_t; + virtual ~usb_device_handle(void); + /*! * Return the device's serial number * \return a string describing the device's serial number diff --git a/host/include/uhd/transport/usb_zero_copy.hpp b/host/include/uhd/transport/usb_zero_copy.hpp index ae1926e1b..092873803 100644 --- a/host/include/uhd/transport/usb_zero_copy.hpp +++ b/host/include/uhd/transport/usb_zero_copy.hpp @@ -38,6 +38,8 @@ class UHD_API usb_zero_copy : public virtual zero_copy_if { public: typedef boost::shared_ptr<usb_zero_copy> sptr; + virtual ~usb_zero_copy(void); + /*! * Make a new zero copy USB transport: * This transport is for sending and receiving between the host @@ -55,10 +57,10 @@ public: */ static sptr make( usb_device_handle::sptr handle, - const size_t recv_interface, - const size_t recv_endpoint, - const size_t send_interface, - const size_t send_endpoint, + const int recv_interface, + const unsigned char recv_endpoint, + const int send_interface, + const unsigned char send_endpoint, const device_addr_t &hints = device_addr_t() ); }; diff --git a/host/include/uhd/transport/zero_copy_recv_offload.hpp b/host/include/uhd/transport/zero_copy_recv_offload.hpp new file mode 100644 index 000000000..793753276 --- /dev/null +++ b/host/include/uhd/transport/zero_copy_recv_offload.hpp @@ -0,0 +1,50 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_ZERO_COPY_RECV_OFFLOAD_HPP +#define INCLUDED_UHD_ZERO_COPY_RECV_OFFLOAD_HPP + +#include <uhd/config.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/shared_ptr.hpp> + +namespace uhd{ namespace transport{ + +/*! + * A threaded transport offload that is meant to relieve the main thread of + * the responsibility of making receive calls. + */ +class UHD_API zero_copy_recv_offload : public virtual zero_copy_if { +public: + typedef boost::shared_ptr<zero_copy_recv_offload> sptr; + + /*! + * This transport offload adds a receive thread in order to + * communicate with the underlying transport. It is meant to be + * used in cases where the main thread needs to be relieved of the burden + * of the underlying transport receive calls. + * + * \param transport a shared pointer to the transport interface + * \param timeout a general timeout for pushing and pulling on the bounded buffer + */ + static sptr make(zero_copy_if::sptr transport, + const double timeout); +}; + +}} //namespace + +#endif /* INCLUDED_ZERO_COPY_OFFLOAD_HPP */ diff --git a/host/include/uhd/types/CMakeLists.txt b/host/include/uhd/types/CMakeLists.txt index 3f34782e2..682d3cd9b 100644 --- a/host/include/uhd/types/CMakeLists.txt +++ b/host/include/uhd/types/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2011,2015 Ettus Research LLC +# Copyright 2010-2011,2015-2016 Ettus Research LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/host/include/uhd/types/filters.hpp b/host/include/uhd/types/filters.hpp index 976ae233d..2c30c1007 100644 --- a/host/include/uhd/types/filters.hpp +++ b/host/include/uhd/types/filters.hpp @@ -55,12 +55,12 @@ namespace uhd{ //NOP } - inline virtual bool is_bypassed() + UHD_INLINE virtual bool is_bypassed() { return _bypass; } - inline filter_type get_type() + UHD_INLINE filter_type get_type() { return _type; } @@ -98,7 +98,7 @@ namespace uhd{ //NOP } - inline const std::string& get_analog_type() + UHD_INLINE const std::string& get_analog_type() { return _analog_type; } @@ -128,17 +128,17 @@ namespace uhd{ //NOP } - inline double get_cutoff() + UHD_INLINE double get_cutoff() { return _cutoff; } - inline double get_rolloff() + UHD_INLINE double get_rolloff() { return _cutoff; } - inline void set_cutoff(const double cutoff) + UHD_INLINE void set_cutoff(const double cutoff) { _cutoff = cutoff; } @@ -181,32 +181,32 @@ namespace uhd{ //NOP } - inline double get_output_rate() + UHD_INLINE double get_output_rate() { return (_bypass ? _rate : (_rate / _decimation * _interpolation)); } - inline double get_input_rate() + UHD_INLINE double get_input_rate() { return _rate; } - inline double get_interpolation() + UHD_INLINE double get_interpolation() { return _interpolation; } - inline double get_decimation() + UHD_INLINE double get_decimation() { return _decimation; } - inline double get_tap_full_scale() + UHD_INLINE double get_tap_full_scale() { return _tap_full_scale; } - inline std::vector<tap_t>& get_taps() + UHD_INLINE std::vector<tap_t>& get_taps() { return _taps; } diff --git a/host/include/uhd/types/sensors.hpp b/host/include/uhd/types/sensors.hpp index 529e1e3e3..de1ed014f 100644 --- a/host/include/uhd/types/sensors.hpp +++ b/host/include/uhd/types/sensors.hpp @@ -91,6 +91,12 @@ namespace uhd{ const std::string &unit ); + /*! + * Create a sensor value from another sensor value. + * \param source the source sensor value to copy + */ + sensor_value_t(const sensor_value_t& source); + //! convert the sensor value to a boolean bool to_bool(void) const; @@ -101,21 +107,21 @@ namespace uhd{ double to_real(void) const; //! The name of the sensor value - const std::string name; + std::string name; /*! * The sensor value as a string. * For integer and real number types, this will be the output of the formatter. * For boolean types, the value will be the string literal "true" or "false". */ - const std::string value; + std::string value; /*! * The sensor value's unit type. * For boolean types, this will be the one of the two units * depending upon the value of the boolean true or false. */ - const std::string unit; + std::string unit; //! Enumeration of possible data types in a sensor enum data_type_t { @@ -126,10 +132,13 @@ namespace uhd{ }; //! The data type of the value - const data_type_t type; + data_type_t type; //! Convert this sensor value into a printable string std::string to_pp_string(void) const; + + //! Assignment operator for sensor value + sensor_value_t& operator=(const sensor_value_t& value); }; } //namespace uhd diff --git a/host/include/uhd/types/serial.hpp b/host/include/uhd/types/serial.hpp index 7b565c633..5b7f34fbd 100644 --- a/host/include/uhd/types/serial.hpp +++ b/host/include/uhd/types/serial.hpp @@ -118,13 +118,19 @@ namespace uhd{ //! on what edge is the miso data valid? edge_t miso_edge; + //! Set the clock speed for this transaction + bool use_custom_divider; + + //! Optionally set the SPI clock divider for this transaction + size_t divider; + /*! * Create a new spi config. * \param edge the default edge for mosi and miso */ spi_config_t(edge_t edge = EDGE_RISE); }; - + /*! * The SPI interface class. * Provides routines to transact SPI and do other useful things which haven't been defined yet. diff --git a/host/include/uhd/usrp/CMakeLists.txt b/host/include/uhd/usrp/CMakeLists.txt index e974f808d..7a7dd02f4 100644 --- a/host/include/uhd/usrp/CMakeLists.txt +++ b/host/include/uhd/usrp/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2011,2015 Ettus Research LLC +# Copyright 2010-2011,2014-2015 Ettus Research LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ UHD_INSTALL(FILES #### dboard headers ### + fe_connection.hpp dboard_base.hpp dboard_eeprom.hpp dboard_id.hpp @@ -26,6 +27,7 @@ UHD_INSTALL(FILES ### utilities ### gps_ctrl.hpp + gpio_defs.hpp mboard_eeprom.hpp subdev_spec.hpp diff --git a/host/include/uhd/usrp/dboard_base.hpp b/host/include/uhd/usrp/dboard_base.hpp index 31b3643c7..b106a1ac6 100644 --- a/host/include/uhd/usrp/dboard_base.hpp +++ b/host/include/uhd/usrp/dboard_base.hpp @@ -44,6 +44,10 @@ public: //structors dboard_base(ctor_args_t); + virtual ~dboard_base() {} + + //post-construction initializer + virtual void initialize() {} protected: std::string get_subdev_name(void); @@ -67,6 +71,7 @@ public: * Create a new xcvr dboard object, override in subclasses. */ xcvr_dboard_base(ctor_args_t); + virtual ~xcvr_dboard_base() {} }; /*! @@ -79,6 +84,7 @@ public: * Create a new rx dboard object, override in subclasses. */ rx_dboard_base(ctor_args_t); + virtual ~rx_dboard_base() {} }; /*! @@ -91,6 +97,7 @@ public: * Create a new rx dboard object, override in subclasses. */ tx_dboard_base(ctor_args_t); + virtual ~tx_dboard_base() {} }; }} //namespace diff --git a/host/include/uhd/usrp/dboard_iface.hpp b/host/include/uhd/usrp/dboard_iface.hpp index 686deb48d..7c730f59d 100644 --- a/host/include/uhd/usrp/dboard_iface.hpp +++ b/host/include/uhd/usrp/dboard_iface.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013 Ettus Research LLC +// Copyright 2010-2013,2015-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,8 +22,11 @@ #include <uhd/utils/pimpl.hpp> #include <uhd/types/serial.hpp> #include <uhd/types/time_spec.hpp> +#include <uhd/usrp/fe_connection.hpp> +#include <uhd/usrp/gpio_defs.hpp> #include <boost/shared_ptr.hpp> #include <boost/cstdint.hpp> +#include <boost/thread/thread.hpp> #include <string> #include <vector> @@ -63,16 +66,9 @@ public: //! tells the host which unit to use enum unit_t{ - UNIT_RX = int('r'), - UNIT_TX = int('t') - }; - - //! possible atr registers - enum atr_reg_t{ - ATR_REG_IDLE = int('i'), - ATR_REG_TX_ONLY = int('t'), - ATR_REG_RX_ONLY = int('r'), - ATR_REG_FULL_DUPLEX = int('f') + UNIT_RX = int('r'), + UNIT_TX = int('t'), + UNIT_BOTH = int('b'), }; //! aux dac selection enums (per unit) @@ -89,6 +85,10 @@ public: AUX_ADC_B = int('b') }; + typedef uhd::usrp::gpio_atr::gpio_atr_reg_t atr_reg_t; + + virtual ~dboard_iface(void) {}; + /*! * Get special properties information for this dboard slot. * This call helps the dboard code to handle implementation @@ -123,8 +123,8 @@ public: * \param mask 16-bits, 0=do not change, 1=change value */ virtual void set_pin_ctrl( - unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff - ); + unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffff + ) = 0; /*! * Read back the pin control setting. @@ -132,7 +132,7 @@ public: * \param unit which unit rx or tx * \return the 16-bit settings value */ - virtual boost::uint16_t get_pin_ctrl(unit_t unit); + virtual boost::uint32_t get_pin_ctrl(unit_t unit) = 0; /*! * Set a daughterboard ATR register. @@ -143,8 +143,8 @@ public: * \param mask 16-bits, 0=do not change, 1=change value */ virtual void set_atr_reg( - unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask = 0xffff - ); + unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffff + ) = 0; /*! * Read back an ATR register setting. @@ -153,7 +153,7 @@ public: * \param reg which ATR register * \return the 16-bit settings value */ - virtual boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg); + virtual boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg) = 0; /*! * Set daughterboard GPIO data direction setting. @@ -163,8 +163,8 @@ public: * \param mask 16-bits, 0=do not change, 1=change value */ virtual void set_gpio_ddr( - unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff - ); + unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffff + ) = 0; /*! * Read back the GPIO data direction setting. @@ -172,7 +172,7 @@ public: * \param unit which unit rx or tx * \return the 16-bit settings value */ - virtual boost::uint16_t get_gpio_ddr(unit_t unit); + virtual boost::uint32_t get_gpio_ddr(unit_t unit) = 0; /*! * Set daughterboard GPIO pin output setting. @@ -182,8 +182,8 @@ public: * \param mask 16-bits, 0=do not change, 1=change value */ virtual void set_gpio_out( - unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff - ); + unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffff + ) = 0; /*! * Read back the GPIO pin output setting. @@ -191,15 +191,7 @@ public: * \param unit which unit rx or tx * \return the 16-bit settings value */ - virtual boost::uint16_t get_gpio_out(unit_t unit); - - /*! - * Setup the GPIO debug mux. - * - * \param unit which unit rx or tx - * \param which which debug: 0, 1 - */ - virtual void set_gpio_debug(unit_t unit, int which) = 0; + virtual boost::uint32_t get_gpio_out(unit_t unit) = 0; /*! * Read daughterboard GPIO pin values. @@ -207,7 +199,7 @@ public: * \param unit which unit rx or tx * \return the value of the gpio unit */ - virtual boost::uint16_t read_gpio(unit_t unit) = 0; + virtual boost::uint32_t read_gpio(unit_t unit) = 0; /*! * Write data to SPI bus peripheral. @@ -282,30 +274,35 @@ public: virtual double get_codec_rate(unit_t unit) = 0; /*! + * Configure the front-end connection parameters. + * + * \param unit which unit rx or tx + * \param fe_name name of the front-end to update + * \param fe_conn connection parameters class + */ + virtual void set_fe_connection( + unit_t unit, + const std::string& fe_name, + const uhd::usrp::fe_connection_t& fe_conn + ) = 0; + + /*! * Get the command time. * \return the command time */ - virtual uhd::time_spec_t get_command_time(void); + virtual uhd::time_spec_t get_command_time(void) = 0; /*! * Set the command time. * \param t the time */ - virtual void set_command_time(const uhd::time_spec_t& t); - -private: - UHD_PIMPL_DECL(impl) _impl; - - virtual void _set_pin_ctrl(unit_t unit, boost::uint16_t value) = 0; - virtual void _set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint16_t value) = 0; - virtual void _set_gpio_ddr(unit_t unit, boost::uint16_t value) = 0; - virtual void _set_gpio_out(unit_t unit, boost::uint16_t value) = 0; - -protected: - dboard_iface(void); -public: - virtual ~dboard_iface(void); + virtual void set_command_time(const uhd::time_spec_t& t) = 0; + /*! + * Sleep for a set time + * \param time time to sleep in nanoseconds + */ + virtual void sleep(const boost::chrono::nanoseconds& time); }; }} //namespace diff --git a/host/include/uhd/usrp/dboard_manager.hpp b/host/include/uhd/usrp/dboard_manager.hpp index d3a3ffb5c..e07b87ad8 100644 --- a/host/include/uhd/usrp/dboard_manager.hpp +++ b/host/include/uhd/usrp/dboard_manager.hpp @@ -45,15 +45,17 @@ public: * Register a rx or tx dboard into the system. * For single subdevice boards, omit subdev_names. * \param dboard_id the dboard id (rx or tx) - * \param dboard_ctor the dboard constructor function pointer + * \param db_subdev_ctor the dboard sub-device constructor function pointer (one instance per subdev name) * \param name the canonical name for the dboard represented * \param subdev_names the names of the subdevs on this dboard + * \param db_container_ctor the dboard container constructor function pointer (one instance per dboard) */ static void register_dboard( const dboard_id_t &dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0") + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0"), + dboard_ctor_t db_container_ctor = NULL ); /*! @@ -61,16 +63,58 @@ public: * For single subdevice boards, omit subdev_names. * \param rx_dboard_id the rx unit dboard id * \param tx_dboard_id the tx unit dboard id - * \param dboard_ctor the dboard constructor function pointer + * \param db_subdev_ctor the dboard sub-device constructor function pointer (one instance per subdev name) * \param name the canonical name for the dboard represented * \param subdev_names the names of the subdevs on this dboard + * \param db_container_ctor the dboard container constructor function pointer (one instance per dboard) */ static void register_dboard( const dboard_id_t &rx_dboard_id, const dboard_id_t &tx_dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0") + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0"), + dboard_ctor_t db_container_ctor = NULL + ); + + /*! + * Register a restricted rx or tx dboard into the system. + * A restricted dboard does not add its dboard_iface object into the property tree. + * For single subdevice boards, omit subdev_names. + * The iface for a restricted board is not registered into the property tree. + * \param dboard_id the dboard id (rx or tx) + * \param db_subdev_ctor the dboard sub-device constructor function pointer (one instance per subdev name) + * \param name the canonical name for the dboard represented + * \param subdev_names the names of the subdevs on this dboard + * \param db_container_ctor the dboard container constructor function pointer (one instance per dboard) + */ + static void register_dboard_restricted( + const dboard_id_t &dboard_id, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0"), + dboard_ctor_t db_container_ctor = NULL + ); + + /*! + * Register a restricted xcvr dboard into the system. + * A restricted dboard does not add its dboard_iface object into the property tree. + * For single subdevice boards, omit subdev_names. + * The iface for a restricted board is not registered into the property tree. + * \param rx_dboard_id the rx unit dboard id + * \param tx_dboard_id the tx unit dboard id + * \param db_subdev_ctor the dboard sub-device constructor function pointer (one instance per subdev name) + * \param name the canonical name for the dboard represented + * \param subdev_names the names of the subdevs on this dboard + * \param db_container_ctor the dboard container constructor function pointer (one instance per dboard) + */ + static void register_dboard_restricted( + const dboard_id_t &rx_dboard_id, + const dboard_id_t &tx_dboard_id, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0"), + dboard_ctor_t db_container_ctor = NULL ); /*! @@ -80,6 +124,7 @@ public: * \param gdboard_id the id of the grand-dboard * \param iface the custom dboard interface * \param subtree the subtree to load with props + * \param defer_db_init initialising the daughterboards (DEPRECATED) * \return an sptr to the new dboard manager */ static sptr make( @@ -87,8 +132,28 @@ public: dboard_id_t tx_dboard_id, dboard_id_t gdboard_id, dboard_iface::sptr iface, - property_tree::sptr subtree + property_tree::sptr subtree, + bool defer_db_init = false ); + + virtual ~dboard_manager() {} + + /*! + * Run dboard post constructor initializations if defered during make + */ + virtual void initialize_dboards() = 0; + + /*! + * Returns a vector of RX frontend (subdev) names + * \return a vector of names + */ + virtual const std::vector<std::string>& get_rx_frontends() const = 0; + + /*! + * Returns a vector of TX frontend (subdev) names + * \return a vector of names + */ + virtual const std::vector<std::string>& get_tx_frontends() const = 0; }; }} //namespace diff --git a/host/include/uhd/usrp/fe_connection.hpp b/host/include/uhd/usrp/fe_connection.hpp new file mode 100644 index 000000000..969246087 --- /dev/null +++ b/host/include/uhd/usrp/fe_connection.hpp @@ -0,0 +1,127 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_FE_CONNECTION_HPP +#define INCLUDED_UHD_USRP_FE_CONNECTION_HPP + +#include <uhd/config.hpp> +#include <boost/operators.hpp> +#include <string> + +namespace uhd { namespace usrp { + + class UHD_API fe_connection_t : boost::equality_comparable<fe_connection_t> { + public: + /** Sampling mode. + * Represents the sampling architecture for the front-end + */ + enum sampling_t { + QUADRATURE, /**< Complex sampling (Complex input, Complex output). */ + HETERODYNE, /**< Heterodyne sampling (Real input, Complex output). Only one of the I and Q inputs is used. */ + REAL /**< Real sampling (Real input, Real output). Only one of the I and Q inputs is used. */ + }; + + /*! + * Create a frontend connection class from individual settings. + * \param sampling_mode can be { QUADRATURE, HETERODYNE, REAL } + * \param iq_swapped indicates if the IQ channels are swapped (after inverion and heterodyne correction) + * \param i_inverted indicates if the I channel is inverted (negated) + * \param q_inverted indicates if the Q channel is inverted (negated) + * \param if_freq the baseband sampling frequency. + */ + fe_connection_t( + sampling_t sampling_mode, bool iq_swapped, + bool i_inverted, bool q_inverted, double if_freq = 0.0 + ); + + /*! + * Create a frontend connection class from a connection string + * The connection string can be: + * - in {I, Q}: Real mode sampling with no inversion. + * - in {Ib, Qb}: Real mode sampling with inversion. + * - in {IQ, QI}: Quadrature sampling with no inversion. + * - in {IbQb, QbIb}: Quadrature sampling with inversion. + * - in {II, QQ}: Heterodyne sampling with no inversion. + * - in {IbIb, QbQb}: Heterodyne sampling with inversion. + * + * \param conn_str the connection string. + * \param if_freq the baseband sampling frequency. + */ + fe_connection_t(const std::string& conn_str, double if_freq = 0.0); + + /*! + * Accessor for sampling mode + */ + inline sampling_t get_sampling_mode() const { + return _sampling_mode; + } + + /*! + * Accessor for IQ swap parameter + */ + inline bool is_iq_swapped() const { + return _iq_swapped; + } + + /*! + * Accessor for I inversion parameter + */ + inline bool is_i_inverted() const { + return _i_inverted; + } + + /*! + * Accessor for Q inversion parameter + */ + inline bool is_q_inverted() const { + return _q_inverted; + } + + /*! + * Accessor for IF frequency + */ + inline double get_if_freq() const { + return _if_freq; + } + + /*! + * Mutator for IF frequency + */ + inline void set_if_freq(double freq) { + _if_freq = freq; + } + + private: + sampling_t _sampling_mode; + bool _iq_swapped; + bool _i_inverted; + bool _q_inverted; + double _if_freq; + }; + + /*! + * Comparator operator overloaded for fe_connection_t. + * The boost::equality_comparable provides the !=. + * \param lhs the fe_connection_t to the left of the operator + * \param rhs the fe_connection_t to the right of the operator + * \return true when the fe connections are equal + */ + UHD_API bool operator==(const fe_connection_t &lhs, const fe_connection_t &rhs); + +}} //namespace + +#endif /* INCLUDED_UHD_USRP_FE_CONNECTION_HPP */ diff --git a/host/include/uhd/usrp/gpio_defs.hpp b/host/include/uhd/usrp/gpio_defs.hpp new file mode 100644 index 000000000..c32f22f28 --- /dev/null +++ b/host/include/uhd/usrp/gpio_defs.hpp @@ -0,0 +1,70 @@ +// +// Copyright 2011,2014,2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_GPIO_DEFS_HPP +#define INCLUDED_LIBUHD_USRP_GPIO_DEFS_HPP + +#include <uhd/config.hpp> +#include <boost/assign.hpp> +#include <boost/utility.hpp> +#include <map> + +namespace uhd { namespace usrp { namespace gpio_atr { + +enum gpio_atr_reg_t { + ATR_REG_IDLE = int('i'), + ATR_REG_TX_ONLY = int('t'), + ATR_REG_RX_ONLY = int('r'), + ATR_REG_FULL_DUPLEX = int('f') +}; + +enum gpio_atr_mode_t { + MODE_ATR = 0, //Output driven by the auto-transmit-receive engine + MODE_GPIO = 1 //Output value is static +}; + +enum gpio_ddr_t { + DDR_INPUT = 0, + DDR_OUTPUT = 1 +}; + +enum gpio_attr_t { + GPIO_CTRL, + GPIO_DDR, + GPIO_OUT, + GPIO_ATR_0X, + GPIO_ATR_RX, + GPIO_ATR_TX, + GPIO_ATR_XX +}; + +typedef std::map<gpio_attr_t, std::string> gpio_attr_map_t; + +static const gpio_attr_map_t gpio_attr_map = + boost::assign::map_list_of + (GPIO_CTRL, "CTRL") + (GPIO_DDR, "DDR") + (GPIO_OUT, "OUT") + (GPIO_ATR_0X, "ATR_0X") + (GPIO_ATR_RX, "ATR_RX") + (GPIO_ATR_TX, "ATR_TX") + (GPIO_ATR_XX, "ATR_XX") +; + +}}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_GPIO_DEFS_HPP */ diff --git a/host/include/uhd/usrp/multi_usrp.hpp b/host/include/uhd/usrp/multi_usrp.hpp index 715a57242..8c50178eb 100644 --- a/host/include/uhd/usrp/multi_usrp.hpp +++ b/host/include/uhd/usrp/multi_usrp.hpp @@ -31,6 +31,7 @@ #define UHD_USRP_MULTI_USRP_GPIO_API #define UHD_USRP_MULTI_USRP_REGISTER_API #define UHD_USRP_MULTI_USRP_FILTER_API +#define UHD_USRP_MULTI_USRP_LO_CONFIG_API #include <uhd/config.hpp> #include <uhd/device.hpp> @@ -112,6 +113,9 @@ public: //! A wildcard gain element name static const std::string ALL_GAINS; + //! A wildcard gain element name + static const std::string ALL_LOS; + /*! * Make a new multi usrp from the device address. * \param dev_addr the device address @@ -122,7 +126,7 @@ public: /*! * Get the underlying device object. * This is needed to get access to the streaming API and properties. - * \return the device object within this single usrp + * \return the device object within this USRP */ virtual device::sptr get_device(void) = 0; @@ -487,6 +491,90 @@ public: virtual freq_range_t get_fe_rx_freq_range(size_t chan = 0) = 0; /*! + * Get a list of possible LO stage names + * \param chan the channel index 0 to N-1 + * \return a vector of strings for possible LO names + */ + virtual std::vector<std::string> get_rx_lo_names(size_t chan = 0) = 0; + + /*! + * Set the LO source for the usrp device. + * For usrps that support selectable LOs, this function + * allows switching between them. + * Typical options for source: internal, external. + * \param src a string representing the LO source + * \param name the name of the LO stage to update + * \param chan the channel index 0 to N-1 + */ + virtual void set_rx_lo_source(const std::string &src, const std::string &name = ALL_LOS, size_t chan = 0) = 0; + + /*! + * Get the currently set LO source. + * Channels without controllable LO sources will return + * "internal" + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + * \return the configured LO source + */ + virtual const std::string get_rx_lo_source(const std::string &name = ALL_LOS, size_t chan = 0) = 0; + + /*! + * Get a list of possible LO sources. + * Channels which do not have controllable LO sources + * will return "internal". + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + * \return a vector of strings for possible settings + */ + virtual std::vector<std::string> get_rx_lo_sources(const std::string &name = ALL_LOS, size_t chan = 0) = 0; + + /*! + * Set whether the LO used by the usrp device is exported + * For usrps that support exportable LOs, this function + * configures if the LO used by chan is exported or not. + * \param enabled if true then export the LO + * \param name the name of the LO stage to update + * \param chan the channel index 0 to N-1 for the source channel + */ + virtual void set_rx_lo_export_enabled(bool enabled, const std::string &name = ALL_LOS, size_t chan = 0) = 0; + + /*! + * Returns true if the currently selected LO is being exported. + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + */ + virtual bool get_rx_lo_export_enabled(const std::string &name = ALL_LOS, size_t chan = 0) = 0; + + /*! + * Set the RX LO frequency (Advanced). + * \param freq the frequency to set the LO to + * \param name the name of the LO stage to update + * \param chan the channel index 0 to N-1 + * \return a coerced LO frequency + */ + virtual double set_rx_lo_freq(double freq, const std::string &name, size_t chan = 0) = 0; + + /*! + * Get the current RX LO frequency (Advanced). + * If the channel does not have independently configurable LOs + * the current rf frequency will be returned. + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + * \return the configured LO frequency + */ + virtual double get_rx_lo_freq(const std::string &name, size_t chan = 0) = 0; + + /*! + * Get the LO frequency range of the RX LO. + * If the channel does not have independently configurable LOs + * the rf frequency range will be returned. + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + * \return a frequency range object + */ + virtual freq_range_t get_rx_lo_freq_range(const std::string &name, size_t chan = 0) = 0; + + /*! * Set the RX gain value for the specified gain element. * For an empty name, distribute across all gain elements. * \param gain the gain in dB diff --git a/host/include/uhd/usrp/usrp.h b/host/include/uhd/usrp/usrp.h index 651279e22..f24d12b85 100644 --- a/host/include/uhd/usrp/usrp.h +++ b/host/include/uhd/usrp/usrp.h @@ -1,5 +1,5 @@ // -// Copyright 2015 Ettus Research LLC +// Copyright 2015-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -726,6 +726,83 @@ UHD_API uhd_error uhd_usrp_get_fe_rx_freq_range( uhd_meta_range_handle freq_range_out ); +//! A wildcard for all LO names +UHD_UNUSED(static const char* UHD_USRP_ALL_LOS) = "all"; + +//! Get a list of possible LO stage names +/* + * See uhd::usrp::multi_usrp::get_rx_lo_names() for more details. + */ +UHD_API uhd_error uhd_usrp_get_rx_lo_names( + uhd_usrp_handle h, + size_t chan, + uhd_string_vector_handle *rx_lo_names_out +); + +//! Set the LO source for the USRP device +/* + * See uhd::usrp::multi_usrp::set_rx_lo_source() for more details. + */ +UHD_API uhd_error uhd_usrp_set_rx_lo_source( + uhd_usrp_handle h, + const char* src, + const char* name, + size_t chan +); + +//! Get the currently set LO source +UHD_API uhd_error uhd_usrp_get_rx_lo_source( + uhd_usrp_handle h, + const char* name, + size_t chan, + char* rx_lo_source_out, + size_t strbuffer_len +); + +//! Get a list of possible LO sources +UHD_API uhd_error uhd_usrp_get_rx_lo_sources( + uhd_usrp_handle h, + const char* name, + size_t chan, + uhd_string_vector_handle *rx_lo_sources_out +); + +//! Set whether the LO used by the USRP device is exported +/* + * See uhd::usrp::multi_usrp::set_rx_lo_enabled() for more details. + */ +UHD_API uhd_error uhd_usrp_set_rx_lo_export_enabled( + uhd_usrp_handle h, + bool enabled, + const char* name, + size_t chan +); + +//! Returns true if the currently selected LO is being exported. +UHD_API uhd_error uhd_usrp_get_rx_lo_export_enabled( + uhd_usrp_handle h, + const char* name, + size_t chan, + bool* result_out +); + +//! Set the RX LO frequency. +UHD_API uhd_error uhd_usrp_set_rx_lo_freq( + uhd_usrp_handle h, + double freq, + const char* name, + size_t chan, + double* coerced_freq_out +); + +//! Get the current RX LO frequency. +UHD_API uhd_error uhd_usrp_get_rx_lo_freq( + uhd_usrp_handle h, + const char* name, + size_t chan, + double* rx_lo_freq_out +); + //! Set the RX gain for the given channel and name UHD_API uhd_error uhd_usrp_set_rx_gain( uhd_usrp_handle h, diff --git a/host/include/uhd/utils/algorithm.hpp b/host/include/uhd/utils/algorithm.hpp index 704d745d9..6c6cdf033 100644 --- a/host/include/uhd/utils/algorithm.hpp +++ b/host/include/uhd/utils/algorithm.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -39,7 +39,7 @@ namespace uhd{ * \param range the range of elements to be sorted * \return a new range with the elements sorted */ - template<typename Range> inline Range sorted(const Range &range){ + template<typename Range> UHD_INLINE Range sorted(const Range &range){ Range r(range); std::sort(boost::begin(r), boost::end(r)); return r; } @@ -53,7 +53,7 @@ namespace uhd{ * \param range the range of elements to be reversed * \return a new range with the elements reversed */ - template<typename Range> inline Range reversed(const Range &range){ + template<typename Range> UHD_INLINE Range reversed(const Range &range){ Range r(range); std::reverse(boost::begin(r), boost::end(r)); return r; } @@ -66,7 +66,7 @@ namespace uhd{ * \param value the match to look for in the range * \return true when the value is found in the range */ - template<typename Range, typename T> inline + template<typename Range, typename T> UHD_INLINE bool has(const Range &range, const T &value){ return boost::end(range) != std::find(boost::begin(range), boost::end(range), value); } @@ -78,7 +78,7 @@ namespace uhd{ * \param bound2 the upper or lower bound * \return the value clipped at the bounds */ - template<typename T> inline T clip(const T &val, const T &bound1, const T &bound2){ + template<typename T> UHD_INLINE T clip(const T &val, const T &bound1, const T &bound2){ const T minimum = std::min(bound1, bound2); if (val < minimum) return minimum; const T maximum = std::max(bound1, bound2); diff --git a/host/include/uhd/utils/atomic.hpp b/host/include/uhd/utils/atomic.hpp index 55769d2fd..8c5e6a5da 100644 --- a/host/include/uhd/utils/atomic.hpp +++ b/host/include/uhd/utils/atomic.hpp @@ -1,5 +1,5 @@ // -// Copyright 2012-2013 Ettus Research LLC +// Copyright 2012-2013,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -26,11 +26,7 @@ #include <boost/interprocess/detail/atomic.hpp> #include <boost/version.hpp> -#if BOOST_VERSION >= 104800 -# define BOOST_IPC_DETAIL boost::interprocess::ipcdetail -#else -# define BOOST_IPC_DETAIL boost::interprocess::detail -#endif +#define BOOST_IPC_DETAIL boost::interprocess::ipcdetail namespace uhd{ diff --git a/host/include/uhd/utils/cast.hpp b/host/include/uhd/utils/cast.hpp index 9db92c526..869d53053 100644 --- a/host/include/uhd/utils/cast.hpp +++ b/host/include/uhd/utils/cast.hpp @@ -1,5 +1,5 @@ // -// Copyright 2014 Ettus Research LLC +// Copyright 2014-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ namespace uhd{ namespace cast{ // Example: // boost::uint16_t x = hexstr_cast<boost::uint16_t>("0xDEADBEEF"); // Uses stringstream. - template<typename T> inline T hexstr_cast(const std::string &in) + template<typename T> UHD_INLINE T hexstr_cast(const std::string &in) { T x; std::stringstream ss; diff --git a/host/include/uhd/utils/dirty_tracked.hpp b/host/include/uhd/utils/dirty_tracked.hpp index d228a9e65..561beec9b 100644 --- a/host/include/uhd/utils/dirty_tracked.hpp +++ b/host/include/uhd/utils/dirty_tracked.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -60,7 +60,7 @@ namespace uhd{ /*! * Get underlying data */ - inline const data_t& get() const { + UHD_INLINE const data_t& get() const { return _data; } @@ -68,21 +68,21 @@ namespace uhd{ * Has the underlying data changed since the last * time it was cleaned? */ - inline bool is_dirty() const { + UHD_INLINE bool is_dirty() const { return _dirty; } /*! * Mark the underlying data as clean */ - inline void mark_clean() { + UHD_INLINE void mark_clean() { _dirty = false; } /*! * Mark the underlying data as dirty */ - inline void force_dirty() { + UHD_INLINE void force_dirty() { _dirty = true; } @@ -91,7 +91,7 @@ namespace uhd{ * Store the specified value and mark it as dirty * if it is not equal to the underlying data. */ - inline dirty_tracked& operator=(const data_t& value) + UHD_INLINE dirty_tracked& operator=(const data_t& value) { if(!(_data == value)) { //data_t must have an equality operator _dirty = true; @@ -107,7 +107,7 @@ namespace uhd{ * This exists to optimize out an implicit cast from dirty_tracked * type to data type. */ - inline dirty_tracked& operator=(const dirty_tracked& source) { + UHD_INLINE dirty_tracked& operator=(const dirty_tracked& source) { if (!(_data == source._data)) { _dirty = true; _data = source._data; @@ -118,7 +118,7 @@ namespace uhd{ /*! * Explicit conversion from this type to data_t */ - inline operator const data_t&() const { + UHD_INLINE operator const data_t&() const { return get(); } diff --git a/host/include/uhd/utils/math.hpp b/host/include/uhd/utils/math.hpp index 088983167..0b35f1f17 100644 --- a/host/include/uhd/utils/math.hpp +++ b/host/include/uhd/utils/math.hpp @@ -32,19 +32,6 @@ namespace uhd { namespace math { /*! - * Numeric limits of certain types. - * - * There are many sources for getting these, including std::numeric_limits, - * `<cstdint>`, `<climits>`, and Boost. The `<cstdint>` option is preferable as it - * gives us fixed-width constants, but unfortunately is new as of C++11. - * Since this isn't available on many systems, we need to use one of the - * other options. We will use the Boost option, here, since we use Boost - * data types for portability across UHD. - */ - static const boost::int32_t BOOST_INT32_MAX = boost::numeric::bounds<boost::int32_t>::highest(); - static const boost::int32_t BOOST_INT32_MIN = boost::numeric::bounds<boost::int32_t>::lowest(); - - /*! * Define epsilon values for floating point comparisons. * * There are a lot of different sources for epsilon values that we could use diff --git a/host/include/uhd/utils/msg_task.hpp b/host/include/uhd/utils/msg_task.hpp index d46fdd69e..8ae789d72 100644 --- a/host/include/uhd/utils/msg_task.hpp +++ b/host/include/uhd/utils/msg_task.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2014 Ettus Research LLC +// Copyright 2011-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -42,7 +42,7 @@ namespace uhd{ */ virtual msg_payload_t get_msg_from_dump_queue(boost::uint32_t sid) = 0; - inline static std::vector<boost::uint8_t> buff_to_vector(boost::uint8_t* p, size_t n) { + UHD_INLINE static std::vector<boost::uint8_t> buff_to_vector(boost::uint8_t* p, size_t n) { if(p and n > 0){ std::vector<boost::uint8_t> v(n); memcpy(&v.front(), p, n); diff --git a/host/include/uhd/utils/soft_register.hpp b/host/include/uhd/utils/soft_register.hpp index a2c34a4ec..09a69fce2 100644 --- a/host/include/uhd/utils/soft_register.hpp +++ b/host/include/uhd/utils/soft_register.hpp @@ -57,7 +57,7 @@ namespace uhd { //TODO: These hints were added to boost 1.53. /** \brief hint for the branch prediction */ -inline bool likely(bool expr) +UHD_INLINE bool likely(bool expr) { #ifdef __GNUC__ return __builtin_expect(expr, true); @@ -67,7 +67,7 @@ inline bool likely(bool expr) } /** \brief hint for the branch prediction */ -inline bool unlikely(bool expr) +UHD_INLINE bool unlikely(bool expr) { #ifdef __GNUC__ return __builtin_expect(expr, false); @@ -86,16 +86,16 @@ inline bool unlikely(bool expr) typedef boost::uint32_t soft_reg_field_t; namespace soft_reg_field { - inline size_t width(const soft_reg_field_t field) { + UHD_INLINE size_t width(const soft_reg_field_t field) { return (field & 0xFF); } - inline size_t shift(const soft_reg_field_t field) { + UHD_INLINE size_t shift(const soft_reg_field_t field) { return ((field >> 8) & 0xFF); } template<typename data_t> - inline size_t mask(const soft_reg_field_t field) { + UHD_INLINE size_t mask(const soft_reg_field_t field) { static const data_t ONE = static_cast<data_t>(1); //Behavior for the left shift operation is undefined in C++ //if the shift amount is >= bitwidth of the datatype @@ -122,7 +122,7 @@ public: * Cast the soft_register generic reference to a more specific type */ template <typename soft_reg_t> - inline static soft_reg_t& cast(soft_register_base& reg) { + UHD_INLINE static soft_reg_t& cast(soft_register_base& reg) { soft_reg_t* ptr = dynamic_cast<soft_reg_t*>(®); if (ptr) { return *ptr; @@ -150,7 +150,7 @@ public: /*! * Generic constructor for all soft_register types */ - explicit soft_register_t( + soft_register_t( wb_iface::wb_addr_type wr_addr, wb_iface::wb_addr_type rd_addr, soft_reg_flush_mode_t mode = ALWAYS_FLUSH): @@ -172,7 +172,7 @@ public: * Can be optionally synced with hardware. * NOTE: Memory management of the iface is up to the caller */ - inline void initialize(wb_iface& iface, bool sync = false) + UHD_INLINE void initialize(wb_iface& iface, bool sync = false) { _iface = &iface; @@ -186,7 +186,7 @@ public: * Performs a read-modify-write operation so all other field are preserved. * NOTE: This does not write the value to hardware. */ - inline void set(const soft_reg_field_t field, const reg_data_t value) + UHD_INLINE void set(const soft_reg_field_t field, const reg_data_t value) { _soft_copy = (_soft_copy & ~soft_reg_field::mask<reg_data_t>(field)) | ((value << soft_reg_field::shift(field)) & soft_reg_field::mask<reg_data_t>(field)); @@ -196,7 +196,7 @@ public: * Get the value of the specified field from the soft-copy. * NOTE: This does not read anything from hardware. */ - inline reg_data_t get(const soft_reg_field_t field) + UHD_INLINE reg_data_t get(const soft_reg_field_t field) { return (_soft_copy & soft_reg_field::mask<reg_data_t>(field)) >> soft_reg_field::shift(field); } @@ -204,7 +204,7 @@ public: /*! * Write the contents of the soft-copy to hardware. */ - inline void flush() + UHD_INLINE void flush() { if (writable && _iface) { //If optimized flush then poke only if soft copy is dirty @@ -223,14 +223,14 @@ public: _soft_copy.mark_clean(); } } else { - throw uhd::not_implemented_error("soft_register is not writable."); + throw uhd::not_implemented_error("soft_register is not writable or uninitialized."); } } /*! * Read the contents of the register from hardware and update the soft copy. */ - inline void refresh() + UHD_INLINE void refresh() { if (readable && _iface) { if (get_bitwidth() <= 16) { @@ -244,14 +244,14 @@ public: } _soft_copy.mark_clean(); } else { - throw uhd::not_implemented_error("soft_register is not readable."); + throw uhd::not_implemented_error("soft_register is not readable or uninitialized."); } } /*! * Shortcut for a set and a flush. */ - inline void write(const soft_reg_field_t field, const reg_data_t value) + UHD_INLINE void write(const soft_reg_field_t field, const reg_data_t value) { set(field, value); flush(); @@ -260,7 +260,7 @@ public: /*! * Shortcut for refresh and get */ - inline reg_data_t read(const soft_reg_field_t field) + UHD_INLINE reg_data_t read(const soft_reg_field_t field) { refresh(); return get(field); @@ -269,7 +269,7 @@ public: /*! * Get bitwidth for this register */ - inline size_t get_bitwidth() + UHD_INLINE size_t get_bitwidth() { static const size_t BITS_IN_BYTE = 8; return sizeof(reg_data_t) * BITS_IN_BYTE; @@ -278,7 +278,7 @@ public: /*! * Is the register readable? */ - inline bool is_readable() + UHD_INLINE bool is_readable() { return readable; } @@ -286,7 +286,7 @@ public: /*! * Is the register writable? */ - inline bool is_writable() + UHD_INLINE bool is_writable() { return writable; } @@ -308,7 +308,7 @@ class UHD_API soft_register_sync_t : public soft_register_t<reg_data_t, readable public: typedef boost::shared_ptr< soft_register_sync_t<reg_data_t, readable, writable> > sptr; - explicit soft_register_sync_t( + soft_register_sync_t( wb_iface::wb_addr_type wr_addr, wb_iface::wb_addr_type rd_addr, soft_reg_flush_mode_t mode = ALWAYS_FLUSH): @@ -321,43 +321,43 @@ public: soft_register_t<reg_data_t, readable, writable>(addr, mode), _mutex() {} - inline void initialize(wb_iface& iface, bool sync = false) + UHD_INLINE void initialize(wb_iface& iface, bool sync = false) { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::initialize(iface, sync); } - inline void set(const soft_reg_field_t field, const reg_data_t value) + UHD_INLINE void set(const soft_reg_field_t field, const reg_data_t value) { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::set(field, value); } - inline reg_data_t get(const soft_reg_field_t field) + UHD_INLINE reg_data_t get(const soft_reg_field_t field) { boost::lock_guard<boost::mutex> lock(_mutex); return soft_register_t<reg_data_t, readable, writable>::get(field); } - inline void flush() + UHD_INLINE void flush() { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::flush(); } - inline void refresh() + UHD_INLINE void refresh() { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::refresh(); } - inline void write(const soft_reg_field_t field, const reg_data_t value) + UHD_INLINE void write(const soft_reg_field_t field, const reg_data_t value) { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::write(field, value); } - inline reg_data_t read(const soft_reg_field_t field) + UHD_INLINE reg_data_t read(const soft_reg_field_t field) { boost::lock_guard<boost::mutex> lock(_mutex); return soft_register_t<reg_data_t, readable, writable>::read(field); @@ -469,7 +469,7 @@ public: /*! * Get the name of this register map */ - virtual inline const std::string& get_name() const { return _name; } + virtual UHD_INLINE const std::string& get_name() const { return _name; } /*! * Initialize all registers in this register map using a bus. @@ -542,7 +542,7 @@ protected: /*! * Add a register to this map with an identifier "name" and visibility */ - inline void add_to_map(soft_register_base& reg, const std::string& name, const visibility_t visible = PRIVATE) { + UHD_INLINE void add_to_map(soft_register_base& reg, const std::string& name, const visibility_t visible = PRIVATE) { boost::lock_guard<boost::mutex> lock(_mutex); if (visible == PUBLIC) { //Only add to the map if this register is publicly visible diff --git a/host/include/uhd/version.hpp.in b/host/include/uhd/version.hpp.in index e2c64812d..10f6a97ba 100644 --- a/host/include/uhd/version.hpp.in +++ b/host/include/uhd/version.hpp.in @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,14 +27,14 @@ * The format is oldest API compatible release - ABI compat number. * The compatibility number allows pre-release ABI to be versioned. */ -#define UHD_VERSION_ABI_STRING "3.9.0-0" +#define UHD_VERSION_ABI_STRING "@TRIMMED_VERSION_MAJOR@.@TRIMMED_VERSION_API@.@TRIMMED_VERSION_ABI@" /*! * A macro to check UHD version at compile-time. - * The value of this macro is MAJOR * 10000 + MINOR * 100 + PATCH - * (e.g., for UHD 3.8.1 this is 30801). + * The value of this macro is MAJOR * 1000000 + API * 10000 + ABI * 100 + PATCH + * (e.g., for UHD 3.10.0.1 this is 3100001). */ -#cmakedefine UHD_VERSION @UHD_VERSION_ADDED@ +#define UHD_VERSION @UHD_VERSION_ADDED@ namespace uhd{ diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index f74af1f29..0cd89953c 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -65,15 +65,47 @@ MACRO(INCLUDE_SUBDIRECTORY subdir) ENDMACRO(INCLUDE_SUBDIRECTORY) ######################################################################## +# Register lower level components +######################################################################## +MESSAGE(STATUS "") +# Dependencies +FIND_PACKAGE(USB1) +FIND_PACKAGE(GPSD) +LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF OFF) +LIBUHD_REGISTER_COMPONENT("GPSD" ENABLE_GPSD OFF "ENABLE_LIBUHD;ENABLE_GPSD;LIBGPS_FOUND" OFF OFF) +# Devices +LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) +LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) +LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF OFF) +LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) +LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("N230" ENABLE_N230 ON "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF) + +######################################################################## # Include subdirectories (different than add) ######################################################################## INCLUDE_SUBDIRECTORY(ic_reg_maps) INCLUDE_SUBDIRECTORY(types) INCLUDE_SUBDIRECTORY(convert) -INCLUDE_SUBDIRECTORY(transport) +INCLUDE_SUBDIRECTORY(rfnoc) INCLUDE_SUBDIRECTORY(usrp) INCLUDE_SUBDIRECTORY(usrp_clock) INCLUDE_SUBDIRECTORY(utils) +INCLUDE_SUBDIRECTORY(experts) +INCLUDE_SUBDIRECTORY(transport) + +######################################################################## +# Build info +######################################################################## +INCLUDE(UHDBuildInfo) +UHD_LOAD_BUILD_INFO() +CONFIGURE_FILE( + ${CMAKE_CURRENT_SOURCE_DIR}/build_info.cpp + ${CMAKE_CURRENT_BINARY_DIR}/build_info.cpp +@ONLY) ######################################################################## # Setup UHD_VERSION_STRING for version.cpp @@ -87,8 +119,10 @@ CONFIGURE_FILE( # Append to the list of sources for lib uhd ######################################################################## LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_BINARY_DIR}/build_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/deprecated.cpp ${CMAKE_CURRENT_SOURCE_DIR}/device.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/device3.cpp ${CMAKE_CURRENT_SOURCE_DIR}/image_loader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/stream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exception.cpp @@ -106,6 +140,7 @@ ENDIF(ENABLE_C_API) # Add DLL resource file to Windows build ######################################################################## IF(MSVC) + MATH(EXPR TRIMMED_VERSION_MAJOR_API "${TRIMMED_VERSION_MAJOR} * 1000 + ${TRIMMED_VERSION_API}") SET(RC_TRIMMED_VERSION_PATCH ${TRIMMED_VERSION_PATCH}) IF(UHD_VERSION_DEVEL) SET(RC_TRIMMED_VERSION_PATCH "999") @@ -142,7 +177,7 @@ TARGET_LINK_LIBRARIES(uhd ${Boost_LIBRARIES} ${libuhd_libs}) SET_TARGET_PROPERTIES(uhd PROPERTIES DEFINE_SYMBOL "UHD_DLL_EXPORTS") IF(NOT LIBUHDDEV_PKG) SET_TARGET_PROPERTIES(uhd PROPERTIES SOVERSION "${UHD_VERSION_MAJOR}") - SET_TARGET_PROPERTIES(uhd PROPERTIES VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_MINOR}") + SET_TARGET_PROPERTIES(uhd PROPERTIES VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_API}") ENDIF(NOT LIBUHDDEV_PKG) IF(DEFINED LIBUHD_OUTPUT_NAME) SET_TARGET_PROPERTIES(uhd PROPERTIES OUTPUT_NAME ${LIBUHD_OUTPUT_NAME}) diff --git a/host/lib/build_info.cpp b/host/lib/build_info.cpp new file mode 100644 index 000000000..5ccfd0268 --- /dev/null +++ b/host/lib/build_info.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2015 National Instruments Corp. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <config.h> + +#include <uhd/build_info.hpp> + +#include <boost/format.hpp> +#include <boost/version.hpp> +#include <boost/algorithm/string.hpp> + +#ifdef ENABLE_USB +#include <libusb.h> +#endif + +namespace uhd { namespace build_info { + + const std::string boost_version() { + return boost::algorithm::replace_all_copy( + std::string(BOOST_LIB_VERSION), "_", "." + ); + } + + const std::string build_date() { + return "@UHD_BUILD_DATE@"; + } + + const std::string c_compiler() { + return "@UHD_C_COMPILER@"; + } + + const std::string cxx_compiler() { + return "@UHD_CXX_COMPILER@"; + } + +#ifdef _MSC_VER + static const std::string define_flag = "/D "; +#else + static const std::string define_flag = "-D"; +#endif + + const std::string c_flags() { + return boost::algorithm::replace_all_copy( + (define_flag + std::string("@UHD_C_FLAGS@")), + std::string(";"), (" " + define_flag) + ); + } + + const std::string cxx_flags() { + return boost::algorithm::replace_all_copy( + (define_flag + std::string("@UHD_CXX_FLAGS@")), + std::string(";"), (" " + define_flag) + ); + } + + const std::string enabled_components() { + return boost::algorithm::replace_all_copy( + std::string("@_uhd_enabled_components@"), + std::string(";"), std::string(", ") + ); + } + + const std::string install_prefix() { + return "@CMAKE_INSTALL_PREFIX@"; + } + + const std::string libusb_version() { + #ifdef ENABLE_USB + /* + * Versions can only be queried from 1.0.13 onward. + * Depending on if the commit came from libusbx or + * libusb (now merged), the define might be different. + */ + #ifdef LIBUSB_API_VERSION /* 1.0.18 onward */ + int major_version = LIBUSB_API_VERSION >> 24; + int minor_version = (LIBUSB_API_VERSION & 0xFF0000) >> 16; + int micro_version = ((LIBUSB_API_VERSION & 0xFFFF) - 0x100) + 18; + + return str(boost::format("%d.%d.%d") + % major_version % minor_version % micro_version); + #elif defined(LIBUSBX_API_VERSION) /* 1.0.13 - 1.0.17 */ + switch(LIBUSBX_API_VERSION & 0xFF) { + case 0x00: + return "1.0.13"; + case 0x01: + return "1.0.15"; + case 0xFF: + return "1.0.14"; + default: + return "1.0.16 or 1.0.17"; + } + #else + return "< 1.0.13"; + #endif + #else + return "N/A"; + #endif + } +}} diff --git a/host/lib/convert/convert_item32.cpp b/host/lib/convert/convert_item32.cpp index 57bd64860..d52b47a1a 100644 --- a/host/lib/convert/convert_item32.cpp +++ b/host/lib/convert/convert_item32.cpp @@ -38,7 +38,10 @@ _DECLARE_ITEM32_CONVERTER(cpu_type, sc8) \ _DECLARE_ITEM32_CONVERTER(cpu_type, sc16) +/* Create sc16<->sc16,sc8(otw) */ DECLARE_ITEM32_CONVERTER(sc16) +/* Create fc32<->sc16,sc8(otw) */ DECLARE_ITEM32_CONVERTER(fc32) +/* Create fc64<->sc16,sc8(otw) */ DECLARE_ITEM32_CONVERTER(fc64) _DECLARE_ITEM32_CONVERTER(sc8, sc8) diff --git a/host/lib/convert/gen_convert_general.py b/host/lib/convert/gen_convert_general.py index 4f9eeb747..5c62d51df 100644 --- a/host/lib/convert/gen_convert_general.py +++ b/host/lib/convert/gen_convert_general.py @@ -39,30 +39,37 @@ DECLARE_CONVERTER(item32, 1, item32, 1, PRIORITY_GENERAL) { } """ -TMPL_CONV_GEN2_ITEM32 = """ -DECLARE_CONVERTER(item32, 1, sc16_item32_{end}, 1, PRIORITY_GENERAL) {{ +# Some 32-bit types converters are also defined in convert_item32.cpp to +# take care of quirks such as I/Q ordering on the wire etc. +TMPL_CONV_ITEM32 = """ +DECLARE_CONVERTER({in_type}, 1, {out_type}, 1, PRIORITY_GENERAL) {{ const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); for (size_t i = 0; i < nsamps; i++) {{ - output[i] = {to_wire}(input[i]); + output[i] = {to_wire_or_host}(input[i]); }} }} +""" -DECLARE_CONVERTER(sc16_item32_{end}, 1, item32, 1, PRIORITY_GENERAL) {{ +# 64-bit data types are two consecutive item32 items +TMPL_CONV_ITEM64 = """ +DECLARE_CONVERTER({in_type}, 1, {out_type}, 1, PRIORITY_GENERAL) {{ const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); - for (size_t i = 0; i < nsamps; i++) {{ - output[i] = {to_host}(input[i]); + // An item64 is two item32_t's + for (size_t i = 0; i < nsamps * 2; i++) {{ + output[i] = {to_wire_or_host}(input[i]); }} }} """ -TMPL_CONV_U8 = """ -DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{ - const boost::uint32_t *input = reinterpret_cast<const boost::uint32_t *>(inputs[0]); - boost::uint32_t *output = reinterpret_cast<boost::uint32_t *>(outputs[0]); + +TMPL_CONV_U8S8 = """ +DECLARE_CONVERTER({us8}, 1, {us8}_item32_{end}, 1, PRIORITY_GENERAL) {{ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); // 1) Copy all the 4-byte tuples size_t n_words = nsamps / 4; @@ -72,8 +79,8 @@ DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{ // 2) If nsamps was not a multiple of 4, copy the rest by hand size_t bytes_left = nsamps % 4; if (bytes_left) {{ - const u8_t *last_input_word = reinterpret_cast<const u8_t *>(&input[n_words]); - u8_t *last_output_word = reinterpret_cast<u8_t *>(&output[n_words]); + const {us8}_t *last_input_word = reinterpret_cast<const {us8}_t *>(&input[n_words]); + {us8}_t *last_output_word = reinterpret_cast<{us8}_t *>(&output[n_words]); for (size_t k = 0; k < bytes_left; k++) {{ last_output_word[k] = last_input_word[k]; }} @@ -81,9 +88,9 @@ DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{ }} }} -DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{ - const boost::uint32_t *input = reinterpret_cast<const boost::uint32_t *>(inputs[0]); - boost::uint32_t *output = reinterpret_cast<boost::uint32_t *>(outputs[0]); +DECLARE_CONVERTER({us8}_item32_{end}, 1, {us8}, 1, PRIORITY_GENERAL) {{ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); // 1) Copy all the 4-byte tuples size_t n_words = nsamps / 4; @@ -93,9 +100,9 @@ DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{ // 2) If nsamps was not a multiple of 4, copy the rest by hand size_t bytes_left = nsamps % 4; if (bytes_left) {{ - boost::uint32_t last_input_word = {to_host}(input[n_words]); - const u8_t *last_input_word_ptr = reinterpret_cast<const u8_t *>(&last_input_word); - u8_t *last_output_word = reinterpret_cast<u8_t *>(&output[n_words]); + item32_t last_input_word = {to_host}(input[n_words]); + const {us8}_t *last_input_word_ptr = reinterpret_cast<const {us8}_t *>(&last_input_word); + {us8}_t *last_output_word = reinterpret_cast<{us8}_t *>(&output[n_words]); for (size_t k = 0; k < bytes_left; k++) {{ last_output_word[k] = last_input_word_ptr[k]; }} @@ -103,6 +110,40 @@ DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{ }} """ +TMPL_CONV_S16 = """ +DECLARE_CONVERTER(s16, 1, s16_item32_{end}, 1, PRIORITY_GENERAL) {{ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + // 1) Copy all the 4-byte tuples + size_t n_words = nsamps / 2; + for (size_t i = 0; i < n_words; i++) {{ + output[i] = {to_wire}(input[i]); + }} + // 2) If nsamps was not a multiple of 2, copy the last one by hand + if (nsamps % 2) {{ + item32_t tmp = item32_t(*reinterpret_cast<const s16_t *>(&input[n_words])); + output[n_words] = {to_wire}(tmp); + }} +}} + +DECLARE_CONVERTER(s16_item32_{end}, 1, s16, 1, PRIORITY_GENERAL) {{ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + // 1) Copy all the 4-byte tuples + size_t n_words = nsamps / 2; + for (size_t i = 0; i < n_words; i++) {{ + output[i] = {to_host}(input[i]); + }} + // 2) If nsamps was not a multiple of 2, copy the last one by hand + if (nsamps % 2) {{ + item32_t tmp = {to_host}(input[n_words]); + *reinterpret_cast<s16_t *>(&output[n_words]) = s16_t(tmp); + }} +}} +""" + TMPL_CONV_USRP1_COMPLEX = """ DECLARE_CONVERTER(${cpu_type}, ${width}, sc16_item16_usrp1, 1, PRIORITY_GENERAL){ % for w in range(width): @@ -164,23 +205,52 @@ if __name__ == '__main__': file = os.path.basename(__file__) output = parse_tmpl(TMPL_HEADER, file=file) - #generate complex converters for all gen2 platforms - for end, to_host, to_wire in ( - ('be', 'uhd::ntohx', 'uhd::htonx'), - ('le', 'uhd::wtohx', 'uhd::htowx'), - ): - output += TMPL_CONV_GEN2_ITEM32.format( - end=end, to_host=to_host, to_wire=to_wire - ) - #generate raw (u8) converters: + ## Generate all data types that are exactly + ## item32 or multiples thereof: + for end in ('be', 'le'): + host_to_wire = {'be': 'uhd::htonx', 'le': 'uhd::htowx'}[end] + wire_to_host = {'be': 'uhd::ntohx', 'le': 'uhd::wtohx'}[end] + # item32 types (sc16->sc16 is a special case because it defaults + # to Q/I order on the wire: + for in_type, out_type, to_wire_or_host in ( + ('item32', 'sc16_item32_{end}', host_to_wire), + ('sc16_item32_{end}', 'item32', wire_to_host), + ('f32', 'f32_item32_{end}', host_to_wire), + ('f32_item32_{end}', 'f32', wire_to_host), + ): + output += TMPL_CONV_ITEM32.format( + end=end, to_wire_or_host=to_wire_or_host, + in_type=in_type.format(end=end), out_type=out_type.format(end=end) + ) + # 2xitem32 types: + for in_type, out_type in ( + ('fc32', 'fc32_item32_{end}'), + ('fc32_item32_{end}', 'fc32'), + ): + output += TMPL_CONV_ITEM64.format( + end=end, to_wire_or_host=to_wire_or_host, + in_type=in_type.format(end=end), out_type=out_type.format(end=end) + ) + + ## Real 16-Bit: for end, to_host, to_wire in ( ('be', 'uhd::ntohx', 'uhd::htonx'), ('le', 'uhd::wtohx', 'uhd::htowx'), ): - output += TMPL_CONV_U8.format( - end=end, to_host=to_host, to_wire=to_wire + output += TMPL_CONV_S16.format( + end=end, to_host=to_host, to_wire=to_wire ) + ## Real 8-Bit Types: + for us8 in ('u8', 's8'): + for end, to_host, to_wire in ( + ('be', 'uhd::ntohx', 'uhd::htonx'), + ('le', 'uhd::wtohx', 'uhd::htowx'), + ): + output += TMPL_CONV_U8S8.format( + us8=us8, end=end, to_host=to_host, to_wire=to_wire + ) + #generate complex converters for usrp1 format (requires Cheetah) for width in 1, 2, 4: for cpu_type, do_scale in ( diff --git a/host/lib/device.cpp b/host/lib/device.cpp index 3e84d5bea..ff4bbc212 100644 --- a/host/lib/device.cpp +++ b/host/lib/device.cpp @@ -50,7 +50,7 @@ static size_t hash_device_addr( if(dev_addr.has_key("resource")) { boost::hash_combine(hash, "resource"); - boost::hash_combine(hash, dev_addr["resource"]); + boost::hash_combine(hash, dev_addr["resource"]); } else { BOOST_FOREACH(const std::string &key, uhd::sorted(dev_addr.keys())){ diff --git a/host/lib/device3.cpp b/host/lib/device3.cpp new file mode 100644 index 000000000..3b316e8ea --- /dev/null +++ b/host/lib/device3.cpp @@ -0,0 +1,75 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/format.hpp> +#include <uhd/device3.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +device3::sptr device3::make(const device_addr_t &hint, const size_t which) +{ + device3::sptr device3_sptr = + boost::dynamic_pointer_cast< device3 >(device::make(hint, device::USRP, which)); + if (not device3_sptr) { + throw uhd::key_error(str( + boost::format("No gen-3 devices found for ----->\n%s") % hint.to_pp_string() + )); + } + + return device3_sptr; +} + +bool device3::has_block(const rfnoc::block_id_t &block_id) const +{ + for (size_t i = 0; i < _rfnoc_block_ctrl.size(); i++) { + if (_rfnoc_block_ctrl[i]->get_block_id() == block_id) { + return true; + } + } + return false; +} + +block_ctrl_base::sptr device3::get_block_ctrl(const block_id_t &block_id) const +{ + for (size_t i = 0; i < _rfnoc_block_ctrl.size(); i++) { + if (_rfnoc_block_ctrl[i]->get_block_id() == block_id) { + return _rfnoc_block_ctrl[i]; + } + } + throw uhd::lookup_error(str(boost::format("This device does not have a block with ID: %s") % block_id.to_string())); +} + +std::vector<rfnoc::block_id_t> device3::find_blocks(const std::string &block_id_hint) const +{ + std::vector<rfnoc::block_id_t> block_ids; + for (size_t i = 0; i < _rfnoc_block_ctrl.size(); i++) { + if (_rfnoc_block_ctrl[i]->get_block_id().match(block_id_hint)) { + block_ids.push_back(_rfnoc_block_ctrl[i]->get_block_id()); + } + } + return block_ids; +} + +void device3::clear() +{ + BOOST_FOREACH(const block_ctrl_base::sptr &block, _rfnoc_block_ctrl) { + block->clear(); + } +} +// vim: sw=4 et: diff --git a/host/lib/exception.cpp b/host/lib/exception.cpp index a9e36fd15..fc87fe791 100644 --- a/host/lib/exception.cpp +++ b/host/lib/exception.cpp @@ -43,6 +43,7 @@ make_exception_impl("EnvironmentError", environment_error, exception) make_exception_impl("IOError", io_error, environment_error) make_exception_impl("OSError", os_error, environment_error) make_exception_impl("SystemError", system_error, exception) +make_exception_impl("SyntaxError", syntax_error, exception) usb_error::usb_error(int code, const std::string &what): runtime_error(str(boost::format("%s %d: %s") % "USBError" % code % what)), _code(code) {} diff --git a/host/lib/experts/CMakeLists.txt b/host/lib/experts/CMakeLists.txt new file mode 100644 index 000000000..db533e7fa --- /dev/null +++ b/host/lib/experts/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# Copyright 2016 Ettus Research +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/expert_container.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/expert_factory.cpp +) + +# Verbose Debug output for send/recv +SET( UHD_EXPERT_LOGGING OFF CACHE BOOL "Enable verbose logging for experts" ) +OPTION( UHD_EXPERT_LOGGING "Enable verbose logging for experts" "" ) +IF(UHD_EXPERT_LOGGING) + MESSAGE(STATUS "Enabling verbose logging for experts") + ADD_DEFINITIONS(-DUHD_EXPERT_LOGGING) +ENDIF() diff --git a/host/lib/experts/expert_container.cpp b/host/lib/experts/expert_container.cpp new file mode 100644 index 000000000..edfc2ebe3 --- /dev/null +++ b/host/lib/experts/expert_container.cpp @@ -0,0 +1,531 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expert_container.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread.hpp> +#include <boost/graph/graph_traits.hpp> +#include <boost/graph/depth_first_search.hpp> +#include <boost/graph/topological_sort.hpp> +#include <boost/graph/adjacency_list.hpp> + +#ifdef UHD_EXPERT_LOGGING +#define EX_LOG(depth, str) _log(depth, str) +#else +#define EX_LOG(depth, str) +#endif + +namespace uhd { namespace experts { + +typedef boost::adjacency_list< + boost::vecS, //Container used to represent the edge-list for each of the vertices. + boost::vecS, //container used to represent the vertex-list of the graph. + boost::directedS, //Directionality of graph + dag_vertex_t*, //Storage for each vertex + boost::no_property, //Storage for each edge + boost::no_property, //Storage for graph object + boost::listS //Container used to represent the edge-list for the graph. +> expert_graph_t; + +typedef std::map<std::string, expert_graph_t::vertex_descriptor> vertex_map_t; +typedef std::list<expert_graph_t::vertex_descriptor> node_queue_t; + +typedef boost::graph_traits<expert_graph_t>::edge_iterator edge_iter; +typedef boost::graph_traits<expert_graph_t>::vertex_iterator vertex_iter; + +class expert_container_impl : public expert_container +{ +private: //Visitor class for cycle detection algorithm + struct cycle_det_visitor : public boost::dfs_visitor<> + { + cycle_det_visitor(std::vector<std::string>& back_edges): + _back_edges(back_edges) {} + + template <class Edge, class Graph> + void back_edge(Edge u, const Graph& g) { + _back_edges.push_back( + g[boost::source(u,g)]->get_name() + "->" + g[boost::target(u,g)]->get_name()); + } + private: std::vector<std::string>& _back_edges; + }; + +public: + expert_container_impl(const std::string& name): + _name(name) + { + } + + ~expert_container_impl() + { + clear(); + } + + const std::string& get_name() const + { + return _name; + } + + void resolve_all(bool force = false) + { + boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + EX_LOG(0, str(boost::format("resolve_all(%s)") % (force?"force":""))); + _resolve_helper("", "", force); + } + + void resolve_from(const std::string& node_name) + { + boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + EX_LOG(0, str(boost::format("resolve_from(%s)") % node_name)); + _resolve_helper(node_name, "", false); + } + + void resolve_to(const std::string& node_name) + { + boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + EX_LOG(0, str(boost::format("resolve_to(%s)") % node_name)); + _resolve_helper("", node_name, false); + } + + dag_vertex_t& retrieve(const std::string& name) const + { + try { + expert_graph_t::vertex_descriptor vertex = _lookup_vertex(name); + return _get_vertex(vertex); + } catch(std::exception&) { + throw uhd::lookup_error("failed to find node " + name + " in expert graph"); + } + } + + const dag_vertex_t& lookup(const std::string& name) const + { + return retrieve(name); + } + + const node_retriever_t& node_retriever() const + { + return *this; + } + + std::string to_dot() const + { + static const std::string DATA_SHAPE("ellipse"); + static const std::string WORKER_SHAPE("box"); + + std::string dot_str; + dot_str += "digraph uhd_experts_" + _name + " {\n rankdir=LR;\n"; + // Iterate through the vertices and print them out + for (std::pair<vertex_iter, vertex_iter> vi = boost::vertices(_expert_dag); + vi.first != vi.second; + ++vi.first + ) { + const dag_vertex_t& vertex = _get_vertex(*vi.first); + if (vertex.get_class() != CLASS_WORKER) { + dot_str += str(boost::format(" %d [label=\"%s\",shape=%s,xlabel=\"%s\"];\n") % + boost::uint32_t(*vi.first) % vertex.get_name() % + DATA_SHAPE % vertex.get_dtype()); + } else { + dot_str += str(boost::format(" %d [label=\"%s\",shape=%s];\n") % + boost::uint32_t(*vi.first) % vertex.get_name() % WORKER_SHAPE); + } + } + + // Iterate through the edges and print them out + for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); + ei.first != ei.second; + ++ei.first + ) { + dot_str += str(boost::format(" %d -> %d;\n") % + boost::uint32_t(boost::source(*(ei.first), _expert_dag)) % + boost::uint32_t(boost::target(*(ei.first), _expert_dag))); + } + dot_str += "}\n"; + return dot_str; + } + + void debug_audit() const + { +#ifdef UHD_EXPERT_LOGGING + EX_LOG(0, "debug_audit()"); + + //Test 1: Check for cycles in graph + std::vector<std::string> back_edges; + cycle_det_visitor cdet_vis(back_edges); + boost::depth_first_search(_expert_dag, boost::visitor(cdet_vis)); + if (back_edges.empty()) { + EX_LOG(1, "cycle check ... PASSED"); + } else { + EX_LOG(1, "cycle check ... ERROR!!!"); + BOOST_FOREACH(const std::string& e, back_edges) { + EX_LOG(2, "back edge: " + e); + } + } + back_edges.clear(); + + //Test 2: Check data node input and output edges + std::vector<std::string> data_node_issues; + BOOST_FOREACH(const vertex_map_t::value_type& v, _datanode_map) { + size_t in_count = 0, out_count = 0; + for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); + ei.first != ei.second; + ++ei.first + ) { + if (boost::target(*(ei.first), _expert_dag) == v.second) + in_count++; + if (boost::source(*(ei.first), _expert_dag) == v.second) + out_count++; + } + bool prop_unused = false; + if (in_count > 1) { + data_node_issues.push_back(v.first + ": multiple writers (workers)"); + } else if (in_count > 0) { + if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY) { + data_node_issues.push_back(v.first + ": multiple writers (worker and property tree)"); + } + } else { + if (_expert_dag[v.second]->get_class() != CLASS_PROPERTY) { + data_node_issues.push_back(v.first + ": unreachable (will always hold initial value)"); + } else if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY and not _expert_dag[v.second]->has_write_callback()) { + if (out_count > 0) { + data_node_issues.push_back(v.first + ": needs explicit resolve after write"); + } else { + data_node_issues.push_back(v.first + ": unused (no readers or writers)"); + prop_unused = true; + } + } + } + if (out_count < 1) { + if (_expert_dag[v.second]->get_class() != CLASS_PROPERTY) { + data_node_issues.push_back(v.first + ": unused (is not read by any worker)"); + } else if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY and not _expert_dag[v.second]->has_read_callback()) { + if (not prop_unused) { + data_node_issues.push_back(v.first + ": needs explicit resolve to read"); + } + } + } + } + + if (data_node_issues.empty()) { + EX_LOG(1, "data node check ... PASSED"); + } else { + EX_LOG(1, "data node check ... WARNING!"); + BOOST_FOREACH(const std::string& i, data_node_issues) { + EX_LOG(2, i); + } + } + data_node_issues.clear(); + + //Test 3: Check worker node input and output edges + std::vector<std::string> worker_issues; + BOOST_FOREACH(const vertex_map_t::value_type& v, _worker_map) { + size_t in_count = 0, out_count = 0; + for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); + ei.first != ei.second; + ++ei.first + ) { + if (boost::target(*(ei.first), _expert_dag) == v.second) + in_count++; + if (boost::source(*(ei.first), _expert_dag) == v.second) + out_count++; + } + if (in_count < 1) { + worker_issues.push_back(v.first + ": no inputs (will never resolve)"); + } + if (out_count < 1) { + worker_issues.push_back(v.first + ": no outputs"); + } + } + if (worker_issues.empty()) { + EX_LOG(1, "worker check ... PASSED"); + } else { + EX_LOG(1, "worker check ... WARNING!"); + BOOST_FOREACH(const std::string& i, worker_issues) { + EX_LOG(2, i); + } + } + worker_issues.clear(); +#endif + } + + inline boost::recursive_mutex& resolve_mutex() { + return _resolve_mutex; + } + +protected: + void add_data_node(dag_vertex_t* data_node, auto_resolve_mode_t resolve_mode) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + //Sanity check node pointer + if (data_node == NULL) { + throw uhd::runtime_error("NULL data node passed into expert container for registration."); + } + + //Sanity check the data node and ensure that it is not already in this graph + EX_LOG(0, str(boost::format("add_data_node(%s)") % data_node->get_name())); + if (data_node->get_class() == CLASS_WORKER) { + throw uhd::runtime_error("Supplied node " + data_node->get_name() + " is not a data/property node."); + delete data_node; + } + if (_datanode_map.find(data_node->get_name()) != _datanode_map.end()) { + throw uhd::runtime_error("Data node with name " + data_node->get_name() + " already exists"); + delete data_node; + } + + try { + //Add a vertex in this graph for the data node + expert_graph_t::vertex_descriptor gr_node = boost::add_vertex(data_node, _expert_dag); + EX_LOG(1, str(boost::format("added vertex %s") % data_node->get_name())); + _datanode_map.insert(vertex_map_t::value_type(data_node->get_name(), gr_node)); + + //Add resolve callbacks + if (resolve_mode == AUTO_RESOLVE_ON_WRITE or resolve_mode == AUTO_RESOLVE_ON_READ_WRITE) { + EX_LOG(2, str(boost::format("added write callback"))); + data_node->set_write_callback(boost::bind(&expert_container_impl::resolve_from, this, _1)); + } + if (resolve_mode == AUTO_RESOLVE_ON_READ or resolve_mode == AUTO_RESOLVE_ON_READ_WRITE) { + EX_LOG(2, str(boost::format("added read callback"))); + data_node->set_read_callback(boost::bind(&expert_container_impl::resolve_to, this, _1)); + } + } catch (...) { + clear(); + throw uhd::assertion_error("Unknown unrecoverable error adding data node. Cleared expert container."); + } + } + + void add_worker(worker_node_t* worker) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + //Sanity check node pointer + if (worker == NULL) { + throw uhd::runtime_error("NULL worker passed into expert container for registration."); + } + + //Sanity check the data node and ensure that it is not already in this graph + EX_LOG(0, str(boost::format("add_worker(%s)") % worker->get_name())); + if (worker->get_class() != CLASS_WORKER) { + throw uhd::runtime_error("Supplied node " + worker->get_name() + " is not a worker node."); + delete worker; + } + if (_worker_map.find(worker->get_name()) != _worker_map.end()) { + throw uhd::runtime_error("Resolver with name " + worker->get_name() + " already exists."); + delete worker; + } + + try { + //Add a vertex in this graph for the worker node + expert_graph_t::vertex_descriptor gr_node = boost::add_vertex(worker, _expert_dag); + EX_LOG(1, str(boost::format("added vertex %s") % worker->get_name())); + _worker_map.insert(vertex_map_t::value_type(worker->get_name(), gr_node)); + + //For each input, add an edge from the input to this node + BOOST_FOREACH(const std::string& node_name, worker->get_inputs()) { + vertex_map_t::const_iterator node = _datanode_map.find(node_name); + if (node != _datanode_map.end()) { + boost::add_edge((*node).second, gr_node, _expert_dag); + EX_LOG(1, str(boost::format("added edge %s->%s") % _expert_dag[(*node).second]->get_name() % _expert_dag[gr_node]->get_name())); + } else { + throw uhd::runtime_error("Data node with name " + node_name + " was not found"); + } + } + + //For each output, add an edge from this node to the output + BOOST_FOREACH(const std::string& node_name, worker->get_outputs()) { + vertex_map_t::const_iterator node = _datanode_map.find(node_name); + if (node != _datanode_map.end()) { + boost::add_edge(gr_node, (*node).second, _expert_dag); + EX_LOG(1, str(boost::format("added edge %s->%s") % _expert_dag[gr_node]->get_name() % _expert_dag[(*node).second]->get_name())); + } else { + throw uhd::runtime_error("Data node with name " + node_name + " was not found"); + } + } + } catch (uhd::runtime_error& ex) { + clear(); + //Promote runtime_error to assertion_error + throw uhd::assertion_error(std::string(ex.what()) + " (Cleared expert container because error is unrecoverable)."); + } catch (...) { + clear(); + throw uhd::assertion_error("Unknown unrecoverable error adding worker. Cleared expert container."); + } + } + + void clear() + { + boost::lock_guard<boost::mutex> lock(_mutex); + EX_LOG(0, "clear()"); + + // Iterate through the vertices and release their node storage + typedef boost::graph_traits<expert_graph_t>::vertex_iterator vertex_iter; + for (std::pair<vertex_iter, vertex_iter> vi = boost::vertices(_expert_dag); + vi.first != vi.second; + ++vi.first + ) { + try { + delete _expert_dag[*vi.first]; + _expert_dag[*vi.first] = NULL; + } catch (...) { + //If a dag_vertex is a worker, it has a virtual dtor which + //can possibly throw an exception. We will not let that + //terminate clear() and leave things in a bad state. + } + } + + //The following calls will not throw because they all contain + //intrinsic types. + + // Release all vertices and edges in the DAG + _expert_dag.clear(); + + // Release all nodes in the map + _worker_map.clear(); + _datanode_map.clear(); + } + +private: + void _resolve_helper(std::string start, std::string stop, bool force) + { + //Sort the graph topologically. This ensures that for all dependencies, the dependant + //is always after all of its dependencies. + node_queue_t sorted_nodes; + try { + boost::topological_sort(_expert_dag, std::front_inserter(sorted_nodes)); + } catch (boost::not_a_dag&) { + std::vector<std::string> back_edges; + cycle_det_visitor cdet_vis(back_edges); + boost::depth_first_search(_expert_dag, boost::visitor(cdet_vis)); + if (not back_edges.empty()) { + std::string edges; + BOOST_FOREACH(const std::string& e, back_edges) { + edges += "* " + e + ""; + } + throw uhd::runtime_error("Cannot resolve expert because it has at least one cycle!\n" + "The following back-edges were found:" + edges); + } + } + if (sorted_nodes.empty()) return; + + //Determine the start and stop node. If one is not explicitly specified then + //resolve everything + expert_graph_t::vertex_descriptor start_vertex = sorted_nodes.front(); + expert_graph_t::vertex_descriptor stop_vertex = sorted_nodes.back(); + if (not start.empty()) start_vertex = _lookup_vertex(start); + if (not stop.empty()) stop_vertex = _lookup_vertex(stop); + + //First Pass: Resolve all nodes if they are dirty, in a topological order + std::list<dag_vertex_t*> resolved_workers; + bool start_node_encountered = false; + for (node_queue_t::iterator node_iter = sorted_nodes.begin(); + node_iter != sorted_nodes.end(); + ++node_iter + ) { + //Determine if we are at or beyond the starting node + if (*node_iter == start_vertex) start_node_encountered = true; + + //Only resolve if the starting node has passed + if (start_node_encountered) { + dag_vertex_t& node = _get_vertex(*node_iter); + std::string node_val; + if (force or node.is_dirty()) { + node.resolve(); + if (node.get_class() == CLASS_WORKER) { + resolved_workers.push_back(&node); + } + EX_LOG(1, str(boost::format("resolved node %s (%s) [%s]") % + node.get_name() % (node.is_dirty()?"dirty":"clean") % node.to_string())); + } else { + EX_LOG(1, str(boost::format("skipped node %s (%s) [%s]") % + node.get_name() % (node.is_dirty()?"dirty":"clean") % node.to_string())); + } + } + + //Determine if we are beyond the stop node + if (*node_iter == stop_vertex) break; + } + + //Second Pass: Mark all the workers clean. The policy is that a worker will mark all of + //its dependencies clean so after this step all data nodes that are not consumed by a worker + //will remain dirty (as they should because no one has consumed their value) + for (std::list<dag_vertex_t*>::iterator worker = resolved_workers.begin(); + worker != resolved_workers.end(); + ++worker + ) { + (*worker)->mark_clean(); + } + } + + expert_graph_t::vertex_descriptor _lookup_vertex(const std::string& name) const + { + expert_graph_t::vertex_descriptor vertex; + //Look for node in the data-node map + vertex_map_t::const_iterator vertex_iter = _datanode_map.find(name); + if (vertex_iter != _datanode_map.end()) { + vertex = (*vertex_iter).second; + } else { + //If not found, look in the worker-node map + vertex_iter = _worker_map.find(name); + if (vertex_iter != _worker_map.end()) { + vertex = (*vertex_iter).second; + } else { + throw uhd::lookup_error("Could not find node with name " + name); + } + } + return vertex; + } + + dag_vertex_t& _get_vertex(expert_graph_t::vertex_descriptor desc) const { + //Requirement: Node must exist in expert graph + dag_vertex_t* vertex_ptr = _expert_dag[desc]; + if (vertex_ptr) { + return *vertex_ptr; + } else { + throw uhd::assertion_error("Expert graph malformed. Found a NULL node."); + } + } + + void _log(size_t depth, const std::string& str) const + { + std::string indents; + for (size_t i = 0; i < depth; i++) indents += "- "; + UHD_MSG(fastpath) << "[expert::" + _name + "] " << indents << str << std::endl; + } + +private: + const std::string _name; + expert_graph_t _expert_dag; //The primary graph data structure as an adjacency list + vertex_map_t _worker_map; //A map from vertex name to vertex descriptor for workers + vertex_map_t _datanode_map; //A map from vertex name to vertex descriptor for data nodes + boost::mutex _mutex; + boost::recursive_mutex _resolve_mutex; +}; + +expert_container::sptr expert_container::make(const std::string& name) +{ + return boost::make_shared<expert_container_impl>(name); +} + +}} diff --git a/host/lib/experts/expert_container.hpp b/host/lib/experts/expert_container.hpp new file mode 100644 index 000000000..7e626dcc5 --- /dev/null +++ b/host/lib/experts/expert_container.hpp @@ -0,0 +1,202 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP + +#include "expert_nodes.hpp" +#include <uhd/config.hpp> +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/thread/recursive_mutex.hpp> + +namespace uhd { namespace experts { + + enum auto_resolve_mode_t { + AUTO_RESOLVE_OFF, + AUTO_RESOLVE_ON_READ, + AUTO_RESOLVE_ON_WRITE, + AUTO_RESOLVE_ON_READ_WRITE + }; + + class UHD_API expert_container : private boost::noncopyable, public node_retriever_t { + public: //Methods + typedef boost::shared_ptr<expert_container> sptr; + + virtual ~expert_container() {}; + + /*! + * Return the name of this container + */ + virtual const std::string& get_name() const = 0; + + /*! + * Resolves all the nodes in this expert graph. + * + * Dependency analysis is performed on the graph and nodes + * are resolved in a topologically sorted order to ensure + * that no nodes receive stale data. + * Nodes and their dependencies are resolved only if they are + * dirty i.e. their contained values have changed since the + * last resolve. + * This call requires an acyclic expert graph. + * + * \param force If true then ignore dirty state and resolve all nodes + * \throws uhd::runtime_error if graph cannot be resolved + */ + virtual void resolve_all(bool force = false) = 0; + + /*! + * Resolves all the nodes that depend on the specified node. + * + * Dependency analysis is performed on the graph and nodes + * are resolved in a topologically sorted order to ensure + * that no nodes receive stale data. + * Nodes and their dependencies are resolved only if they are + * dirty i.e. their contained values have changed since the + * last resolve. + * This call requires an acyclic expert graph. + * + * \param node_name Name of the node to start resolving from + * \throws uhd::lookup_error if node_name not in container + * \throws uhd::runtime_error if graph cannot be resolved + * + */ + virtual void resolve_from(const std::string& node_name) = 0; + + /*! + * Resolves all the specified node and all of its dependencies. + * + * Dependency analysis is performed on the graph and nodes + * are resolved in a topologically sorted order to ensure + * that no nodes receive stale data. + * Nodes and their dependencies are resolved only if they are + * dirty i.e. their contained values have changed since the + * last resolve. + * This call requires an acyclic expert graph. + * + * \param node_name Name of the node to resolve + * \throws uhd::lookup_error if node_name not in container + * \throws uhd::runtime_error if graph cannot be resolved + * + */ + virtual void resolve_to(const std::string& node_name) = 0; + + /*! + * Return a node retriever object for this container + */ + virtual const node_retriever_t& node_retriever() const = 0; + + /*! + * Returns a DOT (graph description language) representation + * of the expert graph. The output has labels for the node + * name, node type (data or worker) and the underlying + * data type for each node. + * + */ + virtual std::string to_dot() const = 0; + + /*! + * Runs several sanity checks on the underlying graph to + * flag dependency issues. Outputs of the checks are + * logged to the console so UHD_EXPERTS_VERBOSE_LOGGING + * must be enabled to see the results + * + */ + virtual void debug_audit() const = 0; + + private: + /*! + * Lookup a node with the specified name in the contained graph + * + * If the node is found, a reference to the node is returned. + * If the node is not found, uhd::lookup_error is thrown + * lookup can return a data or a worker node + * \implements uhd::experts::node_retriever_t + * + * \param name Name of the node to find + * + */ + virtual const dag_vertex_t& lookup(const std::string& name) const = 0; + virtual dag_vertex_t& retrieve(const std::string& name) const = 0; + + /*! + * expert_factory is a friend of expert_container and + * handles all operations that change the structure of + * the underlying dependency graph. + * The expert_container instance owns all data and worker + * nodes and is responsible for release storage on destruction. + * However, the expert_factory allocates storage for the + * node and passes them into the expert_container using the + * following "protected" API calls. + * + */ + friend class expert_factory; + + /*! + * Creates an empty instance of expert_container with the + * specified name. + * + * \param name Name of the container + */ + static sptr make(const std::string& name); + + /*! + * Returns a reference to the resolver mutex. + * + * The resolver mutex guarantees that external operations + * to data-nodes are serialized with resolves of this + * container. + * + */ + virtual boost::recursive_mutex& resolve_mutex() = 0; + + /*! + * Add a data node to the expert graph + * + * \param data_node Pointer to a fully constructed data node object + * \resolve_mode Auto resolve options: Choose from "disabled" and resolve on "read", "write" or "both" + * \throws uhd::runtime_error if node already exists or is of a wrong type (recoverable) + * \throws uhd::assertion_error for other failures (unrecoverable. will clear the graph) + * + */ + virtual void add_data_node(dag_vertex_t* data_node, auto_resolve_mode_t resolve_mode = AUTO_RESOLVE_OFF) = 0; + + /*! + * Add a worker node to the expert graph + * + * \param worker Pointer to a fully constructed worker object + * \throws uhd::runtime_error if worker already exists or is of a wrong type (recoverable) + * \throws uhd::assertion_error for other failures (unrecoverable. will clear the graph) + * + */ + virtual void add_worker(worker_node_t* worker) = 0; + + /*! + * Release all storage for this object. This will delete all contained + * data and worker nodes and remove all dependency relationship and + * resolve callbacks. + * + * The object will be restored to its newly constructed state. Will not + * throw. + */ + virtual void clear() = 0; + }; + +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP */ diff --git a/host/lib/experts/expert_factory.cpp b/host/lib/experts/expert_factory.cpp new file mode 100644 index 000000000..f887ad4a3 --- /dev/null +++ b/host/lib/experts/expert_factory.cpp @@ -0,0 +1,27 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expert_factory.hpp" + +namespace uhd { namespace experts { + +expert_container::sptr expert_factory::create_container(const std::string& name) +{ + return expert_container::make(name); +} + +}} diff --git a/host/lib/experts/expert_factory.hpp b/host/lib/experts/expert_factory.hpp new file mode 100644 index 000000000..83369117d --- /dev/null +++ b/host/lib/experts/expert_factory.hpp @@ -0,0 +1,337 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP + +#include "expert_container.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/config.hpp> +#include <boost/noncopyable.hpp> +#include <boost/bind.hpp> +#include <memory> + +namespace uhd { namespace experts { + + /*! + * expert_factory is a friend of expert_container and + * handles all operations to create and change the structure of + * the an expert container. + * The expert_factory allocates storage for the nodes in the + * expert_container and passes allocated objects to the container + * using private APIs. The expert_container instance owns all + * data and workernodes and is responsible for releasing their + * storage on destruction. + * + */ + class UHD_API expert_factory : public boost::noncopyable { + public: + + /*! + * Creates an empty instance of expert_container with the + * specified name. + * + * \param name Name of the container + */ + static expert_container::sptr create_container( + const std::string& name + ); + + /*! + * Add a data node to the expert graph. + * + * \param container A shared pointer to the container to add the node to + * \param name The name of the data node + * \param init_val The initial value of the data node + * \param mode The auto resolve mode + * + * Requirements for data_t + * - Must have a default constructor + * - Must have a copy constructor + * - Must have an assignment operator (=) + * - Must have an equality operator (==) + */ + template<typename data_t> + inline static void add_data_node( + expert_container::sptr container, + const std::string& name, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + container->add_data_node(new data_node_t<data_t>(name, init_val), mode); + } + + /*! + * Add a expert property to a property tree AND an expert graph + * + * \param container A shared pointer to the expert container to add the node to + * \param subtree A shared pointer to subtree to add the property to + * \param path The path of the property in the subtree + * \param name The name of the data node in the expert graph + * \param init_val The initial value of the data node + * \param mode The auto resolve mode + * + * Requirements for data_t + * - Must have a default constructor + * - Must have a copy constructor + * - Must have an assignment operator (=) + * - Must have an equality operator (==) + */ + template<typename data_t> + inline static property<data_t>& add_prop_node( + expert_container::sptr container, + property_tree::sptr subtree, + const fs_path &path, + const std::string& name, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + property<data_t>& prop = subtree->create<data_t>(path, property_tree::MANUAL_COERCE); + data_node_t<data_t>* node_ptr = + new data_node_t<data_t>(name, init_val, &container->resolve_mutex()); + prop.set(init_val); + prop.add_desired_subscriber(boost::bind(&data_node_t<data_t>::commit, node_ptr, _1)); + prop.set_publisher(boost::bind(&data_node_t<data_t>::retrieve, node_ptr)); + container->add_data_node(node_ptr, mode); + return prop; + } + + /*! + * Add a expert property to a property tree AND an expert graph. + * The property is registered with the path as the identifier for + * both the property subtree and the expert container + * + * \param container A shared pointer to the expert container to add the node to + * \param subtree A shared pointer to subtree to add the property to + * \param path The path of the property in the subtree + * \param init_val The initial value of the data node + * \param mode The auto resolve mode + * + */ + template<typename data_t> + inline static property<data_t>& add_prop_node( + expert_container::sptr container, + property_tree::sptr subtree, + const fs_path &path, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + return add_prop_node(container, subtree, path, path, init_val, mode); + } + + /*! + * Add a dual expert property to a property tree AND an expert graph. + * A dual property is a desired and coerced value pair + * + * \param container A shared pointer to the expert container to add the node to + * \param subtree A shared pointer to subtree to add the property to + * \param path The path of the property in the subtree + * \param desired_name The name of the desired data node in the expert graph + * \param desired_name The name of the coerced data node in the expert graph + * \param init_val The initial value of both the data nodes + * \param mode The auto resolve mode + * + * Requirements for data_t + * - Must have a default constructor + * - Must have a copy constructor + * - Must have an assignment operator (=) + * - Must have an equality operator (==) + */ + template<typename data_t> + inline static property<data_t>& add_dual_prop_node( + expert_container::sptr container, + property_tree::sptr subtree, + const fs_path &path, + const std::string& desired_name, + const std::string& coerced_name, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + bool auto_resolve_desired = (mode==AUTO_RESOLVE_ON_WRITE or mode==AUTO_RESOLVE_ON_READ_WRITE); + bool auto_resolve_coerced = (mode==AUTO_RESOLVE_ON_READ or mode==AUTO_RESOLVE_ON_READ_WRITE); + + property<data_t>& prop = subtree->create<data_t>(path, property_tree::MANUAL_COERCE); + data_node_t<data_t>* desired_node_ptr = + new data_node_t<data_t>(desired_name, init_val, &container->resolve_mutex()); + data_node_t<data_t>* coerced_node_ptr = + new data_node_t<data_t>(coerced_name, init_val, &container->resolve_mutex()); + prop.set(init_val); + prop.set_coerced(init_val); + prop.add_desired_subscriber(boost::bind(&data_node_t<data_t>::commit, desired_node_ptr, _1)); + prop.set_publisher(boost::bind(&data_node_t<data_t>::retrieve, coerced_node_ptr)); + + container->add_data_node(desired_node_ptr, + auto_resolve_desired ? AUTO_RESOLVE_ON_WRITE : AUTO_RESOLVE_OFF); + container->add_data_node(coerced_node_ptr, + auto_resolve_coerced ? AUTO_RESOLVE_ON_READ : AUTO_RESOLVE_OFF); + return prop; + } + + /*! + * Add a dual expert property to a property tree AND an expert graph. + * A dual property is a desired and coerced value pair + * The property is registered with path/desired as the desired node + * name and path/coerced as the coerced node name + * + * \param container A shared pointer to the expert container to add the node to + * \param subtree A shared pointer to subtree to add the property to + * \param path The path of the property in the subtree + * \param init_val The initial value of both the data nodes + * \param mode The auto resolve mode + * + */ + template<typename data_t> + inline static property<data_t>& add_dual_prop_node( + expert_container::sptr container, + property_tree::sptr subtree, + const fs_path &path, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + return add_dual_prop_node(container, subtree, path, path + "/desired", path + "/coerced", init_val, mode); + } + + /*! + * Add a worker node to the expert graph. + * The expert_container owns and manages storage for the worker + * + * \tparam worker_t Data type of the worker class + * + * \param container A shared pointer to the container to add the node to + * + */ + template<typename worker_t> + inline static void add_worker_node( + expert_container::sptr container + ) { + container->add_worker(new worker_t()); + } + + /*! + * Add a worker node to the expert graph. + * The expert_container owns and manages storage for the worker + * + * \tparam worker_t Data type of the worker class + * \tparam arg1_t Data type of the first argument to the constructor + * \tparam ... + * \tparam argN_t Data type of the Nth argument to the constructor + * + * \param container A shared pointer to the container to add the node to + * \param arg1 First arg to ctor + * \param ... + * \param argN Nth arg to ctor + * + */ + template<typename worker_t, typename arg1_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1 + ) { + container->add_worker(new worker_t(arg1)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2 + ) { + container->add_worker(new worker_t(arg1, arg2)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, + typename arg5_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4, + arg5_t const & arg5 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, + typename arg5_t, typename arg6_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4, + arg5_t const & arg5, + arg6_t const & arg6 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, + typename arg5_t, typename arg6_t, typename arg7_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4, + arg5_t const & arg5, + arg6_t const & arg6, + arg7_t const & arg7 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6, arg7)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, + typename arg5_t, typename arg6_t, typename arg7_t, typename arg8_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4, + arg5_t const & arg5, + arg6_t const & arg6, + arg7_t const & arg7, + arg7_t const & arg8 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)); + } + }; +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP */ diff --git a/host/lib/experts/expert_nodes.hpp b/host/lib/experts/expert_nodes.hpp new file mode 100644 index 000000000..dc5cc934b --- /dev/null +++ b/host/lib/experts/expert_nodes.hpp @@ -0,0 +1,475 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_EXPERTS_EXPERT_NODES_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_NODES_HPP + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/dirty_tracked.hpp> +#include <boost/function.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/recursive_mutex.hpp> +#include <boost/thread.hpp> +#include <boost/units/detail/utility.hpp> +#include <memory> +#include <list> + +namespace uhd { namespace experts { + + enum node_class_t { CLASS_WORKER, CLASS_DATA, CLASS_PROPERTY }; + enum node_access_t { ACCESS_READER, ACCESS_WRITER }; + enum node_author_t { AUTHOR_NONE, AUTHOR_USER, AUTHOR_EXPERT }; + + /*!--------------------------------------------------------- + * class dag_vertex_t + * + * This serves as the base class for all nodes in the expert + * graph. Data nodes and workers are derived from this class. + * --------------------------------------------------------- + */ + class dag_vertex_t : private boost::noncopyable { + public: + typedef boost::function<void(std::string)> callback_func_t; + + virtual ~dag_vertex_t() {} + + // Getters for basic info about the node + inline node_class_t get_class() const { + return _class; + } + + inline const std::string& get_name() const { + return _name; + } + + virtual const std::string& get_dtype() const = 0; + + virtual std::string to_string() const = 0; + + // Graph resolution specific + virtual bool is_dirty() const = 0; + virtual void mark_clean() = 0; + virtual void resolve() = 0; + + // External callbacks + virtual void set_write_callback(const callback_func_t& func) = 0; + virtual bool has_write_callback() const = 0; + virtual void clear_write_callback() = 0; + virtual void set_read_callback(const callback_func_t& func) = 0; + virtual bool has_read_callback() const = 0; + virtual void clear_read_callback() = 0; + + protected: + dag_vertex_t(const node_class_t c, const std::string& n): + _class(c), _name(n) {} + + private: + const node_class_t _class; + const std::string _name; + }; + + class data_node_printer { + public: + //Generic implementation + template<typename data_t> + static std::string print(const data_t& val) { + std::ostringstream os; + os << val; + return os.str(); + } + + static std::string print(const boost::uint8_t& val) { + std::ostringstream os; + os << int(val); + return os.str(); + } + }; + + /*!--------------------------------------------------------- + * class data_node_t + * + * The data node class hold a passive piece of data in the + * expert graph. A data node is clean if its underlying data + * is clean. Access to the underlying data is provided using + * two methods: + * 1. Special accessor classes (for R/W enforcement) + * 2. External clients (via commit and retrieve). This access + * is protected by the callback mutex. + * + * Requirements for data_t + * - Must have a default constructor + * - Must have a copy constructor + * - Must have an assignment operator (=) + * - Must have an equality operator (==) + * --------------------------------------------------------- + */ + template<typename data_t> + class data_node_t : public dag_vertex_t { + public: + // A data_node_t instance can have a type of CLASS_DATA or CLASS_PROPERTY + // In general a data node is a property if it can be accessed and modified + // from the outside world (of experts) using read and write callbacks. We + // assume that if a callback mutex is passed into the data node that it will + // be accessed from the outside and tag the data node as a PROPERTY. + data_node_t(const std::string& name, boost::recursive_mutex* mutex = NULL) : + dag_vertex_t(mutex?CLASS_PROPERTY:CLASS_DATA, name), _callback_mutex(mutex), _data(), _author(AUTHOR_NONE) {} + + data_node_t(const std::string& name, const data_t& value, boost::recursive_mutex* mutex = NULL) : + dag_vertex_t(mutex?CLASS_PROPERTY:CLASS_DATA, name), _callback_mutex(mutex), _data(value), _author(AUTHOR_NONE) {} + + // Basic info + virtual const std::string& get_dtype() const { + static const std::string dtype( + boost::units::detail::demangle(typeid(data_t).name())); + return dtype; + } + + virtual std::string to_string() const { + return data_node_printer::print(get()); + } + + inline node_author_t get_author() const { + return _author; + } + + // Graph resolution specific + virtual bool is_dirty() const { + return _data.is_dirty(); + } + + virtual void mark_clean() { + _data.mark_clean(); + } + + void resolve() { + //NOP + } + + // Data node specific setters and getters (for the framework) + void set(const data_t& value) { + _data = value; + _author = AUTHOR_EXPERT; + } + + const data_t& get() const { + return _data; + } + + // Data node specific setters and getters (for external entities) + void commit(const data_t& value) { + if (_callback_mutex == NULL) throw uhd::assertion_error("node " + get_name() + " is missing the callback mutex"); + boost::lock_guard<boost::recursive_mutex> lock(*_callback_mutex); + set(value); + _author = AUTHOR_USER; + if (is_dirty() and has_write_callback()) { + _wr_callback(std::string(get_name())); //Put the name on the stack before calling + } + } + + const data_t retrieve() const { + if (_callback_mutex == NULL) throw uhd::assertion_error("node " + get_name() + " is missing the callback mutex"); + boost::lock_guard<boost::recursive_mutex> lock(*_callback_mutex); + if (has_read_callback()) { + _rd_callback(std::string(get_name())); + } + return get(); + } + + private: + // External callbacks + virtual void set_write_callback(const callback_func_t& func) { + _wr_callback = func; + } + + virtual bool has_write_callback() const { + return not _wr_callback.empty(); + } + + virtual void clear_write_callback() { + _wr_callback.clear(); + } + + virtual void set_read_callback(const callback_func_t& func) { + _rd_callback = func; + } + + virtual bool has_read_callback() const { + return not _rd_callback.empty(); + } + + virtual void clear_read_callback() { + _rd_callback.clear(); + } + + boost::recursive_mutex* _callback_mutex; + callback_func_t _rd_callback; + callback_func_t _wr_callback; + dirty_tracked<data_t> _data; + node_author_t _author; + }; + + /*!--------------------------------------------------------- + * class node_retriever_t + * + * Node storage is managed by a framework class so we need + * and interface to find and retrieve data nodes to associate + * with accessors. + * --------------------------------------------------------- + */ + class node_retriever_t { + public: + virtual ~node_retriever_t() {} + virtual const dag_vertex_t& lookup(const std::string& name) const = 0; + private: + friend class data_accessor_t; + virtual dag_vertex_t& retrieve(const std::string& name) const = 0; + }; + + /*!--------------------------------------------------------- + * class data_accessor_t + * + * Accessors provide protected access to data nodes and help + * establish dependency relationships. + * --------------------------------------------------------- + */ + class data_accessor_t { + public: + virtual ~data_accessor_t() {} + + virtual bool is_reader() const = 0; + virtual bool is_writer() const = 0; + virtual dag_vertex_t& node() const = 0; + protected: + data_accessor_t(const node_retriever_t& r, const std::string& n): + _vertex(r.retrieve(n)) {} + dag_vertex_t& _vertex; + }; + + template<typename data_t> + class data_accessor_base : public data_accessor_t { + public: + virtual ~data_accessor_base() {} + + virtual bool is_reader() const { + return _access == ACCESS_READER; + } + + virtual bool is_writer() const { + return _access == ACCESS_WRITER; + } + + inline bool is_dirty() const { + return _datanode->is_dirty(); + } + + inline node_class_t get_class() const { + return _datanode->get_class(); + } + + inline node_author_t get_author() const { + return _datanode->get_author(); + } + + protected: + data_accessor_base( + const node_retriever_t& r, const std::string& n, const node_access_t a) : + data_accessor_t(r, n), _datanode(NULL), _access(a) + { + _datanode = dynamic_cast< data_node_t<data_t>* >(&node()); + if (_datanode == NULL) { + throw uhd::type_error("Expected data type for node " + n + + " was " + boost::units::detail::demangle(typeid(data_t).name()) + + " but got " + node().get_dtype()); + } + } + + data_node_t<data_t>* _datanode; + const node_access_t _access; + + private: + virtual dag_vertex_t& node() const { + return _vertex; + } + }; + + /*!--------------------------------------------------------- + * class data_reader_t + * + * Accessor to read the value of a data node and to establish + * a data node => worker node dependency + * --------------------------------------------------------- + */ + template<typename data_t> + class data_reader_t : public data_accessor_base<data_t> { + public: + data_reader_t(const node_retriever_t& retriever, const std::string& node) : + data_accessor_base<data_t>( + retriever, node, ACCESS_READER) {} + + inline const data_t& get() const { + return data_accessor_base<data_t>::_datanode->get(); + } + + inline operator const data_t&() const { + return get(); + } + + inline bool operator==(const data_t& rhs) { + return get() == rhs; + } + + inline bool operator!=(const data_t& rhs) { + return !(get() == rhs); + } + }; + + /*!--------------------------------------------------------- + * class data_reader_t + * + * Accessor to read and write the value of a data node and + * to establish a worker node => data node dependency + * --------------------------------------------------------- + */ + template<typename data_t> + class data_writer_t : public data_accessor_base<data_t> { + public: + data_writer_t(const node_retriever_t& retriever, const std::string& node) : + data_accessor_base<data_t>( + retriever, node, ACCESS_WRITER) {} + + inline const data_t& get() const { + return data_accessor_base<data_t>::_datanode->get(); + } + + inline operator const data_t&() const { + return get(); + } + + inline bool operator==(const data_t& rhs) { + return get() == rhs; + } + + inline bool operator!=(const data_t& rhs) { + return !(get() == rhs); + } + + inline void set(const data_t& value) { + data_accessor_base<data_t>::_datanode->set(value); + } + + inline data_writer_t<data_t>& operator=(const data_t& value) { + set(value); + return *this; + } + + inline data_writer_t<data_t>& operator=(const data_writer_t<data_t>& value) { + set(value.get()); + return *this; + } +}; + + /*!--------------------------------------------------------- + * class worker_node_t + * + * A node class to implement a function that consumes + * zero or more input data nodes and emits zeroor more output + * data nodes. The worker can also operate on other non-expert + * interfaces because worker_node_t is abstract and the client + * is required to implement the "resolve" method in a subclass. + * --------------------------------------------------------- + */ + class worker_node_t : public dag_vertex_t { + public: + worker_node_t(const std::string& name) : + dag_vertex_t(CLASS_WORKER, name) {} + + // Worker node specific + std::list<std::string> get_inputs() const { + std::list<std::string> retval; + BOOST_FOREACH(data_accessor_t* acc, _inputs) { + retval.push_back(acc->node().get_name()); + } + return retval; + } + + std::list<std::string> get_outputs() const { + std::list<std::string> retval; + BOOST_FOREACH(data_accessor_t* acc, _outputs) { + retval.push_back(acc->node().get_name()); + } + return retval; + } + + protected: + // This function is used to bind data accessors + // to this worker. Accessors can be read/write + // and the binding will ensure proper dependency + // handling. + void bind_accessor(data_accessor_t& accessor) { + if (accessor.is_reader()) { + _inputs.push_back(&accessor); + } else if (accessor.is_writer()) { + _outputs.push_back(&accessor); + } else { + throw uhd::assertion_error("Invalid accessor type"); + } + } + + private: + // Graph resolution specific + virtual bool is_dirty() const { + bool inputs_dirty = false; + BOOST_FOREACH(data_accessor_t* acc, _inputs) { + inputs_dirty |= acc->node().is_dirty(); + } + return inputs_dirty; + } + + virtual void mark_clean() { + BOOST_FOREACH(data_accessor_t* acc, _inputs) { + acc->node().mark_clean(); + } + } + + virtual void resolve() = 0; + + // Basic type info + virtual const std::string& get_dtype() const { + static const std::string dtype = "<worker>"; + return dtype; + } + + virtual std::string to_string() const { + return "<worker>"; + } + + // Workers don't have callbacks so implement stubs + virtual void set_write_callback(const callback_func_t&) {} + virtual bool has_write_callback() const { return false; } + virtual void clear_write_callback() {} + virtual void set_read_callback(const callback_func_t&) {} + virtual bool has_read_callback() const { return false; } + virtual void clear_read_callback() {} + + std::list<data_accessor_t*> _inputs; + std::list<data_accessor_t*> _outputs; + }; + +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_NODE_HPP */ diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt index 1de50579f..f8ccd9814 100644 --- a/host/lib/ic_reg_maps/CMakeLists.txt +++ b/host/lib/ic_reg_maps/CMakeLists.txt @@ -122,4 +122,9 @@ LIBUHD_PYTHON_GEN_SOURCE( ${CMAKE_CURRENT_BINARY_DIR}/lmk04816_regs.hpp ) +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf5355_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/adf5355_regs.hpp +) + SET(LIBUHD_PYTHON_GEN_SOURCE_DEPS) diff --git a/host/lib/ic_reg_maps/gen_adf5355_regs.py b/host/lib/ic_reg_maps/gen_adf5355_regs.py new file mode 100755 index 000000000..db7cc09a9 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_adf5355_regs.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +int_16_bit 0[4:19] 0 +prescaler 0[20] 0 4_5, 8_9 +autocal_en 0[21] 0 disabled, enabled +reg0_reserved0 0[22:31] 0x000 +######################################################################## +## address 1 +######################################################################## +frac1_24_bit 1[4:27] 0 +reg1_reserved0 1[28:31] 0x0 +######################################################################## +## address 2 +######################################################################## +mod2_14_bit 2[4:17] 0 +frac2_14_bit 2[18:31] 0 +######################################################################## +## address 3 +######################################################################## +phase_24_bit 3[4:27] 0 +phase_adjust 3[28] 0 disabled, enabled +phase_resync 3[29] 0 disabled, enabled +sd_load_reset 3[30] 0 on_reg0_update, disabled +##reserved 3[31] 0 +######################################################################## +## address 4 +######################################################################## +counter_reset 4[4] 0 disabled, enabled +cp_three_state 4[5] 0 disabled, enabled +power_down 4[6] 0 disabled, enabled +pd_polarity 4[7] 1 negative, positive +mux_logic 4[8] 0 1_8V, 3_3V +ref_mode 4[9] 0 single, diff +<% current_setting_enums = ', '.join(map(lambda x: '_'.join(("%0.2fma"%(round(x*31.27 + 31.27)/100)).split('.')), range(0,16))) %>\ +charge_pump_current 4[10:13] 0 ${current_setting_enums} +double_buff_div 4[14] 0 disabled, enabled +r_counter_10_bit 4[15:24] 0 +reference_divide_by_2 4[25] 1 disabled, enabled +reference_doubler 4[26] 0 disabled, enabled +muxout 4[27:29] 1 3state, dvdd, dgnd, rdiv, ndiv, analog_ld, dld, reserved +reg4_reserved0 4[30:31] 0 +######################################################################## +## address 5 +######################################################################## +reg5_reserved0 5[4:31] 0x0080002 +######################################################################## +## address 6 +######################################################################## +output_power 6[4:5] 0 m4dbm, m1dbm, 2dbm, 5dbm +rf_out_a_enabled 6[6] 0 disabled, enabled +reg6_reserved0 6[7:9] 0x0 +rf_out_b_enabled 6[10] 1 enabled, disabled +mute_till_lock_detect 6[11] 0 mute_disabled, mute_enabled +reg6_reserved1 6[12] 0 +cp_bleed_current 6[13:20] 0 +rf_divider_select 6[21:23] 0 div1, div2, div4, div8, div16, div32, div64 +feedback_select 6[24] 0 divided, fundamental +reg6_reserved2 6[25:28] 0xA +negative_bleed 6[29] 0 disabled, enabled +gated_bleed 6[30] 0 disabled, enabled +reg6_reserved3 6[31] 0 +######################################################################## +## address 7 +######################################################################## +ld_mode 7[4] 0 frac_n, int_n +frac_n_ld_precision 7[5:6] 0 5ns, 6ns, 8ns, 12ns +loss_of_lock_mode 7[7] 0 disabled, enabled +ld_cyc_count 7[8:9] 0 1024, 2048, 4096, 8192 +reg7_reserved0 7[10:24] 0x0 +le_sync 7[25] 0 disabled, le_synced_to_refin +reg7_reserved1 7[26:31] 0x4 +######################################################################## +## address 8 +######################################################################## +reg8_reserved0 8[4:31] 0x102D402 +######################################################################## +## address 9 +######################################################################## +synth_lock_timeout 9[4:8] 0 +auto_level_timeout 9[9:13] 0 +timeout 9[14:23] 0 +vco_band_div 9[24:31] 0 +######################################################################## +## address 10 +######################################################################## +adc_enable 10[4] 0 disabled, enabled +adc_conversion 10[5] 0 disabled, enabled +adc_clock_divider 10[6:13] 1 +reg10_reserved0 10[14:31] 0x300 +######################################################################## +## address 11 +######################################################################## +reg11_reserved0 11[4:31] 0x0061300 +######################################################################## +## address 12 +######################################################################## +reg12_reserved0 12[4:15] 0x041 +phase_resync_clk_div 12[16:31] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +enum addr_t{ + ADDR_R0 = 0, + ADDR_R1 = 1, + ADDR_R2 = 2, + ADDR_R3 = 3, + ADDR_R4 = 4, + ADDR_R5 = 5, + ADDR_R6 = 6, + ADDR_R7 = 7, + ADDR_R8 = 8, + ADDR_R9 = 9, + ADDR_R10 = 10, + ADDR_R11 = 11, + ADDR_R12 = 12 +}; + +boost::uint32_t get_reg(boost::uint8_t addr){ + boost::uint32_t reg = addr & 0xF; + switch(addr){ + % for addr in range(12+1): + case ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + reg |= (boost::uint32_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()}; + % endfor + break; + % endfor + } + return reg; +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='adf5355_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/property_tree.cpp b/host/lib/property_tree.cpp index 039f05f12..76d7bccba 100644 --- a/host/lib/property_tree.cpp +++ b/host/lib/property_tree.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011,2014 Ettus Research LLC +// Copyright 2011,2014-2016 Ettus Research // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt new file mode 100644 index 000000000..130b4173e --- /dev/null +++ b/host/lib/rfnoc/CMakeLists.txt @@ -0,0 +1,53 @@ +# +# Copyright 2014-2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( + # Infrastructure: + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_base_factory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/blockdef_xml_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_id.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ctrl_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/graph_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/legacy_compat.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rate_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_stream_terminator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/scalar_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sink_block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sink_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source_block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/stream_sig.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tick_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tx_stream_terminator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/wb_iface_adapter.cpp + # Default block control classes: + ${CMAKE_CURRENT_SOURCE_DIR}/ddc_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/duc_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_block_ctrl_impl +) + +INCLUDE_SUBDIRECTORY(nocscript) diff --git a/host/lib/rfnoc/block_ctrl_base.cpp b/host/lib/rfnoc/block_ctrl_base.cpp new file mode 100644 index 000000000..21bd32a1c --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_base.cpp @@ -0,0 +1,587 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// This file contains the block control functions for block controller classes. +// See block_ctrl_base_factory.cpp for discovery and factory functions. + +#include "ctrl_iface.hpp" +#include "nocscript/block_iface.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/convert.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> + +#define UHD_BLOCK_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::rfnoc; +using std::string; + +/*********************************************************************** + * Helpers + **********************************************************************/ +//! Convert register to a peek/poke compatible address +inline boost::uint32_t _sr_to_addr(boost::uint32_t reg) { return reg * 4; }; +inline boost::uint32_t _sr_to_addr64(boost::uint32_t reg) { return reg * 8; }; // for peek64 + +/*********************************************************************** + * Structors + **********************************************************************/ +block_ctrl_base::block_ctrl_base( + const make_args_t &make_args +) : _tree(make_args.tree), + _transport_is_big_endian(make_args.is_big_endian), + _ctrl_ifaces(make_args.ctrl_ifaces), + _base_address(make_args.base_address & 0xFFF0) +{ + UHD_BLOCK_LOG() << "block_ctrl_base()" << std::endl; + + /*** Identify this block (NoC-ID, block-ID, and block definition) *******/ + // Read NoC-ID (name is passed in through make_args): + boost::uint64_t noc_id = sr_read64(SR_READBACK_REG_ID); + _block_def = blockdef::make_from_noc_id(noc_id); + if (_block_def) UHD_BLOCK_LOG() << "Found valid blockdef" << std::endl; + if (not _block_def) + _block_def = blockdef::make_from_noc_id(DEFAULT_NOC_ID); + UHD_ASSERT_THROW(_block_def); + // For the block ID, we start with block count 0 and increase until + // we get a block ID that's not already registered: + _block_id.set(make_args.device_index, make_args.block_name, 0); + while (_tree->exists("xbar/" + _block_id.get_local())) { + _block_id++; + } + UHD_BLOCK_LOG() + << "NOC ID: " << str(boost::format("0x%016X ") % noc_id) + << "Block ID: " << _block_id << std::endl; + + /*** Initialize property tree *******************************************/ + _root_path = "xbar/" + _block_id.get_local(); + _tree->create<boost::uint64_t>(_root_path / "noc_id").set(noc_id); + + /*** Reset block state *******************************************/ + clear(); + + /*** Configure ports ****************************************************/ + size_t n_valid_input_buffers = 0; + BOOST_FOREACH(const size_t ctrl_port, get_ctrl_ports()) { + // Set source addresses: + sr_write(SR_BLOCK_SID, get_address(ctrl_port), ctrl_port); + // Set sink buffer sizes: + settingsbus_reg_t reg = SR_READBACK_REG_FIFOSIZE; + uint64_t value = sr_read64(reg, ctrl_port); + size_t buf_size_log2 = value & 0xFF; + size_t buf_size_bytes = BYTES_PER_LINE * (1 << buf_size_log2); // Bytes == 8 * 2^x + if (buf_size_bytes > 0) n_valid_input_buffers++; + _tree->create<size_t>(_root_path / "input_buffer_size" / ctrl_port).set(buf_size_bytes); + } + + /*** Register names *****************************************************/ + blockdef::registers_t sregs = _block_def->get_settings_registers(); + BOOST_FOREACH(const std::string ®_name, sregs.keys()) { + if (DEFAULT_NAMED_SR.has_key(reg_name)) { + throw uhd::runtime_error(str( + boost::format("Register name %s is already defined!") + % reg_name + )); + } + _tree->create<size_t>(_root_path / "registers" / "sr" / reg_name) + .set(sregs.get(reg_name)); + } + blockdef::registers_t rbacks = _block_def->get_readback_registers(); + BOOST_FOREACH(const std::string ®_name, rbacks.keys()) { + _tree->create<size_t>(_root_path / "registers"/ "rb" / reg_name) + .set(rbacks.get(reg_name)); + } + + /*** Init I/O port definitions ******************************************/ + _init_port_defs("in", _block_def->get_input_ports()); + _init_port_defs("out", _block_def->get_output_ports()); + // FIXME this warning always fails until the input buffer code above is fixed + if (_tree->list(_root_path / "ports/in").size() != n_valid_input_buffers) { + UHD_MSG(warning) << + boost::format("[%s] defines %d input buffer sizes, but %d input ports") + % get_block_id().get() % n_valid_input_buffers % _tree->list(_root_path / "ports/in").size() + << std::endl; + } + + /*** Init default block args ********************************************/ + _nocscript_iface = nocscript::block_iface::make(this); + _init_block_args(); +} + +block_ctrl_base::~block_ctrl_base() +{ + _tree->remove(_root_path); +} + +void block_ctrl_base::_init_port_defs( + const std::string &direction, + blockdef::ports_t ports, + const size_t first_port_index +) { + size_t port_index = first_port_index; + BOOST_FOREACH(const blockdef::port_t &port_def, ports) { + fs_path port_path = _root_path / "ports" / direction / port_index; + if (not _tree->exists(port_path)) { + _tree->create<blockdef::port_t>(port_path); + } + UHD_RFNOC_BLOCK_TRACE() << "Adding port definition at " << port_path + << boost::format(": type = '%s' pkt_size = '%s' vlen = '%s'") % port_def["type"] % port_def["pkt_size"] % port_def["vlen"] + << std::endl; + _tree->access<blockdef::port_t>(port_path).set(port_def); + port_index++; + } +} + +void block_ctrl_base::_init_block_args() +{ + blockdef::args_t args = _block_def->get_args(); + fs_path arg_path = _root_path / "args"; + BOOST_FOREACH(const size_t port, get_ctrl_ports()) { + _tree->create<std::string>(arg_path / port); + } + + // First, create all nodes. + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_type_path = arg_path / arg["port"] / arg["name"] / "type"; + _tree->create<std::string>(arg_type_path).set(arg["type"]); + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else if (arg["type"] == "int") { _tree->create<int>(arg_val_path); } + else if (arg["type"] == "double") { _tree->create<double>(arg_val_path); } + else if (arg["type"] == "string") { _tree->create<string>(arg_val_path); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + // Next: Create all the subscribers and coercers. + // TODO: Add coercer +#define _SUBSCRIBE_CHECK_AND_RUN(type, arg_tag, error_message) \ + _tree->access<type>(arg_val_path).add_coerced_subscriber(boost::bind((&nocscript::block_iface::run_and_check), _nocscript_iface, arg[#arg_tag], error_message)) + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (not arg["check"].empty()) { + if (arg["type"] == "string") { _SUBSCRIBE_CHECK_AND_RUN(string, check, arg["check_message"]); } + else if (arg["type"] == "int") { _SUBSCRIBE_CHECK_AND_RUN(int, check, arg["check_message"]); } + else if (arg["type"] == "double") { _SUBSCRIBE_CHECK_AND_RUN(double, check, arg["check_message"]); } + else if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + if (not arg["action"].empty()) { + if (arg["type"] == "string") { _SUBSCRIBE_CHECK_AND_RUN(string, action, ""); } + else if (arg["type"] == "int") { _SUBSCRIBE_CHECK_AND_RUN(int, action, ""); } + else if (arg["type"] == "double") { _SUBSCRIBE_CHECK_AND_RUN(double, action, ""); } + else if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + } + + // Finally: Set the values. This will call subscribers, if we have any. + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (not arg["value"].empty()) { + if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else if (arg["type"] == "int") { _tree->access<int>(arg_val_path).set(boost::lexical_cast<int>(arg["value"])); } + else if (arg["type"] == "double") { _tree->access<double>(arg_val_path).set(boost::lexical_cast<double>(arg["value"])); } + else if (arg["type"] == "string") { _tree->access<string>(arg_val_path).set(arg["value"]); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + } +} + +/*********************************************************************** + * FPGA control & communication + **********************************************************************/ +wb_iface::sptr block_ctrl_base::get_ctrl_iface(const size_t block_port) +{ + return _ctrl_ifaces[block_port]; +} + +std::vector<size_t> block_ctrl_base::get_ctrl_ports() const +{ + std::vector<size_t> ctrl_ports; + ctrl_ports.reserve(_ctrl_ifaces.size()); + std::pair<size_t, wb_iface::sptr> it; + BOOST_FOREACH(it, _ctrl_ifaces) { + ctrl_ports.push_back(it.first); + } + return ctrl_ports; +} + +void block_ctrl_base::sr_write(const boost::uint32_t reg, const boost::uint32_t data, const size_t port) +{ + //UHD_BLOCK_LOG() << " "; + //UHD_RFNOC_BLOCK_TRACE() << boost::format("sr_write(%d, %08X, %d)") % reg % data % port << std::endl; + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_write(): No such port: %d") % get_block_id().get() % port)); + } + try { + _ctrl_ifaces[port]->poke32(_sr_to_addr(reg), data); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_write() failed: %s") % get_block_id().get() % ex.what())); + } +} + +void block_ctrl_base::sr_write(const std::string ®, const boost::uint32_t data, const size_t port) +{ + boost::uint32_t reg_addr = 255; + if (DEFAULT_NAMED_SR.has_key(reg)) { + reg_addr = DEFAULT_NAMED_SR[reg]; + } else { + if (not _tree->exists(_root_path / "registers" / "sr" / reg)) { + throw uhd::key_error(str( + boost::format("Unknown settings register name: %s") + % reg + )); + } + reg_addr = boost::uint32_t(_tree->access<size_t>(_root_path / "registers" / "sr" / reg).get()); + } + UHD_BLOCK_LOG() << " "; + UHD_RFNOC_BLOCK_TRACE() << boost::format("sr_write(%s, %08X) ==> ") % reg % data << std::endl; + return sr_write(reg_addr, data, port); +} + +boost::uint64_t block_ctrl_base::sr_read64(const settingsbus_reg_t reg, const size_t port) +{ + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_read64(): No such port: %d") % get_block_id().get() % port)); + } + try { + return _ctrl_ifaces[port]->peek64(_sr_to_addr64(reg)); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_read64() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint32_t block_ctrl_base::sr_read32(const settingsbus_reg_t reg, const size_t port) +{ + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_read32(): No such port: %d") % get_block_id().get() % port)); + } + try { + return _ctrl_ifaces[port]->peek32(_sr_to_addr64(reg)); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_read32() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint64_t block_ctrl_base::user_reg_read64(const boost::uint32_t addr, const size_t port) +{ + try { + // Set readback register address + sr_write(SR_READBACK_ADDR, addr, port); + // Read readback register via RFNoC + return sr_read64(SR_READBACK_REG_USER, port); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("%s user_reg_read64() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint64_t block_ctrl_base::user_reg_read64(const std::string ®, const size_t port) +{ + if (not _tree->exists(_root_path / "registers" / "rb" / reg)) { + throw uhd::key_error(str( + boost::format("Invalid readback register name: %s") + % reg + )); + } + return user_reg_read64(boost::uint32_t( + _tree->access<size_t>(_root_path / "registers" / "rb" / reg).get() + ), port); +} + +boost::uint32_t block_ctrl_base::user_reg_read32(const boost::uint32_t addr, const size_t port) +{ + try { + // Set readback register address + sr_write(SR_READBACK_ADDR, addr, port); + // Read readback register via RFNoC + return sr_read32(SR_READBACK_REG_USER, port); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] user_reg_read32() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint32_t block_ctrl_base::user_reg_read32(const std::string ®, const size_t port) +{ + if (not _tree->exists(_root_path / "registers" / "rb" / reg)) { + throw uhd::key_error(str( + boost::format("Invalid readback register name: %s") + % reg + )); + } + return user_reg_read32(boost::uint32_t( + _tree->access<size_t>(_root_path / "registers" / "sr" / reg).get() + ), port); +} + +void block_ctrl_base::set_command_time( + const time_spec_t &time_spec, + const size_t port +) { + if (port == ANY_PORT) { + BOOST_FOREACH(const size_t specific_port, get_ctrl_ports()) { + set_command_time(time_spec, specific_port); + } + return; + } + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_time(time_spec); +} + +time_spec_t block_ctrl_base::get_command_time( + const size_t port +) { + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot get command time on port '%d'") + % unique_id() % port + )); + } + + return iface_sptr->get_time(); +} + +void block_ctrl_base::set_command_tick_rate( + const double tick_rate, + const size_t port +) { + if (port == ANY_PORT) { + BOOST_FOREACH(const size_t specific_port, get_ctrl_ports()) { + set_command_tick_rate(tick_rate, specific_port); + } + return; + } + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_tick_rate(tick_rate); +} + +void block_ctrl_base::clear_command_time(const size_t port) +{ + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_time(time_spec_t(0.0)); +} + +void block_ctrl_base::clear(const size_t /* port */) +{ + UHD_RFNOC_BLOCK_TRACE() << "block_ctrl_base::clear() " << std::endl; + // Call parent... + node_ctrl_base::clear(); + // ...then child + BOOST_FOREACH(const size_t port_index, get_ctrl_ports()) { + _clear(port_index); + } +} + +boost::uint32_t block_ctrl_base::get_address(size_t block_port) { + UHD_ASSERT_THROW(block_port < 16); + return (_base_address & 0xFFF0) | (block_port & 0xF); +} + +/*********************************************************************** + * Argument handling + **********************************************************************/ +void block_ctrl_base::set_args(const uhd::device_addr_t &args, const size_t port) +{ + BOOST_FOREACH(const std::string &key, args.keys()) { + if (_tree->exists(get_arg_path(key, port))) { + set_arg(key, args.get(key), port); + } + } +} + +void block_ctrl_base::set_arg(const std::string &key, const std::string &val, const size_t port) +{ + fs_path arg_path = get_arg_path(key, port); + if (not _tree->exists(arg_path / "value")) { + throw uhd::runtime_error(str( + boost::format("Attempting to set uninitialized argument '%s' on block '%s'") + % key % unique_id() + )); + } + + std::string type = _tree->access<std::string>(arg_path / "type").get(); + fs_path arg_val_path = arg_path / "value"; + try { + if (type == "string") { + _tree->access<std::string>(arg_val_path).set(val); + } + else if (type == "int") { + _tree->access<int>(arg_val_path).set(boost::lexical_cast<int>(val)); + } + else if (type == "double") { + _tree->access<double>(arg_val_path).set(boost::lexical_cast<double>(val)); + } + else if (type == "int_vector") { + throw uhd::runtime_error("not yet implemented: int_vector"); + } + } catch (const boost::bad_lexical_cast &) { + throw uhd::value_error(str( + boost::format("Error trying to cast value %s == '%s' to type '%s'") + % key % val % type + )); + } +} + +device_addr_t block_ctrl_base::get_args(const size_t port) const +{ + device_addr_t args; + BOOST_FOREACH(const std::string &key, _tree->list(_root_path / "args" / port)) { + args[key] = get_arg(key); + } + return args; +} + +std::string block_ctrl_base::get_arg(const std::string &key, const size_t port) const +{ + fs_path arg_path = get_arg_path(key, port); + if (not _tree->exists(arg_path / "value")) { + throw uhd::runtime_error(str( + boost::format("Attempting to get uninitialized argument '%s' on block '%s'") + % key % unique_id() + )); + } + + std::string type = _tree->access<std::string>(arg_path / "type").get(); + fs_path arg_val_path = arg_path / "value"; + if (type == "string") { + return _tree->access<std::string>(arg_val_path).get(); + } + else if (type == "int") { + return boost::lexical_cast<std::string>(_tree->access<int>(arg_val_path).get()); + } + else if (type == "double") { + return boost::lexical_cast<std::string>(_tree->access<double>(arg_val_path).get()); + } + else if (type == "int_vector") { + throw uhd::runtime_error("not yet implemented: int_vector"); + } + + UHD_THROW_INVALID_CODE_PATH(); + return ""; +} + +std::string block_ctrl_base::get_arg_type(const std::string &key, const size_t port) const +{ + fs_path arg_type_path = _root_path / "args" / port / key / "type"; + return _tree->access<std::string>(arg_type_path).get(); +} + +stream_sig_t block_ctrl_base::_resolve_port_def(const blockdef::port_t &port_def) const +{ + if (not port_def.is_valid()) { + throw uhd::runtime_error(str( + boost::format("Invalid port definition: %s") % port_def.to_string() + )); + } + + // TODO this entire section is pretty dumb at this point. Needs better + // checks. + stream_sig_t stream_sig; + // Item Type + if (port_def.is_variable("type")) { + std::string var_name = port_def["type"].substr(1); + // TODO check this is even a string + stream_sig.item_type = get_arg(var_name); + } else if (port_def.is_keyword("type")) { + throw uhd::runtime_error("keywords resolution for type not yet implemented"); + } else { + stream_sig.item_type = port_def["type"]; + } + //UHD_RFNOC_BLOCK_TRACE() << " item type: " << stream_sig.item_type << std::endl; + + // Vector length + if (port_def.is_variable("vlen")) { + std::string var_name = port_def["vlen"].substr(1); + stream_sig.vlen = boost::lexical_cast<size_t>(get_arg(var_name)); + } else if (port_def.is_keyword("vlen")) { + throw uhd::runtime_error("keywords resolution for vlen not yet implemented"); + } else { + stream_sig.vlen = boost::lexical_cast<size_t>(port_def["vlen"]); + } + //UHD_RFNOC_BLOCK_TRACE() << " vector length: " << stream_sig.vlen << std::endl; + + // Packet size + if (port_def.is_variable("pkt_size")) { + std::string var_name = port_def["pkt_size"].substr(1); + stream_sig.packet_size = boost::lexical_cast<size_t>(get_arg(var_name)); + } else if (port_def.is_keyword("pkt_size")) { + if (port_def["pkt_size"] != "%vlen") { + throw uhd::runtime_error("generic keywords resolution for pkt_size not yet implemented"); + } + if (stream_sig.vlen == 0) { + stream_sig.packet_size = 0; + } else { + if (stream_sig.item_type.empty()) { + throw uhd::runtime_error("cannot resolve pkt_size if item type is not given"); + } + size_t bpi = uhd::convert::get_bytes_per_item(stream_sig.item_type); + stream_sig.packet_size = stream_sig.vlen * bpi; + } + } else { + stream_sig.packet_size = boost::lexical_cast<size_t>(port_def["pkt_size"]); + } + //UHD_RFNOC_BLOCK_TRACE() << " packet size: " << stream_sig.vlen << std::endl; + + return stream_sig; +} + + +/*********************************************************************** + * Hooks & Derivables + **********************************************************************/ +void block_ctrl_base::_clear(const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "block_ctrl_base::_clear() " << std::endl; + sr_write(SR_CLEAR_TX_FC, 0x00C1EA12, port); // 'CLEAR', but we can write anything, really + sr_write(SR_CLEAR_RX_FC, 0x00C1EA12, port); // 'CLEAR', but we can write anything, really +} + +// vim: sw=4 et: diff --git a/host/lib/rfnoc/block_ctrl_base_factory.cpp b/host/lib/rfnoc/block_ctrl_base_factory.cpp new file mode 100644 index 000000000..aab2ed475 --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_base_factory.cpp @@ -0,0 +1,96 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/format.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> + +#define UHD_FACTORY_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::rfnoc; + +typedef uhd::dict<std::string, block_ctrl_base::make_t> block_fcn_reg_t; +// Instantiate the block function registry container +UHD_SINGLETON_FCN(block_fcn_reg_t, get_block_fcn_regs); + +void block_ctrl_base::register_block( + const make_t &make, + const std::string &key +) { + if (get_block_fcn_regs().has_key(key)) { + throw uhd::runtime_error( + str(boost::format("Attempting to register an RFNoC block with key %s for the second time.") % key) + ); + } + + get_block_fcn_regs().set(key, make); +} + +/*! Look up names for blocks in XML files using NoC ID. + */ +static void lookup_block_key(boost::uint64_t noc_id, make_args_t &make_args) +{ + try { + blockdef::sptr bd = blockdef::make_from_noc_id(noc_id); + if (not bd) { + make_args.block_key = DEFAULT_BLOCK_NAME; + make_args.block_name = DEFAULT_BLOCK_NAME; + return; + } + UHD_ASSERT_THROW(bd->is_block()); + make_args.block_key = bd->get_key(); + make_args.block_name = bd->get_name(); + return; + } catch (std::exception &e) { + UHD_MSG(warning) << str(boost::format("Error while looking up name for NoC-ID %016X.\n%s") % noc_id % e.what()) << std::endl; + } + + make_args.block_key = DEFAULT_BLOCK_NAME; + make_args.block_name = DEFAULT_BLOCK_NAME; +} + + +block_ctrl_base::sptr block_ctrl_base::make( + const make_args_t &make_args_, + boost::uint64_t noc_id +) { + UHD_FACTORY_LOG() << "[RFNoC Factory] block_ctrl_base::make() " << std::endl; + make_args_t make_args = make_args_; + + // Check if a block key was specified, in this case, we *must* either + // create a specialized block controller class or throw + if (make_args.block_key.empty()) { + lookup_block_key(noc_id, make_args); + } else if (not get_block_fcn_regs().has_key(make_args.block_key)) { + throw uhd::runtime_error( + str(boost::format("No block controller class registered for key '%s'.") % make_args.block_key) + ); + } + if (not get_block_fcn_regs().has_key(make_args.block_key)) { + make_args.block_key = DEFAULT_BLOCK_NAME; + } + if (make_args.block_name.empty()) { + make_args.block_name = make_args.block_key; + } + + UHD_FACTORY_LOG() << "[RFNoC Factory] Using controller key '" << make_args.block_key << "' and block name '" << make_args.block_name << "'" << std::endl; + return get_block_fcn_regs()[make_args.block_key](make_args); +} + diff --git a/host/lib/rfnoc/block_ctrl_impl.cpp b/host/lib/rfnoc/block_ctrl_impl.cpp new file mode 100644 index 000000000..be21538f3 --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_impl.cpp @@ -0,0 +1,33 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/block_ctrl.hpp> + +using namespace uhd::rfnoc; + +class block_ctrl_impl : public block_ctrl +{ +public: + UHD_RFNOC_BLOCK_CONSTRUCTOR(block_ctrl) + { + // nop + } + + // Very empty class, this one +}; + +UHD_RFNOC_BLOCK_REGISTER(block_ctrl, DEFAULT_BLOCK_NAME); diff --git a/host/lib/rfnoc/block_id.cpp b/host/lib/rfnoc/block_id.cpp new file mode 100644 index 000000000..55bd5e6f7 --- /dev/null +++ b/host/lib/rfnoc/block_id.cpp @@ -0,0 +1,150 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/format.hpp> +#include <boost/regex.hpp> +#include <boost/lexical_cast.hpp> +#include <uhd/exception.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/block_id.hpp> + +#include <iostream> + +using namespace uhd::rfnoc; + +block_id_t::block_id_t() : + _device_no(0), + _block_name(""), + _block_ctr(0) +{ +} + +block_id_t::block_id_t(const std::string &block_str) + : _device_no(0), + _block_name(""), + _block_ctr(0) +{ + if (not set(block_str)) { + throw uhd::value_error("block_id_t: Invalid block ID string."); + } +} + +block_id_t::block_id_t( + const size_t device_no, + const std::string &block_name, + const size_t block_ctr +) : _device_no(device_no), + _block_name(block_name), + _block_ctr(block_ctr) +{ + if (not is_valid_blockname(block_name)) { + throw uhd::value_error("block_id_t: Invalid block name."); + } +} + +bool block_id_t::is_valid_blockname(const std::string &block_name) +{ + return boost::regex_match(block_name, boost::regex(VALID_BLOCKNAME_REGEX)); +} + +bool block_id_t::is_valid_block_id(const std::string &block_name) +{ + return boost::regex_match(block_name, boost::regex(VALID_BLOCKID_REGEX)); +} + +std::string block_id_t::to_string() const +{ + return str(boost::format("%d/%s") + % get_device_no() + % get_local() + ); +} + +std::string block_id_t::get_local() const +{ + return str(boost::format("%s_%d") + % get_block_name() + % get_block_count() + ); +} + +uhd::fs_path block_id_t::get_tree_root() const +{ + return str(boost::format("/mboards/%d/xbar/%s") + % get_device_no() + % get_local() + ); +} + +bool block_id_t::match(const std::string &block_str) +{ + boost::cmatch matches; + if (not boost::regex_match(block_str.c_str(), matches, boost::regex(VALID_BLOCKID_REGEX))) { + return false; + } + try { + return (matches[1] == "" or boost::lexical_cast<size_t>(matches[1]) == _device_no) + and (matches[2] == "" or matches[2] == _block_name) + and (matches[3] == "" or boost::lexical_cast<size_t>(matches[3]) == _block_ctr) + and not (matches[1] == "" and matches[2] == "" and matches[3] == ""); + } catch (const std::bad_cast &e) { + return false; + } + return false; +} + +bool block_id_t::set(const std::string &new_name) +{ + boost::cmatch matches; + if (not boost::regex_match(new_name.c_str(), matches, boost::regex(VALID_BLOCKID_REGEX))) { + return false; + } + if (not (matches[1] == "")) { + _device_no = boost::lexical_cast<size_t>(matches[1]); + } + if (not (matches[2] == "")) { + _block_name = matches[2]; + } + if (not (matches[3] == "")) { + _block_ctr = boost::lexical_cast<size_t>(matches[3]); + } + return true; +} + +bool block_id_t::set( + const size_t device_no, + const std::string &block_name, + const size_t block_ctr +) { + if (not set_block_name(block_name)) { + return false; + } + set_device_no(device_no); + set_block_count(block_ctr); + return true; +} + +bool block_id_t::set_block_name(const std::string &block_name) +{ + if (not is_valid_blockname(block_name)) { + return false; + } + _block_name = block_name; + return true; +} + diff --git a/host/lib/rfnoc/blockdef_xml_impl.cpp b/host/lib/rfnoc/blockdef_xml_impl.cpp new file mode 100644 index 000000000..5ff69d512 --- /dev/null +++ b/host/lib/rfnoc/blockdef_xml_impl.cpp @@ -0,0 +1,442 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/exception.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> +#include <cstdlib> + +using namespace uhd; +using namespace uhd::rfnoc; +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +static const fs::path XML_BLOCKS_SUBDIR("blocks"); +static const fs::path XML_COMPONENTS_SUBDIR("components"); +static const fs::path XML_EXTENSION(".xml"); + + +/**************************************************************************** + * port_t stuff + ****************************************************************************/ +const device_addr_t blockdef::port_t::PORT_ARGS( + "name," + "type," + "vlen=0," + "pkt_size=0," + "optional=0," + "bursty=0," + "port," +); + +blockdef::port_t::port_t() +{ + // This guarantees that we can access these keys + // even if they were never initialized: + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + set(key, PORT_ARGS[key]); + } +} + +bool blockdef::port_t::is_variable(const std::string &key) const +{ + const std::string &val = get(key); + return (val[0] == '$'); +} + +bool blockdef::port_t::is_keyword(const std::string &key) const +{ + const std::string &val = get(key); + return (val[0] == '%'); +} + +bool blockdef::port_t::is_valid() const +{ + // Check we have all the keys: + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + if (not has_key(key)) { + return false; + } + } + + // Twelve of the clock, all seems well + return true; +} + +std::string blockdef::port_t::to_string() const +{ + std::string result; + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + if (has_key(key)) { + result += str(boost::format("%s=%s,") % key % get(key)); + } + } + + return result; +} + +/**************************************************************************** + * arg_t stuff + ****************************************************************************/ +const device_addr_t blockdef::arg_t::ARG_ARGS( + // List all tags/args an <arg> can have here: + "name," + "type," + "value," + "check," + "check_message," + "action," + "port=0," +); + +const std::set<std::string> blockdef::arg_t::VALID_TYPES = boost::assign::list_of + // List all tags/args a <type> can have here: + ("string") + ("int") + ("int_vector") + ("double") +; + +blockdef::arg_t::arg_t() +{ + // This guarantees that we can access these keys + // even if they were never initialized: + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + set(key, ARG_ARGS[key]); + } +} + +bool blockdef::arg_t::is_valid() const +{ + // 1. Check we have all the keys: + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + if (not has_key(key)) { + return false; + } + } + + // 2. Check arg type is valid + if (not get("type").empty() and not VALID_TYPES.count(get("type"))) { + return false; + } + + // Twelve of the clock, all seems well + return true; +} + +std::string blockdef::arg_t::to_string() const +{ + std::string result; + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + if (has_key(key)) { + result += str(boost::format("%s=%s,") % key % get(key)); + } + } + + return result; +} + +/**************************************************************************** + * blockdef_impl stuff + ****************************************************************************/ +class blockdef_xml_impl : public blockdef +{ +public: + enum xml_repr_t { + DESCRIBES_BLOCK, + DESCRIBES_COMPONENT + }; + + //! Returns a list of base paths for the XML files. + // It is assumed that block definitions are in a subdir with name + // XML_BLOCKS_SUBDIR and component definitions in a subdir with name + // XML_COMPONENTS_SUBDIR + static std::vector<boost::filesystem::path> get_xml_paths() + { + std::vector<boost::filesystem::path> paths; + + // Path from environment variable + if (std::getenv(XML_PATH_ENV.c_str()) != NULL) { + paths.push_back(boost::filesystem::path(std::getenv(XML_PATH_ENV.c_str()))); + } + + // Finally, the default path + const boost::filesystem::path pkg_path = uhd::get_pkg_path(); + paths.push_back(pkg_path / XML_DEFAULT_PATH); + + return paths; + } + + //! Matches a NoC ID through substring matching + static bool match_noc_id(const std::string &lhs_, boost::uint64_t rhs_) + { + // Sanitize input: Make both values strings with all uppercase + // characters and no leading 0x. Check inputs are valid. + std::string lhs = boost::to_upper_copy(lhs_); + std::string rhs = str(boost::format("%016X") % rhs_); + if (lhs.size() > 2 and lhs[0] == '0' and lhs[1] == 'X') { + lhs = lhs.substr(2); + } + UHD_ASSERT_THROW(rhs.size() == 16); + if (lhs.size() < 4 or lhs.size() > 16) { + throw uhd::value_error(str(boost::format( + "%s is not a valid NoC ID (must be hexadecimal, min 4 and max 16 characters)" + ) % lhs_)); + } + + // OK, all good now. Next, we try and match the substring lhs in rhs: + return (rhs.find(lhs) == 0); + } + + //! Open the file at filename and see if it's a block definition for the given NoC ID + static bool has_noc_id(boost::uint64_t noc_id, const fs::path &filename) + { + pt::ptree propt; + try { + read_xml(filename.string(), propt); + BOOST_FOREACH(pt::ptree::value_type &v, propt.get_child("nocblock.ids")) { + if (v.first == "id" and match_noc_id(v.second.data(), noc_id)) { + return true; + } + } + } catch (std::exception &e) { + UHD_MSG(warning) << "has_noc_id(): caught exception " << e.what() << std::endl; + return false; + } + return false; + } + + blockdef_xml_impl(const fs::path &filename, boost::uint64_t noc_id, xml_repr_t type=DESCRIBES_BLOCK) : + _type(type), + _noc_id(noc_id) + { + //UHD_MSG(status) << "Reading XML file: " << filename.string().c_str() << std::endl; + read_xml(filename.string(), _pt); + try { + // Check key is valid + get_key(); + // Check name is valid + get_name(); + // Check there's at least one port + ports_t in = get_input_ports(); + ports_t out = get_output_ports(); + if (in.empty() and out.empty()) { + throw uhd::runtime_error("Block does not define inputs or outputs."); + } + // Check args are valid + get_args(); + // TODO any more checks? + } catch (const std::exception &e) { + throw uhd::runtime_error(str( + boost::format("Invalid block definition in %s: %s") + % filename.string() % e.what() + )); + } + } + + bool is_block() const + { + return _type == DESCRIBES_BLOCK; + } + + bool is_component() const + { + return _type == DESCRIBES_COMPONENT; + } + + std::string get_key() const + { + try { + return _pt.get<std::string>("nocblock.key"); + } catch (const pt::ptree_bad_path &) { + return _pt.get<std::string>("nocblock.blockname"); + } + } + + std::string get_name() const + { + return _pt.get<std::string>("nocblock.blockname"); + } + + boost::uint64_t noc_id() const + { + return _noc_id; + } + + ports_t get_input_ports() + { + return _get_ports("sink"); + } + + ports_t get_output_ports() + { + return _get_ports("source"); + } + + ports_t _get_ports(const std::string &port_type) + { + std::set<size_t> port_numbers; + size_t n_ports = 0; + ports_t ports; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.ports")) { + if (v.first != port_type) continue; + // Now we have the correct sink or source node: + port_t port; + BOOST_FOREACH(const std::string &key, port_t::PORT_ARGS.keys()) { + port[key] = v.second.get(key, port_t::PORT_ARGS[key]); + } + // We have to be extra-careful with the port numbers: + if (port["port"].empty()) { + port["port"] = boost::lexical_cast<std::string>(n_ports); + } + size_t new_port_number; + try { + new_port_number = boost::lexical_cast<size_t>(port["port"]); + } catch (const boost::bad_lexical_cast &e) { + throw uhd::value_error(str( + boost::format("Invalid port number '%s' on port '%s'") + % port["port"] % port["name"] + )); + } + if (port_numbers.count(new_port_number) or new_port_number > MAX_NUM_PORTS) { + throw uhd::value_error(str( + boost::format("Port '%s' has invalid port number %d!") + % port["name"] % new_port_number + )); + } + port_numbers.insert(new_port_number); + n_ports++; + ports.push_back(port); + } + return ports; + } + + std::vector<size_t> get_all_port_numbers() + { + std::set<size_t> set_ports; + BOOST_FOREACH(const port_t &port, get_input_ports()) { + set_ports.insert(boost::lexical_cast<size_t>(port["port"])); + } + BOOST_FOREACH(const port_t &port, get_output_ports()) { + set_ports.insert(boost::lexical_cast<size_t>(port["port"])); + } + return std::vector<size_t>(set_ports.begin(), set_ports.end()); + } + + + blockdef::args_t get_args() + { + args_t args; + bool is_valid = true; + pt::ptree def; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.args", def)) { + arg_t arg; + if (v.first != "arg") continue; + BOOST_FOREACH(const std::string &key, arg_t::ARG_ARGS.keys()) { + arg[key] = v.second.get(key, arg_t::ARG_ARGS[key]); + } + if (arg["type"].empty()) { + arg["type"] = "string"; + } + if (not arg.is_valid()) { + UHD_MSG(warning) << boost::format("Found invalid argument: %s") % arg.to_string() << std::endl; + is_valid = false; + } + args.push_back(arg); + } + if (not is_valid) { + throw uhd::runtime_error(str( + boost::format("Found invalid arguments for block %s.") + % get_name() + )); + } + return args; + } + + registers_t get_settings_registers() + { + return _get_regs("setreg"); + } + + registers_t get_readback_registers() + { + return _get_regs("readback"); + } + + registers_t _get_regs(const std::string ®_type) + { + registers_t registers; + pt::ptree def; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.registers", def)) { + if (v.first != reg_type) continue; + registers[v.second.get<std::string>("name")] = + boost::lexical_cast<size_t>(v.second.get<size_t>("address")); + } + return registers; + } + + +private: + + //! Tells us if is this for a NoC block, or a component. + const xml_repr_t _type; + //! The NoC-ID as reported (there may be several valid NoC IDs, this is the one used) + const boost::uint64_t _noc_id; + + //! This is a boost property tree, not the same as + // our property tree. + pt::ptree _pt; + +}; + +blockdef::sptr blockdef::make_from_noc_id(boost::uint64_t noc_id) +{ + std::vector<fs::path> paths = blockdef_xml_impl::get_xml_paths(); + // Iterate over all paths + BOOST_FOREACH(const fs::path &base_path, paths) { + fs::path this_path = base_path / XML_BLOCKS_SUBDIR; + if (not fs::exists(this_path) or not fs::is_directory(this_path)) { + continue; + } + // Iterate over all .xml files + fs::directory_iterator end_itr; + for (fs::directory_iterator i(this_path); i != end_itr; ++i) { + if (not fs::exists(*i) or fs::is_directory(*i) or fs::is_empty(*i)) { + continue; + } + if (i->path().filename().extension() != XML_EXTENSION) { + continue; + } + if (blockdef_xml_impl::has_noc_id(noc_id, i->path())) { + return blockdef::sptr(new blockdef_xml_impl(i->path(), noc_id)); + } + } + } + + return blockdef::sptr(); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/ctrl_iface.cpp b/host/lib/rfnoc/ctrl_iface.cpp new file mode 100644 index 000000000..83c3a2626 --- /dev/null +++ b/host/lib/rfnoc/ctrl_iface.cpp @@ -0,0 +1,376 @@ +// +// Copyright 2012-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "ctrl_iface.hpp" +#include "async_packet_handler.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <queue> + +using namespace uhd; +using namespace uhd::rfnoc; +using namespace uhd::transport; + +static const double ACK_TIMEOUT = 2.0; //supposed to be worst case practical timeout +static const double MASSIVE_TIMEOUT = 10.0; //for when we wait on a timed command +static const size_t SR_READBACK = 32; + +ctrl_iface::~ctrl_iface(void){ + /* NOP */ +} + +class ctrl_iface_impl: public ctrl_iface +{ +public: + + ctrl_iface_impl(const bool big_endian, + uhd::transport::zero_copy_if::sptr ctrl_xport, + uhd::transport::zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, const std::string &name + ) : + _link_type(vrt::if_packet_info_t::LINK_TYPE_CHDR), + _packet_type(vrt::if_packet_info_t::PACKET_TYPE_CONTEXT), + _bige(big_endian), + _ctrl_xport(ctrl_xport), _resp_xport(resp_xport), + _sid(sid), + _name(name), + _seq_out(0), + _timeout(ACK_TIMEOUT), + _resp_queue(128/*max response msgs*/), + _resp_queue_size(_resp_xport ? _resp_xport->get_num_recv_frames() : 3), + _rb_address(uhd::rfnoc::SR_READBACK) + { + if (resp_xport) { + while (resp_xport->get_recv_buff(0.0)) {} //flush + } + this->set_time(uhd::time_spec_t(0.0)); + this->set_tick_rate(1.0); //something possible but bogus + } + + ~ctrl_iface_impl(void) + { + _timeout = ACK_TIMEOUT; //reset timeout to something small + UHD_SAFE_CALL( + this->peek32(0);//dummy peek with the purpose of ack'ing all packets + _async_task.reset();//now its ok to release the task + ) + } + + /******************************************************************* + * Peek and poke 32 bit implementation + ******************************************************************/ + void poke32(const wb_addr_type addr, const boost::uint32_t data) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(addr/4, data); + this->wait_for_ack(false); + } + + boost::uint32_t peek32(const wb_addr_type addr) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(_rb_address, addr/8); + const boost::uint64_t res = this->wait_for_ack(true); + const boost::uint32_t lo = boost::uint32_t(res & 0xffffffff); + const boost::uint32_t hi = boost::uint32_t(res >> 32); + return ((addr/4) & 0x1)? hi : lo; + } + + boost::uint64_t peek64(const wb_addr_type addr) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(_rb_address, addr/8); + return this->wait_for_ack(true); + } + + /******************************************************************* + * Update methods for time + ******************************************************************/ + void set_time(const uhd::time_spec_t &time) + { + boost::mutex::scoped_lock lock(_mutex); + _time = time; + _use_time = _time != uhd::time_spec_t(0.0); + if (_use_time) _timeout = MASSIVE_TIMEOUT; //permanently sets larger timeout + } + + uhd::time_spec_t get_time(void) + { + boost::mutex::scoped_lock lock(_mutex); + return _time; + } + + void set_tick_rate(const double rate) + { + boost::mutex::scoped_lock lock(_mutex); + _tick_rate = rate; + } + +private: + // This is the buffer type for response messages + struct resp_buff_type + { + boost::uint32_t data[8]; + }; + + /******************************************************************* + * Primary control and interaction private methods + ******************************************************************/ + inline void send_pkt(const boost::uint32_t addr, const boost::uint32_t data = 0) + { + managed_send_buffer::sptr buff = _ctrl_xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("fifo ctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.link_type = _link_type; + packet_info.packet_type = _packet_type; + packet_info.num_payload_words32 = 2; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = _seq_out; + packet_info.tsf = _time.to_ticks(_tick_rate); + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = _sid; + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = _use_time; + packet_info.has_tlr = false; + + //load header + if (_bige) vrt::if_hdr_pack_be(pkt, packet_info); + else vrt::if_hdr_pack_le(pkt, packet_info); + + //load payload + pkt[packet_info.num_header_words32+0] = (_bige)? uhd::htonx(addr) : uhd::htowx(addr); + pkt[packet_info.num_header_words32+1] = (_bige)? uhd::htonx(data) : uhd::htowx(data); + //UHD_MSG(status) << boost::format("0x%08x, 0x%08x\n") % addr % data; + //send the buffer over the interface + _outstanding_seqs.push(_seq_out); + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); + + _seq_out++;//inc seq for next call + } + + UHD_INLINE boost::uint64_t wait_for_ack(const bool readback) + { + while (readback or (_outstanding_seqs.size() >= _resp_queue_size)) + { + //get seq to ack from outstanding packets list + UHD_ASSERT_THROW(not _outstanding_seqs.empty()); + const size_t seq_to_ack = _outstanding_seqs.front(); + _outstanding_seqs.pop(); + + //parse the packet + vrt::if_packet_info_t packet_info; + resp_buff_type resp_buff; + memset(&resp_buff, 0x00, sizeof(resp_buff)); + boost::uint32_t const *pkt = NULL; + managed_recv_buffer::sptr buff; + + //get buffer from response endpoint - or die in timeout + if (_resp_xport) + { + buff = _resp_xport->get_recv_buff(_timeout); + try + { + UHD_ASSERT_THROW(bool(buff)); + UHD_ASSERT_THROW(buff->size() > 0); + } + catch(const std::exception &ex) + { + throw uhd::io_error(str(boost::format("Block ctrl (%s) no response packet - %s") % _name % ex.what())); + } + pkt = buff->cast<const boost::uint32_t *>(); + packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + } + + //get buffer from response endpoint - or die in timeout + else + { + /* + * Couldn't get message with haste. + * Now check both possible queues for messages. + * Messages should come in on _resp_queue, + * but could end up in dump_queue. + * If we don't get a message --> Die in timeout. + */ + double accum_timeout = 0.0; + const double short_timeout = 0.005; // == 5ms + while(not ((_resp_queue.pop_with_haste(resp_buff)) + || (check_dump_queue(resp_buff)) + || (_resp_queue.pop_with_timed_wait(resp_buff, short_timeout)) + )){ + /* + * If a message couldn't be received within a given timeout + * --> throw AssertionError! + */ + accum_timeout += short_timeout; + UHD_ASSERT_THROW(accum_timeout < _timeout); + } + + pkt = resp_buff.data; + packet_info.num_packet_words32 = sizeof(resp_buff)/sizeof(boost::uint32_t); + } + + //parse the buffer + try + { + packet_info.link_type = _link_type; + if (_bige) vrt::chdr::if_hdr_unpack_be(pkt, packet_info); + else vrt::chdr::if_hdr_unpack_le(pkt, packet_info); + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "[" << _name << "] Block ctrl bad VITA packet: " << ex.what() << std::endl; + if (buff){ + UHD_MSG(status) << boost::format("%08X") % pkt[0] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[1] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[2] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[3] << std::endl; + } + else{ + UHD_MSG(status) << "buff is NULL" << std::endl; + } + } + + //check the buffer + try + { + UHD_ASSERT_THROW(packet_info.has_sid); + if (packet_info.sid != boost::uint32_t((_sid >> 16) | (_sid << 16))) { + throw uhd::io_error( + str( + boost::format("Expected SID: %s Received SID: %s") + % uhd::sid_t(_sid).reversed().to_pp_string_hex() + % uhd::sid_t(packet_info.sid).to_pp_string_hex() + ) + ); + } + + if (packet_info.packet_count != (seq_to_ack & 0xfff)) { + throw uhd::io_error( + str( + boost::format("Expected packet index: %d Received index: %d") + % packet_info.packet_count + % (seq_to_ack & 0xfff) + ) + ); + } + + UHD_ASSERT_THROW(packet_info.num_payload_words32 == 2); + //UHD_ASSERT_THROW(packet_info.packet_type == _packet_type); + } + catch(const std::exception &ex) + { + throw uhd::io_error(str(boost::format("Block ctrl (%s) packet parse error - %s") % _name % ex.what())); + } + + //return the readback value + if (readback and _outstanding_seqs.empty()) + { + const boost::uint64_t hi = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+0]) : uhd::wtohx(pkt[packet_info.num_header_words32+0]); + const boost::uint64_t lo = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+1]) : uhd::wtohx(pkt[packet_info.num_header_words32+1]); + return ((hi << 32) | lo); + } + } + + return 0; + } + + /* + * If ctrl_core waits for a message that didn't arrive it can search for it in the dump queue. + * This actually happens during shutdown. + * handle_async_task can't access queue anymore thus it returns the corresponding message. + * msg_task class implements a dump_queue to store such messages. + * With check_dump_queue we can check if a message we are waiting for got stranded there. + * If a message got stuck we get it here and push it onto our own message_queue. + */ + bool check_dump_queue(resp_buff_type& b) { + const size_t min_buff_size = 8; // Same value as in b200_io_impl->handle_async_task + boost::uint32_t recv_sid = (((_sid)<<16)|((_sid)>>16)); + uhd::msg_task::msg_payload_t msg; + do{ + msg = _async_task->get_msg_from_dump_queue(recv_sid); + } + while(msg.size() < min_buff_size && msg.size() != 0); + + if(msg.size() >= min_buff_size) { + memcpy(b.data, &msg.front(), std::min(msg.size(), sizeof(b.data))); + return true; + } + return false; + } + + void push_response(const boost::uint32_t *buff) + { + resp_buff_type resp_buff; + std::memcpy(resp_buff.data, buff, sizeof(resp_buff)); + _resp_queue.push_with_haste(resp_buff); + } + + void hold_task(uhd::msg_task::sptr task) + { + _async_task = task; + } + + const vrt::if_packet_info_t::link_type_t _link_type; + const vrt::if_packet_info_t::packet_type_t _packet_type; + const bool _bige; + const uhd::transport::zero_copy_if::sptr _ctrl_xport; + const uhd::transport::zero_copy_if::sptr _resp_xport; + uhd::msg_task::sptr _async_task; + const boost::uint32_t _sid; + const std::string _name; + boost::mutex _mutex; + size_t _seq_out; + uhd::time_spec_t _time; + bool _use_time; + double _tick_rate; + double _timeout; + std::queue<size_t> _outstanding_seqs; + bounded_buffer<resp_buff_type> _resp_queue; + const size_t _resp_queue_size; + + const size_t _rb_address; +}; + +ctrl_iface::sptr ctrl_iface::make( + const bool big_endian, + zero_copy_if::sptr ctrl_xport, + zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, + const std::string &name +) { + return sptr(new ctrl_iface_impl( + big_endian, ctrl_xport, resp_xport, sid, name + )); +} diff --git a/host/lib/rfnoc/ctrl_iface.hpp b/host/lib/rfnoc/ctrl_iface.hpp new file mode 100644 index 000000000..4141b6583 --- /dev/null +++ b/host/lib/rfnoc/ctrl_iface.hpp @@ -0,0 +1,68 @@ +// +// Copyright 2012-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP +#define INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP + +#include <uhd/utils/msg_task.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <string> + +namespace uhd { namespace rfnoc { + +/*! + * Provide access to peek, poke for the radio ctrl module + */ +class ctrl_iface : public uhd::timed_wb_iface +{ +public: + typedef boost::shared_ptr<ctrl_iface> sptr; + + virtual ~ctrl_iface(void) = 0; + + //! Make a new control object + static sptr make( + const bool big_endian, + uhd::transport::zero_copy_if::sptr ctrl_xport, + uhd::transport::zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, + const std::string &name = "0" + ); + + //! Hold a ref to a task thats feeding push response + virtual void hold_task(uhd::msg_task::sptr task) = 0; + + //! Push a response externall (resp_xport is NULL) + virtual void push_response(const boost::uint32_t *buff) = 0; + + //! Set the command time that will activate + virtual void set_time(const uhd::time_spec_t &time) = 0; + + //! Get the command time that will activate + virtual uhd::time_spec_t get_time(void) = 0; + + //! Set the tick rate (converting time into ticks) + virtual void set_tick_rate(const double rate) = 0; +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP */ diff --git a/host/lib/rfnoc/ddc_block_ctrl_impl.cpp b/host/lib/rfnoc/ddc_block_ctrl_impl.cpp new file mode 100644 index 000000000..2aac22ca4 --- /dev/null +++ b/host/lib/rfnoc/ddc_block_ctrl_impl.cpp @@ -0,0 +1,281 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dsp_core_utils.hpp" +#include <uhd/rfnoc/ddc_block_ctrl.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/convert.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/math/special_functions/round.hpp> +#include <cmath> + +using namespace uhd::rfnoc; + +// TODO move this to a central location +template <class T> T ceil_log2(T num){ + return std::ceil(std::log(num)/std::log(T(2))); +} + +// TODO remove this once we have actual lambdas +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop, double value) +{ + return tree->access<double>(prop).set(value).get(); +} + +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop) +{ + return tree->access<double>(prop).get(); +} + +class ddc_block_ctrl_impl : public ddc_block_ctrl +{ +public: + static const size_t NUM_HALFBANDS = 3; + static const size_t CIC_MAX_DECIM = 255; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(ddc_block_ctrl) + { + // Argument/prop tree hooks + for (size_t chan = 0; chan < get_input_ports().size(); chan++) { + double default_freq = get_arg<double>("freq", chan); + _tree->access<double>(get_arg_path("freq/value", chan)) + .set_coercer(boost::bind(&ddc_block_ctrl_impl::set_freq, this, _1, chan)) + .set(default_freq); + ; + double default_output_rate = get_arg<double>("output_rate", chan); + _tree->access<double>(get_arg_path("output_rate/value", chan)) + .set_coercer(boost::bind(&ddc_block_ctrl_impl::set_output_rate, this, _1, chan)) + .set(default_output_rate) + ; + _tree->access<double>(get_arg_path("input_rate/value", chan)) + .add_coerced_subscriber(boost::bind(&ddc_block_ctrl_impl::set_input_rate, this, _1, chan)) + ; + + // Legacy properties (for backward compat w/ multi_usrp) + const uhd::fs_path dsp_base_path = _root_path / "legacy_api" / chan; + // Legacy properties + _tree->create<double>(dsp_base_path / "rate/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("output_rate/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("output_rate/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "rate/range") + .set_publisher(boost::bind(&ddc_block_ctrl_impl::get_output_rates, this)) + ; + _tree->create<double>(dsp_base_path / "freq/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "freq/range") + .set_publisher(boost::bind(&ddc_block_ctrl_impl::get_freq_range, this)) + ; + _tree->access<uhd::time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, chan)) + ; + if (_tree->exists("tick_rate")) { + const double tick_rate = _tree->access<double>("tick_rate").get(); + set_command_tick_rate(tick_rate, chan); + _tree->access<double>("tick_rate") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, _1, chan)) + ; + } + + // Rate 1:1 by default + sr_write("N", 1, chan); + sr_write("M", 1, chan); + sr_write("CONFIG", 1, chan); // Enable clear EOB + } + } // end ctor + virtual ~ddc_block_ctrl_impl() {}; + + double get_output_scale_factor(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_rx_streamer_active.count(port) and _rx_streamer_active.at(port))) { + return SCALE_UNDEFINED; + } + return get_arg<double>("scalar_correction", port); + } + + double get_input_samp_rate(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("input_rate", port); + } + + double get_output_samp_rate(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_rx_streamer_active.count(port) and _rx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("output_rate", port); + } + + + void issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd_, + const size_t chan + ) { + UHD_RFNOC_BLOCK_TRACE() << "ddc_block_ctrl_base::issue_stream_cmd()" << std::endl; + + if (list_upstream_nodes().count(chan) == 0) { + UHD_MSG(status) << "No upstream blocks." << std::endl; + return; + } + + uhd::stream_cmd_t stream_cmd = stream_cmd_; + if (stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE or + stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) { + size_t decimation = get_arg<double>("input_rate", chan) / get_arg<double>("output_rate", chan); + stream_cmd.num_samps *= decimation; + } + + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(list_upstream_nodes().at(chan).lock()); + if (this_upstream_block_ctrl) { + this_upstream_block_ctrl->issue_stream_cmd( + stream_cmd, + get_upstream_port(chan) + ); + } + } + +private: + + //! Set the CORDIC frequency shift the signal to \p requested_freq + double set_freq(const double requested_freq, const size_t chan) + { + const double input_rate = get_arg<double>("input_rate"); + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, input_rate, actual_freq, freq_word); + sr_write("CORDIC_FREQ", uint32_t(freq_word), chan); + return actual_freq; + } + + //! Return a range of valid frequencies the CORDIC can tune to + uhd::meta_range_t get_freq_range(void) + { + const double input_rate = get_arg<double>("input_rate"); + return uhd::meta_range_t( + -input_rate/2, + +input_rate/2, + input_rate/std::pow(2.0, 32) + ); + } + + // FIXME this misses a whole bunch of valid rates. Anything with CIC decim <= 255 + // is OK. + uhd::meta_range_t get_output_rates(void) + { + uhd::meta_range_t range; + const double input_rate = get_arg<double>("input_rate"); + for (int decim = 1024; decim > 512; decim -= 8){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 512; decim > 256; decim -= 4){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 256; decim > 128; decim -= 2){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 128; decim >= 1; decim -= 1){ + range.push_back(uhd::range_t(input_rate/decim)); + } + return range; + } + + double set_output_rate(const int requested_rate, const size_t chan) + { + const double input_rate = get_arg<double>("input_rate"); + const size_t decim_rate = boost::math::iround(input_rate/this->get_output_rates().clip(requested_rate, true)); + size_t decim = decim_rate; + + // The FPGA knows which halfbands to enable for any given value of hb_enable. + uint32_t hb_enable = 0; + while ((decim % 2 == 0) and hb_enable < NUM_HALFBANDS) { + hb_enable++; + decim /= 2; + } + UHD_ASSERT_THROW(hb_enable <= NUM_HALFBANDS); + UHD_ASSERT_THROW(decim <= CIC_MAX_DECIM); + // What we can't cover with halfbands, we do with the CIC + sr_write("DECIM_WORD", (hb_enable << 8) | (decim & 0xff), chan); + + // Rate change = M/N + sr_write("N", std::pow(2.0, double(hb_enable)) * (decim & 0xff), chan); + sr_write("M", 1, chan); + + if (decim > 1 and hb_enable == 0) { + UHD_MSG(warning) << boost::format( + "The requested decimation is odd; the user should expect passband CIC rolloff.\n" + "Select an even decimation to ensure that a halfband filter is enabled.\n" + "Decimations factorable by 4 will enable 2 halfbands, those factorable by 8 will enable 3 halfbands.\n" + "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" + ) % decim_rate % (input_rate/1e6) % (requested_rate/1e6); + } + + // Caclulate algorithmic gain of CIC for a given decimation. + // For Ettus CIC R=decim, M=1, N=4. Gain = (R * M) ^ N + const double rate_pow = std::pow(double(decim & 0xff), 4); + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DDC (that provide simple 1/2^n gain compensation). + // CORDIC algorithmic gain limits asymptotically around 1.647 after many iterations. + static const double CORDIC_GAIN = 1.648; + // + // The polar rotation of [I,Q] = [1,1] by Pi/8 also yields max magnitude of SQRT(2) (~1.4142) however + // input to the CORDIC thats outside the unit circle can only be sourced from a saturated RF frontend. + // To provide additional dynamic range head room accordingly using scale factor applied at egress from DDC would + // cost us small signal performance, thus we do no provide compensation gain for a saturated front end and allow + // the signal to clip in the H/W as needed. If we wished to avoid the signal clipping in these circumstances then adjust code to read: + // _scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CORDIC_GAIN*rate_pow*1.415); + const double scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CORDIC_GAIN*rate_pow); + update_scalar(scaling_adjustment, chan); + return input_rate/decim_rate; + } + + //! Set frequency and decimation again + void set_input_rate(const double /* rate */, const size_t chan) + { + const double desired_freq = _tree->access<double>(get_arg_path("freq", chan) / "value").get_desired(); + set_arg<double>("freq", desired_freq, chan); + const double desired_output_rate = _tree->access<double>(get_arg_path("output_rate", chan) / "value").get_desired(); + set_arg<double>("output_rate", desired_output_rate, chan); + } + + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DDC (that provide simple 1/2^n gain compensation). + // Further more factor in OTW format which adds further gain factor to weight output samples correctly. + void update_scalar(const double scalar, const size_t chan) + { + const double target_scalar = (1 << 15) * scalar; + const int32_t actual_scalar = boost::math::iround(target_scalar); + // Calculate the error introduced by using integer representation for the scalar, can be corrected in host later. + const double scalar_correction = + target_scalar / actual_scalar / double(1 << 15) // Rounding error, normalized to 1.0 + * get_arg<double>("fullscale"); // Scaling requested by host + set_arg<double>("scalar_correction", scalar_correction, chan); + // Write DDC with scaling correction for CIC and CORDIC that maximizes dynamic range in 32/16/12/8bits. + sr_write("SCALE_IQ", actual_scalar, chan); + } + +}; + +UHD_RFNOC_BLOCK_REGISTER(ddc_block_ctrl, "DDC"); diff --git a/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp b/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp new file mode 100644 index 000000000..5f476074b --- /dev/null +++ b/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp @@ -0,0 +1,122 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/dma_fifo_block_ctrl.hpp> +#include "dma_fifo_core_3000.hpp" +#include "wb_iface_adapter.hpp" +#include <uhd/convert.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/mutex.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +//TODO (Ashish): This should come from the framework +static const double BUS_CLK_RATE = 166.67e6; + +class dma_fifo_block_ctrl_impl : public dma_fifo_block_ctrl +{ +public: + static const uint32_t DEFAULT_SIZE = 32*1024*1024; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(dma_fifo_block_ctrl) + { + _perifs.resize(get_input_ports().size()); + for(size_t i = 0; i < _perifs.size(); i++) { + _perifs[i].ctrl = boost::make_shared<wb_iface_adapter>( + // poke32 functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const boost::uint32_t, const boost::uint32_t, const size_t) >(&block_ctrl_base::sr_write), + this, _1, _2, i + ), + // peek32 functor + boost::bind( + static_cast< boost::uint32_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read32), + this, + _1, i + ), + // peek64 functor + boost::bind( + static_cast< boost::uint64_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read64), + this, + _1, i + ) + ); + static const uint32_t USER_SR_BASE = 128*4; + static const uint32_t USER_RB_BASE = 0; //Don't care + _perifs[i].base_addr = DEFAULT_SIZE*i; + _perifs[i].depth = DEFAULT_SIZE; + _perifs[i].core = dma_fifo_core_3000::make(_perifs[i].ctrl, USER_SR_BASE, USER_RB_BASE); + _perifs[i].core->resize(_perifs[i].base_addr, _perifs[i].depth); + UHD_MSG(status) << boost::format("[DMA FIFO] Running BIST for FIFO %d... ") % i; + if (_perifs[i].core->ext_bist_supported()) { + boost::uint32_t bisterr = _perifs[i].core->run_bist(); + if (bisterr != 0) { + throw uhd::runtime_error(str(boost::format("BIST failed! (code: %d)\n") % bisterr)); + } else { + double throughput = _perifs[i].core->get_bist_throughput(BUS_CLK_RATE); + UHD_MSG(status) << (boost::format("pass (Throughput: %.1fMB/s)") % (throughput/1e6)) << std::endl; + } + } else { + if (_perifs[i].core->run_bist() == 0) { + UHD_MSG(status) << "pass\n"; + } else { + throw uhd::runtime_error("BIST failed!\n"); + } + } + _tree->access<int>(get_arg_path("base_addr/value", i)) + .add_coerced_subscriber(boost::bind(&dma_fifo_block_ctrl_impl::resize, this, _1, boost::ref(_perifs[i].depth), i)) + .set(_perifs[i].base_addr) + ; + _tree->access<int>(get_arg_path("depth/value", i)) + .add_coerced_subscriber(boost::bind(&dma_fifo_block_ctrl_impl::resize, this, boost::ref(_perifs[i].base_addr), _1, i)) + .set(_perifs[i].depth) + ; + } + } + + void resize(const uint32_t base_addr, const uint32_t depth, const size_t chan) { + boost::lock_guard<boost::mutex> lock(_config_mutex); + _perifs[chan].base_addr = base_addr; + _perifs[chan].depth = depth; + _perifs[chan].core->resize(base_addr, depth); + } + + uint32_t get_base_addr(const size_t chan) const { + return _perifs[chan].base_addr; + } + + uint32_t get_depth(const size_t chan) const { + return _perifs[chan].depth; + } + +private: + struct fifo_perifs_t + { + wb_iface::sptr ctrl; + dma_fifo_core_3000::sptr core; + uint32_t base_addr; + uint32_t depth; + }; + std::vector<fifo_perifs_t> _perifs; + + boost::mutex _config_mutex; +}; + +UHD_RFNOC_BLOCK_REGISTER(dma_fifo_block_ctrl, "DmaFIFO"); diff --git a/host/lib/rfnoc/duc_block_ctrl_impl.cpp b/host/lib/rfnoc/duc_block_ctrl_impl.cpp new file mode 100644 index 000000000..0340ba0d6 --- /dev/null +++ b/host/lib/rfnoc/duc_block_ctrl_impl.cpp @@ -0,0 +1,268 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dsp_core_utils.hpp" +#include <uhd/rfnoc/duc_block_ctrl.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/convert.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/math/special_functions/round.hpp> +#include <cmath> + +using namespace uhd::rfnoc; + +// TODO move this to a central location +template <class T> T ceil_log2(T num){ + return std::ceil(std::log(num)/std::log(T(2))); +} + +// TODO remove this once we have actual lambdas +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop, double value) +{ + return tree->access<double>(prop).set(value).get(); +} + +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop) +{ + return tree->access<double>(prop).get(); +} + +class duc_block_ctrl_impl : public duc_block_ctrl +{ +public: + static const size_t NUM_HALFBANDS = 2; + static const size_t CIC_MAX_INTERP = 128; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(duc_block_ctrl) + { + // Argument/prop tree hooks + for (size_t chan = 0; chan < get_input_ports().size(); chan++) { + double default_freq = get_arg<double>("freq", chan); + _tree->access<double>(get_arg_path("freq/value", chan)) + .set_coercer(boost::bind(&duc_block_ctrl_impl::set_freq, this, _1, chan)) + .set(default_freq); + ; + double default_input_rate = get_arg<double>("input_rate", chan); + _tree->access<double>(get_arg_path("input_rate/value", chan)) + .set_coercer(boost::bind(&duc_block_ctrl_impl::set_input_rate, this, _1, chan)) + .set(default_input_rate) + ; + _tree->access<double>(get_arg_path("output_rate/value", chan)) + .add_coerced_subscriber(boost::bind(&duc_block_ctrl_impl::set_output_rate, this, _1, chan)) + ; + + // Legacy properties (for backward compat w/ multi_usrp) + const uhd::fs_path dsp_base_path = _root_path / "legacy_api" / chan; + // Legacy properties + _tree->create<double>(dsp_base_path / "rate/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("input_rate/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("input_rate/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "rate/range") + .set_publisher(boost::bind(&duc_block_ctrl_impl::get_input_rates, this)) + ; + _tree->create<double>(dsp_base_path / "freq/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "freq/range") + .set_publisher(boost::bind(&duc_block_ctrl_impl::get_freq_range, this)) + ; + _tree->access<uhd::time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, chan)) + ; + if (_tree->exists("tick_rate")) { + const double tick_rate = _tree->access<double>("tick_rate").get(); + set_command_tick_rate(tick_rate, chan); + _tree->access<double>("tick_rate") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, _1, chan)) + ; + } + + // Rate 1:1 by default + sr_write("N", 1, chan); + sr_write("M", 1, chan); + sr_write("CONFIG", 1, chan); // Enable clear EOB + } + } // end ctor + virtual ~duc_block_ctrl_impl() {}; + + double get_input_scale_factor(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return SCALE_UNDEFINED; + } + return get_arg<double>("scalar_correction", port); + } + + double get_input_samp_rate(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("input_rate", port); + } + + double get_output_samp_rate(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("output_rate", port == ANY_PORT ? 0 : port); + } + + void issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd_, + const size_t chan + ) { + UHD_RFNOC_BLOCK_TRACE() << "duc_block_ctrl_base::issue_stream_cmd()" << std::endl; + + uhd::stream_cmd_t stream_cmd = stream_cmd_; + if (stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE or + stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) { + size_t interpolation = get_arg<double>("output_rate", chan) / get_arg<double>("input_rate", chan); + stream_cmd.num_samps *= interpolation; + } + + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, list_upstream_nodes()) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + this_upstream_block_ctrl->issue_stream_cmd(stream_cmd, chan); + } + } + +private: + + //! Set the CORDIC frequency shift the signal to \p requested_freq + double set_freq(const double requested_freq, const size_t chan) + { + const double output_rate = get_arg<double>("output_rate"); + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, output_rate, actual_freq, freq_word); + // Xilinx CORDIC uses a different format for the phase increment, hence the divide-by-four: + sr_write("CORDIC_FREQ", uint32_t(freq_word/4), chan); + return actual_freq; + } + + //! Return a range of valid frequencies the CORDIC can tune to + uhd::meta_range_t get_freq_range(void) + { + const double output_rate = get_arg<double>("output_rate"); + return uhd::meta_range_t( + -output_rate/2, + +output_rate/2, + output_rate/std::pow(2.0, 32) + ); + } + + uhd::meta_range_t get_input_rates(void) + { + uhd::meta_range_t range; + const double output_rate = get_arg<double>("output_rate"); + for (int rate = 512; rate > 256; rate -= 4){ + range.push_back(uhd::range_t(output_rate/rate)); + } + for (int rate = 256; rate > 128; rate -= 2){ + range.push_back(uhd::range_t(output_rate/rate)); + } + for (int rate = 128; rate >= 1; rate -= 1){ + range.push_back(uhd::range_t(output_rate/rate)); + } + return range; + } + + double set_input_rate(const int requested_rate, const size_t chan) + { + const double output_rate = get_arg<double>("output_rate", chan); + const size_t interp_rate = boost::math::iround(output_rate/get_input_rates().clip(requested_rate, true)); + size_t interp = interp_rate; + + uint32_t hb_enable = 0; + while ((interp % 2 == 0) and hb_enable < NUM_HALFBANDS) { + hb_enable++; + interp /= 2; + } + UHD_ASSERT_THROW(hb_enable <= NUM_HALFBANDS); + UHD_ASSERT_THROW(interp > 0 and interp <= CIC_MAX_INTERP); + // hacky hack: Unlike the DUC, the DUC actually simply has 2 + // flags to enable either halfband. + uint32_t hb_enable_word = hb_enable; + if (hb_enable == 2) { + hb_enable_word = 3; + } + hb_enable_word <<= 8; + // What we can't cover with halfbands, we do with the CIC + sr_write("INTERP_WORD", hb_enable_word | (interp & 0xff), chan); + + // Rate change = M/N + sr_write("N", 1, chan); + sr_write("M", std::pow(2.0, double(hb_enable)) * (interp & 0xff), chan); + + if (interp > 1 and hb_enable == 0) { + UHD_MSG(warning) << boost::format( + "The requested interpolation is odd; the user should expect passband CIC rolloff.\n" + "Select an even interpolation to ensure that a halfband filter is enabled.\n" + "interpolation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" + ) % interp_rate % (output_rate/1e6) % (requested_rate/1e6); + } + + // Calculate algorithmic gain of CIC for a given interpolation + // For Ettus CIC R=interp, M=1, N=4. Gain = (R * M) ^ (N - 1) + const int CIC_N = 4; + const double rate_pow = std::pow(double(interp & 0xff), CIC_N - 1); + + // Experimentally determined value to scale the output to [-1, 1] + // This must also encompass the CORDIC gain + static const double CONSTANT_GAIN = 1.1644; + + const double scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CONSTANT_GAIN*rate_pow); + update_scalar(scaling_adjustment, chan); + return output_rate/interp_rate; + } + + //! Set frequency and interpolation again + void set_output_rate(const double /* rate */, const size_t chan) + { + const double desired_freq = _tree->access<double>(get_arg_path("freq", chan) / "value").get_desired(); + set_arg<double>("freq", desired_freq, chan); + const double desired_input_rate = _tree->access<double>(get_arg_path("input_rate", chan) / "value").get_desired(); + set_arg<double>("input_rate", desired_input_rate, chan); + } + + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DUC (that provide simple 1/2^n gain compensation). + // Further more factor in OTW format which adds further gain factor to weight output samples correctly. + void update_scalar(const double scalar, const size_t chan) + { + const double target_scalar = (1 << 15) * scalar; + const int32_t actual_scalar = boost::math::iround(target_scalar); + // Calculate the error introduced by using integer representation for the scalar + const double scalar_correction = + actual_scalar / target_scalar * (double(1 << 15) - 1.0) // Rounding error, normalized to 1.0 + * get_arg<double>("fullscale"); // Scaling requested by host + set_arg<double>("scalar_correction", scalar_correction, chan); + // Write DUC with scaling correction for CIC and CORDIC that maximizes dynamic range in 32/16/12/8bits. + sr_write("SCALE_IQ", actual_scalar, chan); + } +}; + +UHD_RFNOC_BLOCK_REGISTER(duc_block_ctrl, "DUC"); + diff --git a/host/lib/rfnoc/graph_impl.cpp b/host/lib/rfnoc/graph_impl.cpp new file mode 100644 index 000000000..64c6f6abe --- /dev/null +++ b/host/lib/rfnoc/graph_impl.cpp @@ -0,0 +1,164 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "graph_impl.hpp" +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd::rfnoc; + +/**************************************************************************** + * Structors + ***************************************************************************/ +graph_impl::graph_impl( + const std::string &name, + boost::weak_ptr<uhd::device3> device_ptr + //async_msg_handler::sptr msg_handler +) : _name(name) + , _device_ptr(device_ptr) +{ + +} + + +/**************************************************************************** + * Connection API + ***************************************************************************/ +void graph_impl::connect( + const block_id_t &src_block, + size_t src_block_port, + const block_id_t &dst_block, + size_t dst_block_port, + const size_t pkt_size_ +) { + device3::sptr device_ptr = _device_ptr.lock(); + if (not device_ptr) { + throw uhd::runtime_error("Invalid device"); + } + + uhd::rfnoc::source_block_ctrl_base::sptr src = device_ptr->get_block_ctrl<rfnoc::source_block_ctrl_base>(src_block); + uhd::rfnoc::sink_block_ctrl_base::sptr dst = device_ptr->get_block_ctrl<rfnoc::sink_block_ctrl_base>(dst_block); + + /******************************************************************** + * 1. Draw the edges (logically connect the nodes) + ********************************************************************/ + size_t actual_src_block_port = src->connect_downstream( + boost::dynamic_pointer_cast<uhd::rfnoc::node_ctrl_base>(dst), + src_block_port + ); + if (src_block_port == uhd::rfnoc::ANY_PORT) { + src_block_port = actual_src_block_port; + } else if (src_block_port != actual_src_block_port) { + throw uhd::runtime_error(str( + boost::format("Can't connect to port %d on block %s.") + % src_block_port % src->unique_id() + )); + } + size_t actual_dst_block_port = dst->connect_upstream( + boost::dynamic_pointer_cast<uhd::rfnoc::node_ctrl_base>(src), + dst_block_port + ); + if (dst_block_port == uhd::rfnoc::ANY_PORT) { + dst_block_port = actual_dst_block_port; + } else if (dst_block_port != actual_dst_block_port) { + throw uhd::runtime_error(str( + boost::format("Can't connect to port %d on block %s.") + % dst_block_port % dst->unique_id() + )); + } + src->set_downstream_port(actual_src_block_port, actual_dst_block_port); + dst->set_upstream_port(actual_dst_block_port, actual_src_block_port); + // At this point, ports are locked and no one else can simply connect + // into them. + //UHD_MSG(status) + //<< "[" << _name << "] Connecting " + //<< src_block << ":" << actual_src_block_port << " --> " + //<< dst_block << ":" << actual_dst_block_port << std::endl; + + /******************************************************************** + * 2. Check IO signatures match + ********************************************************************/ + if (not rfnoc::stream_sig_t::is_compatible( + src->get_output_signature(actual_src_block_port), + dst->get_input_signature(actual_dst_block_port) + )) { + throw uhd::runtime_error(str( + boost::format("Can't connect block %s to %s: IO signature mismatch\n(%s is incompatible with %s).") + % src->get_block_id().get() % dst->get_block_id().get() + % src->get_output_signature(actual_src_block_port) + % dst->get_input_signature(actual_dst_block_port) + )); + } + + /******************************************************************** + * 3. Configure the source block's destination + ********************************************************************/ + // Calculate SID + sid_t sid = dst->get_address(dst_block_port); + sid.set_src(src->get_address(src_block_port)); + + // Set SID on source block + src->set_destination(sid.get(), src_block_port); + + /******************************************************************** + * 4. Configure flow control + ********************************************************************/ + size_t pkt_size = (pkt_size_ != 0) ? pkt_size_ : src->get_output_signature(src_block_port).packet_size; + if (pkt_size == 0) { // Unspecified packet rate. Assume max packet size. + UHD_MSG(status) << "Assuming max packet size for " << src->get_block_id() << std::endl; + pkt_size = uhd::rfnoc::MAX_PACKET_SIZE; + } + // FC window (in packets) depends on FIFO size... ...and packet size. + size_t buf_size_pkts = dst->get_fifo_size(dst_block_port) / pkt_size; + if (buf_size_pkts == 0) { + throw uhd::runtime_error(str( + boost::format("Input FIFO for block %s is too small (%d kiB) for packets of size %d kiB\n" + "coming from block %s.") + % dst->get_block_id().get() % (dst->get_fifo_size(dst_block_port) / 1024) + % (pkt_size / 1024) % src->get_block_id().get() + )); + } + src->configure_flow_control_out(buf_size_pkts, src_block_port); + // On the same crossbar, use lots of FC packets + size_t pkts_per_ack = std::min( + uhd::rfnoc::DEFAULT_FC_XBAR_PKTS_PER_ACK, + buf_size_pkts - 1 + ); + // Over the network, use less or we'd flood the transport + if (sid.get_src_addr() != sid.get_dst_addr()) { + pkts_per_ack = std::max<size_t>(buf_size_pkts / uhd::rfnoc::DEFAULT_FC_TX_RESPONSE_FREQ, 1); + } + dst->configure_flow_control_in( + 0, // Default to not use cycles + pkts_per_ack, + dst_block_port + ); + + /******************************************************************** + * 5. Configure error policy + ********************************************************************/ + dst->set_error_policy("next_burst"); +} + +void graph_impl::connect( + const block_id_t &src_block, + const block_id_t &dst_block +) { + connect(src_block, ANY_PORT, dst_block, ANY_PORT); +} + diff --git a/host/lib/rfnoc/graph_impl.hpp b/host/lib/rfnoc/graph_impl.hpp new file mode 100644 index 000000000..12dbf6357 --- /dev/null +++ b/host/lib/rfnoc/graph_impl.hpp @@ -0,0 +1,76 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP + +#include <uhd/rfnoc/graph.hpp> +#include <uhd/device3.hpp> + +namespace uhd { namespace rfnoc { + +class graph_impl : public graph +{ +public: + /*! + * \param name An optional name to describe this graph + * \param device_ptr Weak pointer to the originating device3 + * \param msg_handler Pointer to the async message handler + */ + graph_impl( + const std::string &name, + boost::weak_ptr<uhd::device3> device_ptr + //async_msg_handler::sptr msg_handler + ); + virtual ~graph_impl() {}; + + /************************************************************************ + * Connection API + ***********************************************************************/ + void connect( + const block_id_t &src_block, + size_t src_block_port, + const block_id_t &dst_block, + size_t dst_block_port, + const size_t pkt_size = 0 + ); + + void connect( + const block_id_t &src_block, + const block_id_t &dst_block + ); + + /************************************************************************ + * Utilities + ***********************************************************************/ + std::string get_name() const { return _name; } + + +private: + + //! Optional: A string to describe this graph + const std::string _name; + + //! Reference to the generating device object + const boost::weak_ptr<uhd::device3> _device_ptr; + +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/legacy_compat.cpp b/host/lib/rfnoc/legacy_compat.cpp new file mode 100644 index 000000000..843cdea34 --- /dev/null +++ b/host/lib/rfnoc/legacy_compat.cpp @@ -0,0 +1,720 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "legacy_compat.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/rfnoc/radio_ctrl.hpp> +#include <uhd/rfnoc/ddc_block_ctrl.hpp> +#include <uhd/rfnoc/graph.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/stream.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/make_shared.hpp> + +#define UHD_LEGACY_LOG() UHD_LOGV(never) + +using namespace uhd::rfnoc; +using uhd::usrp::subdev_spec_t; +using uhd::usrp::subdev_spec_pair_t; +using uhd::stream_cmd_t; + +/************************************************************************ + * Constants + ***********************************************************************/ +static const std::string RADIO_BLOCK_NAME = "Radio"; +static const std::string DFIFO_BLOCK_NAME = "DmaFIFO"; +static const std::string DDC_BLOCK_NAME = "DDC"; +static const std::string DUC_BLOCK_NAME = "DUC"; +static const size_t MAX_BYTES_PER_HEADER = + uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); +static const size_t BYTES_PER_SAMPLE = 4; // We currently only support sc16 + +/************************************************************************ + * Static helpers + ***********************************************************************/ +static uhd::fs_path mb_root(const size_t mboard) +{ + return uhd::fs_path("/mboards") / mboard; +} + +size_t num_ports(const uhd::property_tree::sptr &tree, const std::string &block_name, const std::string &in_out) +{ + return tree->list( + uhd::fs_path("/mboards/0/xbar") / + str(boost::format("%s_0") % block_name) / + "ports" / in_out + ).size(); +} + +size_t calc_num_tx_chans_per_radio( + const uhd::property_tree::sptr &tree, + const size_t num_radios_per_board, + const bool has_ducs, + const bool has_dmafifo +) { + const size_t num_radio_ports = num_ports(tree, RADIO_BLOCK_NAME, "in"); + if (has_ducs) { + return std::min( + num_radio_ports, + num_ports(tree, DUC_BLOCK_NAME, "in") + ); + } + + if (not has_dmafifo) { + return num_radio_ports; + } + + const size_t num_dmafifo_ports_per_radio = num_ports(tree, DFIFO_BLOCK_NAME, "in") / num_radios_per_board; + UHD_ASSERT_THROW(num_dmafifo_ports_per_radio); + + return std::min( + num_radio_ports, + num_dmafifo_ports_per_radio + ); +} + +double lambda_const_double(const double d) +{ + return d; +} + +uhd::meta_range_t lambda_const_meta_range(const double start, const double stop, const double step) +{ + return uhd::meta_range_t(start, stop, step); +} +/************************************************************************ + * Class Definition + ***********************************************************************/ +class legacy_compat_impl : public legacy_compat +{ +public: + /************************************************************************ + * Structors and Initialization + ***********************************************************************/ + legacy_compat_impl( + uhd::device3::sptr device, + const uhd::device_addr_t &args + ) : _device(device), + _tree(device->get_tree()), + _has_ducs(not args.has_key("skip_duc") and not device->find_blocks(DUC_BLOCK_NAME).empty()), + _has_ddcs(not args.has_key("skip_ddc") and not device->find_blocks(DDC_BLOCK_NAME).empty()), + _has_dmafifo(not args.has_key("skip_dram") and not device->find_blocks(DFIFO_BLOCK_NAME).empty()), + _num_mboards(_tree->list("/mboards").size()), + _num_radios_per_board(device->find_blocks<radio_ctrl>("0/Radio").size()), // These might throw, maybe we catch that and provide a nicer error message. + _num_tx_chans_per_radio( + calc_num_tx_chans_per_radio(_tree, _num_radios_per_board, _has_ducs, not device->find_blocks(DFIFO_BLOCK_NAME).empty()) + ), + _num_rx_chans_per_radio(_has_ddcs ? + std::min(num_ports(_tree, RADIO_BLOCK_NAME, "out"), num_ports(_tree, DDC_BLOCK_NAME, "out")) + : num_ports(_tree, RADIO_BLOCK_NAME, "out")), + _rx_spp(get_block_ctrl<radio_ctrl>(0, RADIO_BLOCK_NAME, 0)->get_arg<int>("spp")), + _tx_spp(_rx_spp), + _rx_channel_map(_num_mboards, std::vector<radio_port_pair_t>(_num_radios_per_board)), + _tx_channel_map(_num_mboards, std::vector<radio_port_pair_t>(_num_radios_per_board)) + { + _device->clear(); + check_available_periphs(); // Throws if invalid configuration. + setup_prop_tree(); + if (_tree->exists("/mboards/0/mtu/send")) { + _tx_spp = (_tree->access<size_t>("/mboards/0/mtu/send").get() - MAX_BYTES_PER_HEADER) / BYTES_PER_SAMPLE; + } + connect_blocks(); + if (args.has_key("skip_ddc")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DDCs by user request." << std::endl; + } else if (not _has_ddcs) { + UHD_MSG(warning) + << "[legacy_compat] No DDCs detected. You will only be able to receive at the radio frontend rate." + << std::endl; + } + if (args.has_key("skip_duc")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DUCs by user request." << std::endl; + } else if (not _has_ducs) { + UHD_MSG(warning) << "[legacy_compat] No DUCs detected. You will only be able to transmit at the radio frontend rate." << std::endl; + } + if (args.has_key("skip_dram")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DRAM by user request." << std::endl; + } else if (not _has_dmafifo) { + UHD_MSG(warning) << "[legacy_compat] No DMA FIFO detected. You will only be able to transmit at slow rates." << std::endl; + } + + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + _rx_channel_map[mboard][radio].radio_index = radio; + _tx_channel_map[mboard][radio].radio_index = radio; + } + + const double tick_rate = _tree->access<double>(mb_root(mboard) / "tick_rate").get(); + update_tick_rate_on_blocks(tick_rate, mboard); + } + } + + /************************************************************************ + * API Calls + ***********************************************************************/ + uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan) + { + // The DSP index is the same as the radio index + size_t dsp_index = _rx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _rx_channel_map[mboard_idx][chan].port_index; + + if (not _has_ddcs) { + return mb_root(mboard_idx) / "rx_dsps" / dsp_index / port_index; + } + + return mb_root(mboard_idx) / "xbar" / + str(boost::format("%s_%d") % DDC_BLOCK_NAME % dsp_index) / + "legacy_api" / port_index; + } + + uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan) + { + // The DSP index is the same as the radio index + size_t dsp_index = _tx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _tx_channel_map[mboard_idx][chan].port_index; + + if (not _has_ducs) { + return mb_root(mboard_idx) / "tx_dsps" / dsp_index / port_index; + } + + return mb_root(mboard_idx) / "xbar" / + str(boost::format("%s_%d") % DUC_BLOCK_NAME % dsp_index) / + "legacy_api" / port_index; + } + + uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan) + { + size_t radio_index = _rx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _rx_channel_map[mboard_idx][chan].port_index; + return uhd::fs_path(str( + boost::format("/mboards/%d/xbar/%s_%d/rx_fe_corrections/%d/") + % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index + )); + } + + uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan) + { + size_t radio_index = _tx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _tx_channel_map[mboard_idx][chan].port_index; + return uhd::fs_path(str( + boost::format("/mboards/%d/xbar/%s_%d/tx_fe_corrections/%d/") + % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index + )); + } + + void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t mboard, size_t chan) + { + UHD_LEGACY_LOG() << "[legacy_compat] issue_stream_cmd() " << std::endl; + const size_t &radio_index = _rx_channel_map[mboard][chan].radio_index; + const size_t &port_index = _rx_channel_map[mboard][chan].port_index; + if (_has_ddcs) { + get_block_ctrl<ddc_block_ctrl>(mboard, DDC_BLOCK_NAME, radio_index)->issue_stream_cmd(stream_cmd, port_index); + } else { + get_block_ctrl<radio_ctrl>(mboard, RADIO_BLOCK_NAME, radio_index)->issue_stream_cmd(stream_cmd, port_index); + } + } + + //! Sets block_id<N> and block_port<N> in the streamer args, otherwise forwards the call + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args_) + { + uhd::stream_args_t args(args_); + if (args.otw_format.empty()) { + args.otw_format = "sc16"; + } + _update_stream_args_for_streaming<uhd::RX_DIRECTION>(args, _rx_channel_map); + UHD_LEGACY_LOG() << "[legacy_compat] rx stream args: " << args.args.to_string() << std::endl; + return _device->get_rx_stream(args); + } + + //! Sets block_id<N> and block_port<N> in the streamer args, otherwise forwards the call. + // If spp is in the args, update the radios. If it's not set, copy the value from the radios. + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args_) + { + uhd::stream_args_t args(args_); + if (args.otw_format.empty()) { + args.otw_format = "sc16"; + } + _update_stream_args_for_streaming<uhd::TX_DIRECTION>(args, _tx_channel_map); + UHD_LEGACY_LOG() << "[legacy_compat] tx stream args: " << args.args.to_string() << std::endl; + return _device->get_tx_stream(args); + } + + double get_tick_rate(const size_t mboard_idx=0) + { + return _tree->access<double>(mb_root(mboard_idx) / "tick_rate").get(); + } + + uhd::meta_range_t lambda_get_samp_rate_range( + const size_t mboard_idx, + const size_t radio_idx, + const size_t chan, + uhd::direction_t dir + ) { + radio_ctrl::sptr radio_sptr = get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx); + const double samp_rate = (dir == uhd::TX_DIRECTION) ? + radio_sptr->get_input_samp_rate(chan) : + radio_sptr->get_output_samp_rate(chan) + ; + + return uhd::meta_range_t(samp_rate, samp_rate, 0.0); + } + + void set_tick_rate(const double tick_rate, const size_t mboard_idx=0) + { + _tree->access<double>(mb_root(mboard_idx) / "tick_rate").set(tick_rate); + update_tick_rate_on_blocks(tick_rate, mboard_idx); + } + +private: // types + struct radio_port_pair_t { + radio_port_pair_t(const size_t radio=0, const size_t port=0) : radio_index(radio), port_index(port) {} + size_t radio_index; + size_t port_index; + }; + //! Map: _rx_channel_map[mboard_idx][chan_idx] => (Radio, Port) + // Container is not a std::map because we need to guarantee contiguous + // ports and correct order anyway. + typedef std::vector< std::vector<radio_port_pair_t> > chan_map_t; + +private: // methods + /************************************************************************ + * Private helpers + ***********************************************************************/ + std::string get_slot_name(const size_t radio_index) + { + return (radio_index == 0) ? "A" : "B"; + } + + size_t get_radio_index(const std::string slot_name) + { + return (slot_name == "A") ? 0 : 1; + } + + template <typename block_type> + inline typename block_type::sptr get_block_ctrl(const size_t mboard_idx, const std::string &name, const size_t block_count) + { + block_id_t block_id(mboard_idx, name, block_count); + return _device->get_block_ctrl<block_type>(block_id); + } + + template <uhd::direction_t dir> + void _update_stream_args_for_streaming( + uhd::stream_args_t &args, + chan_map_t &chan_map + ) { + // If the user provides spp, that value is always applied. If it's + // different from what we thought it was, we need to update the blocks. + // If it's not provided, we provide our own spp value. + const size_t args_spp = args.args.cast<size_t>("spp", 0); + if (dir == uhd::RX_DIRECTION) { + if (args.args.has_key("spp") and args_spp != _rx_spp) { + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + get_block_ctrl<radio_ctrl>(mboard, RADIO_BLOCK_NAME, radio)->set_arg<int>("spp", args_spp); + } + } + _rx_spp = args_spp; + // TODO: Update flow control on the blocks + } else { + args.args["spp"] = str(boost::format("%d") % _rx_spp); + } + } else { + if (args.args.has_key("spp") and args_spp != _tx_spp) { + _tx_spp = args_spp; + // TODO: Update flow control on the blocks + } else { + args.args["spp"] = str(boost::format("%d") % _tx_spp); + } + } + + if (args.channels.empty()) { + args.channels = std::vector<size_t>(1, 0); + } + for (size_t i = 0; i < args.channels.size(); i++) { + const size_t stream_arg_chan_idx = args.channels[i]; + // Determine which mboard, and on that mboard, which channel this is: + size_t mboard_idx = 0; + size_t this_mboard_chan_idx = stream_arg_chan_idx; + while (this_mboard_chan_idx >= chan_map[mboard_idx].size()) { + mboard_idx++; + this_mboard_chan_idx -= chan_map[mboard_idx].size(); + } + if (mboard_idx >= chan_map.size()) { + throw uhd::index_error(str( + boost::format("[legacy_compat]: %s channel %u out of range for given frontend configuration.") + % (dir == uhd::TX_DIRECTION ? "TX" : "RX") + % stream_arg_chan_idx + )); + } + // Map that mboard and channel to a block: + const size_t radio_index = chan_map[mboard_idx][this_mboard_chan_idx].radio_index; + size_t port_index = chan_map[mboard_idx][this_mboard_chan_idx].port_index; + const std::string block_name = _get_streamer_block_id_and_port<dir>(mboard_idx, radio_index, port_index); + args.args[str(boost::format("block_id%d") % stream_arg_chan_idx)] = block_name; + args.args[str(boost::format("block_port%d") % stream_arg_chan_idx)] = str(boost::format("%d") % port_index); + } + } + + template <uhd::direction_t dir> + std::string _get_streamer_block_id_and_port( + const size_t mboard_idx, + const size_t radio_index, + size_t &port_index + ) { + if (dir == uhd::TX_DIRECTION) { + if (_has_dmafifo) { + port_index = radio_index; + return block_id_t(mboard_idx, DFIFO_BLOCK_NAME, 0).to_string(); + } else { + if (_has_ducs) { + return block_id_t(mboard_idx, DUC_BLOCK_NAME, radio_index).to_string(); + } else { + return block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(); + } + } + } else { + if (_has_ddcs) { + return block_id_t(mboard_idx, DDC_BLOCK_NAME, radio_index).to_string(); + } else { + return block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(); + } + } + } + + /************************************************************************ + * Initialization + ***********************************************************************/ + /*! Check this device has all the required peripherals. + * + * Check rules: + * - Every mboard needs the same number of radios. + * - For every radio block, there must be DDC and a DUC block, + * with matching number of ports. + * + * \throw uhd::runtime_error if any of these checks fail. + */ + void check_available_periphs() + { + if (_num_radios_per_board == 0) { + throw uhd::runtime_error("For legacy APIs, all devices require at least one radio."); + } + block_id_t radio_block_id(0, RADIO_BLOCK_NAME); + block_id_t duc_block_id(0, DUC_BLOCK_NAME); + block_id_t ddc_block_id(0, DDC_BLOCK_NAME); + block_id_t fifo_block_id(0, DFIFO_BLOCK_NAME, 0); + for (size_t i = 0; i < _num_mboards; i++) { + radio_block_id.set_device_no(i); + duc_block_id.set_device_no(i); + ddc_block_id.set_device_no(i); + fifo_block_id.set_device_no(i); + for (size_t k = 0; k < _num_radios_per_board; k++) { + radio_block_id.set_block_count(k); + duc_block_id.set_block_count(k); + ddc_block_id.set_block_count(k); + // Only one FIFO per crossbar, so don't set block count for that block + if (not _device->has_block(radio_block_id) + or (_has_ducs and not _device->has_block(duc_block_id)) + or (_has_ddcs and not _device->has_block(ddc_block_id)) + or (_has_dmafifo and not _device->has_block(fifo_block_id)) + ) { + throw uhd::runtime_error("For legacy APIs, all devices require the same number of radios, DDCs and DUCs."); + } + + const size_t this_spp = get_block_ctrl<radio_ctrl>(i, RADIO_BLOCK_NAME, k)->get_arg<int>("spp"); + if (this_spp != _rx_spp) { + throw uhd::runtime_error(str( + boost::format("[legacy compat] Radios have differing spp values: %s has %d, others have %d") + % radio_block_id.to_string() % this_spp % _rx_spp + )); + } + } + } + } + + /*! Initialize properties in property tree to match legacy mode + */ + void setup_prop_tree() + { + for (size_t mboard_idx = 0; mboard_idx < _num_mboards; mboard_idx++) { + uhd::fs_path root = mb_root(mboard_idx); + // Subdev specs + if (_tree->exists(root / "tx_subdev_spec")) { + _tree->access<subdev_spec_t>(root / "tx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION)) + .update() + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION)); + } else { + _tree->create<subdev_spec_t>(root / "tx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION)) + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION)); + } + + if (_tree->exists(root / "rx_subdev_spec")) { + _tree->access<subdev_spec_t>(root / "rx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION)) + .update() + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION)); + } else { + _tree->create<subdev_spec_t>(root / "rx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION)) + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION)); + } + + if (not _has_ddcs) { + for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) { + for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) { + const uhd::fs_path rx_dsp_base_path(mb_root(mboard_idx) / "rx_dsps" / radio_idx / chan); + _tree->create<double>(rx_dsp_base_path / "rate/value") + .set(0.0) + .set_publisher( + boost::bind( + &radio_ctrl::get_output_samp_rate, + get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx), + chan + ) + ) + ; + _tree->create<uhd::meta_range_t>(rx_dsp_base_path / "rate/range") + .set_publisher( + boost::bind( + &legacy_compat_impl::lambda_get_samp_rate_range, + this, + mboard_idx, radio_idx, chan, + uhd::RX_DIRECTION + ) + ) + ; + _tree->create<double>(rx_dsp_base_path / "freq/value") + .set_publisher(boost::bind(&lambda_const_double, 0.0)) + ; + _tree->create<uhd::meta_range_t>(rx_dsp_base_path / "freq/range") + .set_publisher(boost::bind(&lambda_const_meta_range, 0.0, 0.0, 0.0)) + ; + } + } + } + if (not _has_ducs) { + for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) { + for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) { + const uhd::fs_path tx_dsp_base_path(mb_root(mboard_idx) / "tx_dsps" / radio_idx / chan); + _tree->create<double>(tx_dsp_base_path / "rate/value") + .set(0.0) + .set_publisher( + boost::bind( + &radio_ctrl::get_output_samp_rate, + get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx), + chan + ) + ) + ; + _tree->create<uhd::meta_range_t>(tx_dsp_base_path / "rate/range") + .set_publisher( + boost::bind( + &legacy_compat_impl::lambda_get_samp_rate_range, + this, + mboard_idx, radio_idx, chan, + uhd::TX_DIRECTION + ) + ) + ; + _tree->create<double>(tx_dsp_base_path / "freq/value") + .set_publisher(boost::bind(&lambda_const_double, 0.0)) + ; + _tree->create<uhd::meta_range_t>(tx_dsp_base_path / "freq/range") + .set_publisher(boost::bind(&lambda_const_meta_range, 0.0, 0.0, 0.0)) + ; + } + } + } + } + } + + /*! Default block connections. + * + * Tx connections: + * + * [Host] => DMA FIFO => DUC => Radio + * + * Note: There is only one DMA FIFO per crossbar, with twice the number of ports. + * + * Rx connections: + * + * Radio => DDC => [Host] + * + * Streamers are *not* generated here. + */ + void connect_blocks() + { + _graph = _device->create_graph("legacy"); + const size_t rx_bpp = _rx_spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER; + const size_t tx_bpp = _tx_spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER; + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + // Tx Channels + for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) { + if (_has_ducs) { + _graph->connect( + block_id_t(mboard, DUC_BLOCK_NAME, radio), chan, + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + tx_bpp + ); + if (_has_dmafifo) { + // We have DMA FIFO *and* DUCs + _graph->connect( + block_id_t(mboard, DFIFO_BLOCK_NAME, 0), radio, + block_id_t(mboard, DUC_BLOCK_NAME, radio), chan, + tx_bpp + ); + } + } else if (_has_dmafifo) { + // We have DMA FIFO, *no* DUCs + _graph->connect( + block_id_t(mboard, DFIFO_BLOCK_NAME, 0), radio, + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + tx_bpp + ); + } + } + // Rx Channels + for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) { + if (_has_ddcs) { + _graph->connect( + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + block_id_t(mboard, DDC_BLOCK_NAME, radio), chan, + rx_bpp + ); + } + } + } + } + } + + + /************************************************************************ + * Subdev translation + ***********************************************************************/ + /*! Subdev -> (Radio, Port) + * + * Example: Device is X300, subdev spec is 'A:0 B:0', we have 2 radios. + * Then we map to ((0, 0), (1, 0)). I.e., zero-th port on radio 0 and + * radio 1, respectively. + */ + void set_subdev_spec(const subdev_spec_t &spec, const size_t mboard, const uhd::direction_t dir) + { + UHD_ASSERT_THROW(mboard < _num_mboards); + chan_map_t &chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map; + std::vector<radio_port_pair_t> new_mapping(spec.size()); + for (size_t i = 0; i < spec.size(); i++) { + const size_t new_radio_index = get_radio_index(spec[i].db_name); + radio_ctrl::sptr radio = get_block_ctrl<radio_ctrl>(mboard, "Radio", new_radio_index); + size_t new_port_index = radio->get_chan_from_dboard_fe(spec[i].sd_name, dir); + if (new_port_index >= radio->get_input_ports().size()) { + new_port_index = radio->get_input_ports().at(0); + } + radio_port_pair_t new_radio_port_pair(new_radio_index, new_port_index); + new_mapping[i] = new_radio_port_pair; + } + chan_map[mboard] = new_mapping; + } + + subdev_spec_t get_subdev_spec(const size_t mboard, const uhd::direction_t dir) + { + UHD_ASSERT_THROW(mboard < _num_mboards); + subdev_spec_t subdev_spec; + chan_map_t &chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map; + for (size_t chan_idx = 0; chan_idx < chan_map[mboard].size(); chan_idx++) { + const size_t radio_index = chan_map[mboard][chan_idx].radio_index; + const size_t port_index = chan_map[mboard][chan_idx].port_index; + const std::string new_db_name = get_slot_name(radio_index); + const std::string new_sd_name = + get_block_ctrl<radio_ctrl>(mboard, "Radio", radio_index)->get_dboard_fe_from_chan(port_index, dir); + subdev_spec_pair_t new_pair(new_db_name, new_sd_name); + subdev_spec.push_back(new_pair); + } + + return subdev_spec; + } + + void update_tick_rate_on_blocks(const double tick_rate, const size_t mboard_idx) + { + block_id_t radio_block_id(mboard_idx, RADIO_BLOCK_NAME); + block_id_t duc_block_id(mboard_idx, DUC_BLOCK_NAME); + block_id_t ddc_block_id(mboard_idx, DDC_BLOCK_NAME); + + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + radio_block_id.set_block_count(radio); + duc_block_id.set_block_count(radio); + ddc_block_id.set_block_count(radio); + radio_ctrl::sptr radio_sptr = _device->get_block_ctrl<radio_ctrl>(radio_block_id); + radio_sptr->set_rate(tick_rate); + for (size_t chan = 0; chan < _num_rx_chans_per_radio and _has_ddcs; chan++) { + const double radio_output_rate = radio_sptr->get_output_samp_rate(chan); + _device->get_block_ctrl(ddc_block_id)->set_arg<double>("input_rate", radio_output_rate, chan); + } + for (size_t chan = 0; chan < _num_tx_chans_per_radio and _has_ducs; chan++) { + const double radio_input_rate = radio_sptr->get_input_samp_rate(chan); + _device->get_block_ctrl(duc_block_id)->set_arg<double>("output_rate", radio_input_rate, chan); + } + } + } + +private: // attributes + uhd::device3::sptr _device; + uhd::property_tree::sptr _tree; + + const bool _has_ducs; + const bool _has_ddcs; + const bool _has_dmafifo; + const size_t _num_mboards; + const size_t _num_radios_per_board; + const size_t _num_tx_chans_per_radio; + const size_t _num_rx_chans_per_radio; + size_t _rx_spp; + size_t _tx_spp; + + chan_map_t _rx_channel_map; + chan_map_t _tx_channel_map; + + graph::sptr _graph; +}; + +legacy_compat::sptr legacy_compat::make( + uhd::device3::sptr device, + const uhd::device_addr_t &args +) { + UHD_ASSERT_THROW(bool(device)); + static std::map<void *, boost::weak_ptr<legacy_compat> > legacy_cache; + + if (legacy_cache.count(device.get())) { + legacy_compat::sptr legacy_compat_copy = legacy_cache.at(device.get()).lock(); + if (not bool(legacy_compat_copy)) { + throw uhd::runtime_error("Reference to existing legacy compat object expired prematurely!"); + } + + UHD_LEGACY_LOG() << "[legacy_compat] Using existing legacy compat object for this device." << std::endl; + return legacy_compat_copy; + } + + legacy_compat::sptr new_legacy_compat = boost::make_shared<legacy_compat_impl>(device, args); + legacy_cache[device.get()] = new_legacy_compat; + return new_legacy_compat; +} + diff --git a/host/lib/rfnoc/legacy_compat.hpp b/host/lib/rfnoc/legacy_compat.hpp new file mode 100644 index 000000000..29be1bdc2 --- /dev/null +++ b/host/lib/rfnoc/legacy_compat.hpp @@ -0,0 +1,55 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_RFNOC_LEGACY_COMPAT_HPP +#define INCLUDED_RFNOC_LEGACY_COMPAT_HPP + +#include <uhd/device3.hpp> +#include <uhd/stream.hpp> + +namespace uhd { namespace rfnoc { + + /*! Legacy compatibility layer class. + */ + class legacy_compat + { + public: + typedef boost::shared_ptr<legacy_compat> sptr; + + virtual uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, size_t mboard, size_t chan) = 0; + + virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args) = 0; + + virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args) = 0; + + static sptr make( + uhd::device3::sptr device, + const uhd::device_addr_t &args + ); + }; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_RFNOC_LEGACY_COMPAT_HPP */ diff --git a/host/lib/rfnoc/nocscript/CMakeLists.txt b/host/lib/rfnoc/nocscript/CMakeLists.txt new file mode 100644 index 000000000..77fcc101c --- /dev/null +++ b/host/lib/rfnoc/nocscript/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_basic_funcs.py + ${CMAKE_CURRENT_BINARY_DIR}/basic_functions.hpp +) + +IF(ENABLE_MANUAL) + LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_basic_funcs.py + ${CMAKE_BINARY_DIR}/docs/nocscript_functions.dox + ) +ENDIF(ENABLE_MANUAL) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/expression.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function_table.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/parser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_iface.cpp +) diff --git a/host/lib/rfnoc/nocscript/block_iface.cpp b/host/lib/rfnoc/nocscript/block_iface.cpp new file mode 100644 index 000000000..2034d3438 --- /dev/null +++ b/host/lib/rfnoc/nocscript/block_iface.cpp @@ -0,0 +1,255 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "block_iface.hpp" +#include "function_table.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/assign.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +#define UHD_NOCSCRIPT_LOG() UHD_LOGV(never) + +using namespace uhd::rfnoc; +using namespace uhd::rfnoc::nocscript; + +block_iface::block_iface(block_ctrl_base *block_ptr) + : _block_ptr(block_ptr) +{ + function_table::sptr ft = function_table::make(); + + // Add the SR_WRITE() function + expression_function::argtype_list_type sr_write_args = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + ft->register_function( + "SR_WRITE", + boost::bind(&block_iface::_nocscript__sr_write, this, _1), + expression::TYPE_BOOL, + sr_write_args + ); + + // Add read access to arguments ($foo) + expression_function::argtype_list_type arg_set_args_wo_port = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + expression_function::argtype_list_type arg_set_args_w_port = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + (expression::TYPE_INT) + ; +#define REGISTER_ARG_SETTER(noctype, setter_func) \ + arg_set_args_wo_port[1] = expression::noctype; \ + arg_set_args_w_port[1] = expression::noctype; \ + ft->register_function( \ + "SET_ARG", \ + boost::bind(&block_iface::setter_func, this, _1), \ + expression::TYPE_BOOL, \ + arg_set_args_wo_port \ + ); \ + ft->register_function( \ + "SET_ARG", \ + boost::bind(&block_iface::setter_func, this, _1), \ + expression::TYPE_BOOL, \ + arg_set_args_w_port \ + ); + REGISTER_ARG_SETTER(TYPE_INT, _nocscript__arg_set_int); + REGISTER_ARG_SETTER(TYPE_STRING, _nocscript__arg_set_string); + REGISTER_ARG_SETTER(TYPE_DOUBLE, _nocscript__arg_set_double); + REGISTER_ARG_SETTER(TYPE_INT_VECTOR, _nocscript__arg_set_intvec); + + + // Add read/write access to local variables + expression_function::argtype_list_type set_var_args = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + const expression_function::argtype_list_type get_var_args = boost::assign::list_of + (expression::TYPE_STRING) + ; +#define REGISTER_VAR_ACCESS(noctype, typestr) \ + set_var_args[1] = expression::noctype; \ + ft->register_function( \ + "SET_VAR", \ + boost::bind(&block_iface::_nocscript__var_set, this, _1), \ + expression::TYPE_BOOL, \ + set_var_args \ + ); \ + ft->register_function( \ + "GET_"#typestr, \ + boost::bind(&block_iface::_nocscript__var_get, this, _1), \ + expression::noctype, \ + get_var_args \ + ); + REGISTER_VAR_ACCESS(TYPE_INT, INT); + REGISTER_VAR_ACCESS(TYPE_STRING, STRING); + REGISTER_VAR_ACCESS(TYPE_DOUBLE, DOUBLE); + REGISTER_VAR_ACCESS(TYPE_INT_VECTOR, INT_VECTOR); + + // Create the parser + _parser = parser::make( + ft, + boost::bind(&block_iface::_nocscript__arg_get_type, this, _1), + boost::bind(&block_iface::_nocscript__arg_get_val, this, _1) + ); +} + + +void block_iface::run_and_check(const std::string &code, const std::string &error_message) +{ + boost::mutex::scoped_lock local_interpreter_lock(_lil_mutex); + + UHD_NOCSCRIPT_LOG() << "[NocScript] Executing and asserting code: " << code << std::endl; + expression::sptr e = _parser->create_expr_tree(code); + expression_literal result = e->eval(); + if (not result.to_bool()) { + if (error_message.empty()) { + throw uhd::runtime_error(str( + boost::format("[NocScript] Code returned false: %s") + % code + )); + } else { + throw uhd::runtime_error(str( + boost::format("[NocScript] Error: %s") + % error_message + )); + } + } + + _vars.clear(); // We go out of scope, and so do NocScript variables +} + + +expression_literal block_iface::_nocscript__sr_write(expression_container::expr_list_type args) +{ + const std::string reg_name = args[0]->eval().get_string(); + const boost::uint32_t reg_val = boost::uint32_t(args[1]->eval().get_int()); + bool result = true; + try { + UHD_NOCSCRIPT_LOG() << "[NocScript] Executing SR_WRITE() " << std::endl; + _block_ptr->sr_write(reg_name, reg_val); + } catch (const uhd::exception &e) { + UHD_MSG(error) << boost::format("[NocScript] Error while executing SR_WRITE(%s, 0x%X):\n%s") + % reg_name % reg_val % e.what() + << std::endl; + result = false; + } + + return expression_literal(result); +} + +expression::type_t block_iface::_nocscript__arg_get_type(const std::string &varname) +{ + const std::string var_type = _block_ptr->get_arg_type(varname); + if (var_type == "int") { + return expression::TYPE_INT; + } else if (var_type == "string") { + return expression::TYPE_STRING; + } else if (var_type == "double") { + return expression::TYPE_DOUBLE; + } else if (var_type == "int_vector") { + UHD_THROW_INVALID_CODE_PATH(); // TODO + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal block_iface::_nocscript__arg_get_val(const std::string &varname) +{ + const std::string var_type = _block_ptr->get_arg_type(varname); + if (var_type == "int") { + return expression_literal(_block_ptr->get_arg<int>(varname)); + } else if (var_type == "string") { + return expression_literal(_block_ptr->get_arg<std::string>(varname)); + } else if (var_type == "double") { + return expression_literal(_block_ptr->get_arg<double>(varname)); + } else if (var_type == "int_vector") { + UHD_THROW_INVALID_CODE_PATH(); // TODO + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal block_iface::_nocscript__arg_set_int(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const int val = args[1]->eval().get_int(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<int>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_string(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const std::string val = args[1]->eval().get_string(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<std::string>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_double(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const double val = args[1]->eval().get_double(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<double>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_intvec(const expression_container::expr_list_type &) +{ + UHD_THROW_INVALID_CODE_PATH(); +} + +block_iface::sptr block_iface::make(uhd::rfnoc::block_ctrl_base* block_ptr) +{ + return sptr(new block_iface(block_ptr)); +} + +expression_literal block_iface::_nocscript__var_get(const expression_container::expr_list_type &args) +{ + expression_literal expr = _vars[args[0]->eval().get_string()]; + //std::cout << "[NocScript] Getting var " << args[0]->eval().get_string() << " == " << expr << std::endl; + //std::cout << "[NocScript] Type " << expr.infer_type() << std::endl; + //return _vars[args[0]->eval().get_string()]; + return expr; +} + +expression_literal block_iface::_nocscript__var_set(const expression_container::expr_list_type &args) +{ + _vars[args[0]->eval().get_string()] = args[1]->eval(); + //std::cout << "[NocScript] Set var " << args[0]->eval().get_string() << " to " << _vars[args[0]->eval().get_string()] << std::endl; + //std::cout << "[NocScript] Type " << _vars[args[0]->eval().get_string()].infer_type() << std::endl; + return expression_literal(true); +} + diff --git a/host/lib/rfnoc/nocscript/block_iface.hpp b/host/lib/rfnoc/nocscript/block_iface.hpp new file mode 100644 index 000000000..6354409f2 --- /dev/null +++ b/host/lib/rfnoc/nocscript/block_iface.hpp @@ -0,0 +1,94 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expression.hpp" +#include "parser.hpp" +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/thread/mutex.hpp> + +#ifndef INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP +#define INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +/*! NocScript / Block interface class. + * + * This class only exists as a member of an rfnoc::block_ctrl_base class. + * It should never be instantiated anywhere else. It is used to execute + * NocScript function calls that require access to the original block + * controller class. + */ +class block_iface { + + public: + typedef boost::shared_ptr<block_iface> sptr; + + static sptr make(uhd::rfnoc::block_ctrl_base* block_ptr); + + block_iface(uhd::rfnoc::block_ctrl_base* block_ptr); + + /*! Execute \p code and make sure it returns 'true'. + * + * \param code Must be a valid NocScript expression that returns a boolean value. + * If it returns false, this is interpreted as failure. + * \param error_message If the expression fails, this error message is printed. + * \throws uhd::runtime_error if the expression returns false. + * \throws uhd::syntax_error if the expression is invalid. + */ + void run_and_check(const std::string &code, const std::string &error_message=""); + + private: + //! For the local interpreter lock (lil) + boost::mutex _lil_mutex; + + //! Wrapper for block_ctrl_base::sr_write, so we can call it from within NocScript + expression_literal _nocscript__sr_write(expression_container::expr_list_type); + + //! Argument type getter that can be used within NocScript + expression::type_t _nocscript__arg_get_type(const std::string &argname); + + //! Argument value getter that can be used within NocScript + expression_literal _nocscript__arg_get_val(const std::string &argname); + + //! Argument value setters: + expression_literal _nocscript__arg_set_int(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_string(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_double(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_intvec(const expression_container::expr_list_type &); + + //! Variable value getter + expression_literal _nocscript__var_get(const expression_container::expr_list_type &); + + //! Variable value setter + expression_literal _nocscript__var_set(const expression_container::expr_list_type &); + + //! Raw pointer to the block class. Note that since block_iface may + // only live as a member of a block_ctrl_base, we don't really need + // the reference counting. + uhd::rfnoc::block_ctrl_base* _block_ptr; + + //! Pointer to the parser object + parser::sptr _parser; + + //! Container for scoped variables + std::map<std::string, expression_literal> _vars; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/expression.cpp b/host/lib/rfnoc/nocscript/expression.cpp new file mode 100644 index 000000000..38d6e2128 --- /dev/null +++ b/host/lib/rfnoc/nocscript/expression.cpp @@ -0,0 +1,413 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expression.hpp" +#include "function_table.hpp" +#include <uhd/utils/cast.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/assign.hpp> +#include <boost/algorithm/string.hpp> + +using namespace uhd::rfnoc::nocscript; + +std::map<expression::type_t, std::string> expression::type_repr = boost::assign::map_list_of + (TYPE_INT, "INT") + (TYPE_DOUBLE, "DOUBLE") + (TYPE_STRING, "STRING") + (TYPE_BOOL, "BOOL") + (TYPE_INT_VECTOR, "INT_VECTOR") +; + +/******************************************************************** + * Literal expressions (constants) + *******************************************************************/ +expression_literal::expression_literal( + const std::string token_val, + expression::type_t type +) : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _val(token_val) + , _type(type) +{ + switch (_type) { + case expression::TYPE_STRING: + // Remove the leading and trailing quotes: + _val = _val.substr(1, _val.size()-2); + break; + + case expression::TYPE_INT: + if (_val.substr(0, 2) == "0x") { + _int_val = uhd::cast::hexstr_cast<int>(_val); + } else { + _int_val = boost::lexical_cast<int>(_val); + } + break; + + case expression::TYPE_DOUBLE: + _double_val = boost::lexical_cast<double>(_val); + break; + + case expression::TYPE_BOOL: + if (boost::to_upper_copy(_val) == "TRUE") { + _bool_val = true; + } else { + // lexical cast to bool is too picky + _bool_val = bool(boost::lexical_cast<int>(_val)); + } + break; + + case expression::TYPE_INT_VECTOR: + { + std::string str_vec = _val.substr(1, _val.size()-2); + std::vector<std::string> subtoken_list; + boost::split(subtoken_list, str_vec, boost::is_any_of(", "), boost::token_compress_on); + BOOST_FOREACH(const std::string &t, subtoken_list) { + _int_vector_val.push_back(boost::lexical_cast<int>(t)); + } + break; + } + + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal::expression_literal(bool b) + : _bool_val(b) + , _int_val(0) + , _double_val(0.0) + , _val("") + , _type(expression::TYPE_BOOL) +{ + // nop +} + +expression_literal::expression_literal(int i) + : _bool_val(false) + , _int_val(i) + , _double_val(0.0) + , _val("") + , _type(expression::TYPE_INT) +{ + // nop +} + +expression_literal::expression_literal(double d) + : _bool_val(false) + , _int_val(0) + , _double_val(d) + , _val("") + , _type(expression::TYPE_DOUBLE) +{ + // nop +} + +expression_literal::expression_literal(const std::string &s) + : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _val(s) + , _type(expression::TYPE_STRING) +{ + // nop +} + +expression_literal::expression_literal(const std::vector<int> v) + : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _int_vector_val(v) + , _val("") + , _type(expression::TYPE_INT_VECTOR) +{ + // nop +} + +bool expression_literal::to_bool() const +{ + switch (_type) { + case TYPE_INT: + return bool(boost::lexical_cast<int>(_val)); + case TYPE_STRING: + return not _val.empty(); + case TYPE_DOUBLE: + return bool(boost::lexical_cast<double>(_val)); + case TYPE_BOOL: + return _bool_val; + case TYPE_INT_VECTOR: + return not _int_vector_val.empty(); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +int expression_literal::get_int() const +{ + if (_type != TYPE_INT) { + throw uhd::type_error("Cannot call get_int() on non-int value."); + } + + return _int_val; +} + +double expression_literal::get_double() const +{ + if (_type != TYPE_DOUBLE) { + throw uhd::type_error("Cannot call get_double() on non-double value."); + } + + return _double_val; +} + +std::string expression_literal::get_string() const +{ + if (_type != TYPE_STRING) { + throw uhd::type_error("Cannot call get_string() on non-string value."); + } + + return _val; +} + +bool expression_literal::get_bool() const +{ + if (_type != TYPE_BOOL) { + throw uhd::type_error("Cannot call get_bool() on non-boolean value."); + } + + return _bool_val; +} + +std::vector<int> expression_literal::get_int_vector() const +{ + if (_type != TYPE_INT_VECTOR) { + throw uhd::type_error("Cannot call get_bool() on non-boolean value."); + } + + return _int_vector_val; +} + +std::string expression_literal::repr() const +{ + switch (_type) { + case TYPE_INT: + return boost::lexical_cast<std::string>(_int_val); + case TYPE_STRING: + return _val; + case TYPE_DOUBLE: + return boost::lexical_cast<std::string>(_double_val); + case TYPE_BOOL: + return _bool_val ? "TRUE" : "FALSE"; + case TYPE_INT_VECTOR: + { + std::stringstream sstr; + sstr << "["; + for (size_t i = 0; i < _int_vector_val.size(); i++) { + if (i > 0) { + sstr << ", "; + } + sstr << _int_vector_val[i]; + } + sstr << "]"; + return sstr.str(); + } + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +bool expression_literal::operator==(const expression_literal &rhs) const +{ + if (rhs.infer_type() != _type) { + return false; + } + + switch (_type) { + case TYPE_INT: + return get_int() == rhs.get_int(); + case TYPE_STRING: + return get_string() == rhs.get_string(); + case TYPE_DOUBLE: + return get_double() == rhs.get_double(); + case TYPE_BOOL: + return get_bool() == rhs.get_bool(); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +/******************************************************************** + * Containers + *******************************************************************/ +expression_container::sptr expression_container::make() +{ + return sptr(new expression_container); +} + +expression::type_t expression_container::infer_type() const +{ + if (_combiner == COMBINE_OR or _combiner == COMBINE_AND) { + return TYPE_BOOL; + } + + if (_sub_exprs.empty()) { + return TYPE_BOOL; + } + + return _sub_exprs.back()->infer_type(); +} + +void expression_container::add(expression::sptr new_expr) +{ + _sub_exprs.push_back(new_expr); +} + +bool expression_container::empty() const +{ + return _sub_exprs.empty(); +} + +void expression_container::set_combiner_safe(const combiner_type c) +{ + if (_combiner == COMBINE_NOTSET) { + _combiner = c; + return; + } + + throw uhd::syntax_error("Attempting to override combiner type"); +} + +expression_literal expression_container::eval() +{ + if (_sub_exprs.empty()) { + return expression_literal(true); + } + + expression_literal ret_val; + BOOST_FOREACH(const expression::sptr &sub_expr, _sub_exprs) { + ret_val = sub_expr->eval(); + if (_combiner == COMBINE_AND and ret_val.to_bool() == false) { + return ret_val; + } + if (_combiner == COMBINE_OR and ret_val.to_bool() == true) { + return ret_val; + } + // For ALL, we return the last one, so just overwrite it + } + return ret_val; +} + +/******************************************************************** + * Functions + *******************************************************************/ +std::string expression_function::to_string(const std::string &name, const argtype_list_type &types) +{ + std::string s = name; + int arg_count = 0; + BOOST_FOREACH(const expression::type_t type, types) { + if (arg_count == 0) { + s += "("; + } else { + s += ", "; + } + s += type_repr[type]; + arg_count++; + } + s += ")"; + + return s; +} + +expression_function::expression_function( + const std::string &name, + const function_table::sptr func_table +) : _name(name) + , _func_table(func_table) +{ + _combiner = COMBINE_ALL; + if (not _func_table->function_exists(_name)) { + throw uhd::syntax_error(str( + boost::format("Unknown function: %s") + % _name + )); + } +} + +void expression_function::add(expression::sptr new_expr) +{ + expression_container::add(new_expr); + _arg_types.push_back(new_expr->infer_type()); +} + +expression::type_t expression_function::infer_type() const +{ + return _func_table->get_type(_name, _arg_types); +} + +expression_literal expression_function::eval() +{ + return _func_table->eval(_name, _arg_types, _sub_exprs); +} + + +std::string expression_function::repr() const +{ + return to_string(_name, _arg_types); +} + +expression_function::sptr expression_function::make( + const std::string &name, + const function_table::sptr func_table +) { + return sptr(new expression_function(name, func_table)); +} + +/******************************************************************** + * Variables + *******************************************************************/ +expression_variable::expression_variable( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter +) : _type_getter(type_getter) + , _value_getter(value_getter) +{ + // We can assume this is true because otherwise, it's not a valid token: + UHD_ASSERT_THROW(not token_val.empty() and token_val[0] == '$'); + + _varname = token_val.substr(1); +} + +expression::type_t expression_variable::infer_type() const +{ + return _type_getter(_varname); +} + +expression_literal expression_variable::eval() +{ + return _value_getter(_varname); +} + +expression_variable::sptr expression_variable::make( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter +) { + return sptr(new expression_variable(token_val, type_getter, value_getter)); +} + diff --git a/host/lib/rfnoc/nocscript/expression.hpp b/host/lib/rfnoc/nocscript/expression.hpp new file mode 100644 index 000000000..83fc5bcbc --- /dev/null +++ b/host/lib/rfnoc/nocscript/expression.hpp @@ -0,0 +1,380 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/exception.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/function.hpp> +#include <boost/make_shared.hpp> +#include <vector> +#include <map> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +// Forward declaration for expression::eval() +class expression_literal; + +/*! Virtual base class for Noc-Script expressions. + */ +class expression +{ + public: + typedef boost::shared_ptr<expression> sptr; + + //! All the possible return types for expressions within Noc-Script + enum type_t { + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING, + TYPE_BOOL, + TYPE_INT_VECTOR + }; + + // TODO make this a const and fix the [] usage + static std::map<type_t, std::string> type_repr; + + //! Returns the type of this expression without evaluating it + virtual type_t infer_type() const = 0; + + //! Evaluate current expression and return its return value + virtual expression_literal eval() = 0; +}; + +/*! Literal (constant) expression class + * + * A literal is any value that is literally given in the NoC-Script + * source code, such as '5', '"FOO"', or '2.3'. + */ +class expression_literal : public expression +{ + public: + typedef boost::shared_ptr<expression_literal> sptr; + + template <typename expr_type> + static sptr make(expr_type x) { return boost::make_shared<expression_literal>(x); }; + + /*! Generate the literal expression from its token string representation. + * This includes markup, e.g. a string would still have the quotes, and + * a hex value would still have leading 0x. + */ + expression_literal( + const std::string token_val, + expression::type_t type + ); + + //! Create a boolean literal expression from a C++ bool. + expression_literal(bool b=false); + //! Create an integer literal expression from a C++ int. + expression_literal(int i); + //! Create a double literal expression from a C++ double. + expression_literal(double d); + //! Create a string literal expression from a C++ string. + expression_literal(const std::string &s); + //! Create an int vector literal expression from a C++ vector<int>. + expression_literal(std::vector<int> v); + + expression::type_t infer_type() const + { + return _type; + } + + //! Literals aren't evaluated as such, so the evaluation + // simply returns a copy of itself. + expression_literal eval() + { + return *this; // TODO make sure this is copy + } + + /*! A 'type cast' to bool. Cast rules are similar to most + * scripting languages: + * - Integers and doubles are false if zero, true otherwise + * - Strings are false if empty, true otherwise + * - Vectors are false if empty, true otherwise + */ + bool to_bool() const; + + /*! Convenience function to typecast to C++ int + * + * Note that the current type must be TYPE_INT. + * + * \return C++ int representation of current literal + * \throws uhd::type_error if type didn't match + */ + int get_int() const; + + /*! Convenience function to typecast to C++ double + * + * Note that the current type must be TYPE_DOUBLE. + * + * \return C++ double representation of current literal + * \throws uhd::type_error if type didn't match + */ + double get_double() const; + + /*! Convenience function to typecast to C++ std::string. + * + * Note that the current type must be TYPE_STRING. + * + * \return String representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + std::string get_string() const; + + /*! Convenience function to typecast to C++ int vector. + * + * Note that the current type must be TYPE_INT_VECTOR. + * + * \return String representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + std::vector<int> get_int_vector() const; + + /*! Convenience function to typecast to C++ bool. + * + * Note that the current type must be TYPE_BOOL. + * See also expression_literal::to_bool() for a type-cast + * style function. + * + * \return bool representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + bool get_bool() const; + + //! String representation + std::string repr() const; + + bool operator==(const expression_literal &rhs) const; + + private: + //! For TYPE_BOOL + bool _bool_val; + + //! For TYPE_INT + int _int_val; + + //! For TYPE_DOUBLE + double _double_val; + + //! For TYPE_INT_VECTOR + std::vector<int> _int_vector_val; + + //! Store the token value + std::string _val; + + //! Current expression type + expression::type_t _type; +}; + +UHD_INLINE std::ostream& operator<< (std::ostream& out, const expression_literal &l) +{ + out << l.repr(); + return out; +} + +UHD_INLINE std::ostream& operator<< (std::ostream& out, const expression_literal::sptr &l) +{ + out << l->repr(); + return out; +} + +/*! Contains multiple (sub-)expressions. + */ +class expression_container : public expression +{ + public: + typedef boost::shared_ptr<expression_container> sptr; + typedef std::vector<expression::sptr> expr_list_type; + + //! Return an sptr to an empty container + static sptr make(); + + //! List of valid combination types (see expression_container::eval()). + enum combiner_type { + COMBINE_ALL, + COMBINE_AND, + COMBINE_OR, + COMBINE_NOTSET + }; + + //! Create an empty container + expression_container() : _combiner(COMBINE_NOTSET) {}; + + /*! Type-deduction rules for containers are: + * - If the combination type is COMBINE_ALL or COMBINE_AND, + * return value must be TYPE_BOOL + * - In all other cases, we return the last expression return + * value, and hence its type is relevant + */ + expression::type_t infer_type() const; + + /*! Add another expression container to this container. + */ + virtual void add(expression::sptr new_expr); + + virtual bool empty() const; + + void set_combiner_safe(const combiner_type c); + + void set_combiner(const combiner_type c) { _combiner = c; }; + + combiner_type get_combiner() const { return _combiner; }; + + /*! Evaluate a container by evaluating its sub-expressions. + * + * If a container contains multiple sub-expressions, the rules + * for evaluating them depend on the combiner_type: + * - COMBINE_ALL: Run all the sub-expressions and return the last + * expression's return value + * - COMBINE_AND: Run sub-expressions, in order, until one of them + * returns false. Following expressions are not evaluated (like + * most C++ compilers). + * - COMBINE_OR: Run sub-expressions, in order, until one of them + * returns true. Following expressions are not evaluated. + * + * In the special case where no sub-expressions are contained, always + * returns true. + */ + virtual expression_literal eval(); + + protected: + //! Store all the sub-expressions, in order + expr_list_type _sub_exprs; + combiner_type _combiner; +}; + +// Forward declaration: +class function_table; +/*! A function call is a special type of container. + * + * All arguments are sub-expressions. The combiner type is + * always COMBINE_ALL in this case (changing the combiner type + * does not affect anything). + * + * The actual function maps to a C++ function available through + * a uhd::rfnoc::nocscript::function_table object. + * + * The recommended to use this is: + * 1. Create a function object giving its name (e.g. ADD) + * 2. Use the add() method to add all the function arguments + * in the right order (left to right). + * 3. Once step 2 is complete, the function object can be used. + * Call infer_type() to get the return value, if required. + * 4. Calling eval() will call into the function table. The + * argument expressions are evaluated, if so required, inside + * the function (lazy evalulation). Functions do not need + * to evaluate arguments. + */ +class expression_function : public expression_container +{ + public: + typedef boost::shared_ptr<expression_function> sptr; + typedef std::vector<expression::type_t> argtype_list_type; + + //! Return an sptr to a function object without args + static sptr make( + const std::string &name, + const boost::shared_ptr<function_table> func_table + ); + + static std::string to_string(const std::string &name, const argtype_list_type &types); + + expression_function( + const std::string &name, + const boost::shared_ptr<function_table> func_table + ); + + //! Add an argument expression + virtual void add(expression::sptr new_expr); + + /*! Looks up the function type in the function table. + * + * Note that this will only work after all arguments have been + * added, as they are also used to look up a function's type in the + * function table. + */ + expression::type_t infer_type() const; + + /*! Evaluate all arguments, then the function itself. + */ + expression_literal eval(); + + //! String representation + std::string repr() const; + + private: + std::string _name; + const boost::shared_ptr<function_table> _func_table; + std::vector<expression::type_t> _arg_types; +}; + + +/*! Variable expression + * + * Variables are like literals, only their type and value aren't known + * at parse-time. Instead, we provide a function object to look up + * variable's types and value. + */ +class expression_variable : public expression +{ + public: + typedef boost::shared_ptr<expression_variable> sptr; + typedef boost::function<expression::type_t(const std::string &)> type_getter_type; + typedef boost::function<expression_literal(const std::string &)> value_getter_type; + + static sptr make( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter + ); + + /*! Create a variable object from its token value + * (e.g. '$spp', i.e. including the '$' symbol). The variable + * does not have to exist at this point. + */ + expression_variable( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter + ); + + /*! Looks up the variable type in the variable table. + * + * \throws Depending on \p type_getter, this may throw when the variable does not exist. + * Recommended behaviour is to throw uhd::syntax_error. + */ + expression::type_t infer_type() const; + + /*! Look up a variable's value in the variable table. + * + * \throws Depending on \p value_getter, this may throw when the variable does not exist. + * Recommended behaviour is to throw uhd::syntax_error. + */ + expression_literal eval(); + + private: + std::string _varname; + type_getter_type _type_getter; + value_getter_type _value_getter; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/function_table.cpp b/host/lib/rfnoc/nocscript/function_table.cpp new file mode 100644 index 000000000..bebceb8dc --- /dev/null +++ b/host/lib/rfnoc/nocscript/function_table.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "function_table.hpp" +#include "basic_functions.hpp" +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <map> + +using namespace uhd::rfnoc::nocscript; + +class function_table_impl : public function_table +{ + public: + struct function_info { + expression::type_t return_type; + function_ptr function; + + function_info() {}; + function_info(const expression::type_t return_type_, const function_ptr &function_) + : return_type(return_type_), function(function_) + {}; + }; + // Should be an unordered_map... sigh, we'll get to C++11 someday. + typedef std::map<std::string, std::map<expression_function::argtype_list_type, function_info> > table_type; + + /************************************************************************ + * Structors + ***********************************************************************/ + function_table_impl() + { + _REGISTER_ALL_FUNCS(); + } + + ~function_table_impl() {}; + + + /************************************************************************ + * Interface implementation + ***********************************************************************/ + bool function_exists(const std::string &name) const { + return bool(_table.count(name)); + } + + bool function_exists( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + table_type::const_iterator it = _table.find(name); + return (it != _table.end()) and bool(it->second.count(arg_types)); + } + + expression::type_t get_type( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + table_type::const_iterator it = _table.find(name); + if (it == _table.end() or (it->second.find(arg_types) == it->second.end())) { + throw uhd::syntax_error(str( + boost::format("Unable to retrieve return value for function %s") + % expression_function::to_string(name, arg_types) + )); + } + return it->second.find(arg_types)->second.return_type; + } + + expression_literal eval( + const std::string &name, + const expression_function::argtype_list_type &arg_types, + expression_container::expr_list_type &arguments + ) { + if (not function_exists(name, arg_types)) { + throw uhd::syntax_error(str( + boost::format("Cannot eval() function %s, not a known signature") + % expression_function::to_string(name, arg_types) + )); + } + + return _table[name][arg_types].function(arguments); + } + + void register_function( + const std::string &name, + const function_table::function_ptr &ptr, + const expression::type_t return_type, + const expression_function::argtype_list_type &sig + ) { + _table[name][sig] = function_info(return_type, ptr); + } + + private: + table_type _table; +}; + +function_table::sptr function_table::make() +{ + return sptr(new function_table_impl()); +} diff --git a/host/lib/rfnoc/nocscript/function_table.hpp b/host/lib/rfnoc/nocscript/function_table.hpp new file mode 100644 index 000000000..6c715308e --- /dev/null +++ b/host/lib/rfnoc/nocscript/function_table.hpp @@ -0,0 +1,93 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expression.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <vector> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +class function_table +{ + public: + typedef boost::shared_ptr<function_table> sptr; + typedef boost::function<expression_literal(expression_container::expr_list_type&)> function_ptr; + + static sptr make(); + virtual ~function_table() {}; + + /*! Check if any function with a given name exists + * + * \returns True, if any function with name \p name is registered. + */ + virtual bool function_exists(const std::string &name) const = 0; + + /*! Check if a function with a given name and list of argument types exists + * + * \returns True, if such a function is registered. + */ + virtual bool function_exists( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const = 0; + + /*! Get the return type of a function with given name and argument type list + * + * \returns The function's return type + * \throws uhd::syntax_error if no such function is registered + */ + virtual expression::type_t get_type( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const = 0; + + /*! Calls the function \p name with the argument list \p arguments + * + * \param arg_types A list of types for each argument + * \param arguments An expression list of the arguments + * \returns The return value of the called function + * \throws uhd::syntax_error if no such function is found + */ + virtual expression_literal eval( + const std::string &name, + const expression_function::argtype_list_type &arg_types, + expression_container::expr_list_type &arguments + ) = 0; + + /*! Register a new function + * + * \param name Name of the function (e.g. 'ADD') + * \param ptr Function object + * \param return_type The function's return value + * \param sig The function signature (list of argument types) + */ + virtual void register_function( + const std::string &name, + const function_ptr &ptr, + const expression::type_t return_type, + const expression_function::argtype_list_type &sig + ) = 0; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/gen_basic_funcs.py b/host/lib/rfnoc/nocscript/gen_basic_funcs.py new file mode 100755 index 000000000..702b3e884 --- /dev/null +++ b/host/lib/rfnoc/nocscript/gen_basic_funcs.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python +""" +Generate the function list for the basic NocScript functions +""" + +import re +import os +import sys +from mako.template import Template + +############################################################################# +# This is the interesting part: Add new functions in here +# +# Notes: +# - Lines starting with # are considered comments, and will be removed from +# the output +# - C++ comments will be copied onto the generated file if inside functions +# - Docstrings start with //! and are required +# - Function signature is RETURN_TYPE NAME(ARG_TYPE1, ARG_TYPE2, ...) +# - Function body is valid C++ +# - If your function requires special includes, put them in INCLUDE_LIST +# - End of functions is delimited by s/^}/, so take care with the indents! +# - Use these substitutions: +# - ${RETURN}(...): Create a valid return value +# - ${args[n]}: Access the n-th argument +# +INCLUDE_LIST = """ +#include <boost/math/special_functions/round.hpp> +#include <boost/thread/thread.hpp> +""" +FUNCTION_LIST = """ +CATEGORY: Math Functions +//! Returns x + y +INT ADD(INT, INT) +{ + ${RETURN}(${args[0]} + ${args[1]}); +} + +//! Returns x + y +DOUBLE ADD(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} + ${args[1]}); +} + +//! Returns x * y +DOUBLE MULT(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} * ${args[1]}); +} + +//! Returns x * y +INT MULT(INT, INT) +{ + ${RETURN}(${args[0]} * ${args[1]}); +} + +//! Returns x / y +DOUBLE DIV(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} / ${args[1]}); +} + +//! Returns true if x <= y (Less or Equal) +BOOL LE(INT, INT) +{ + ${RETURN}(bool(${args[0]} <= ${args[1]})); +} + +//! Returns true if x <= y (Less or Equal) +BOOL LE(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} <= ${args[1]})); +} + +//! Returns true if x >= y (Greater or Equal) +BOOL GE(INT, INT) +{ + ${RETURN}(bool(${args[0]} >= ${args[1]})); +} + +//! Returns true if x >= y (Greater or Equal) +BOOL GE(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} >= ${args[1]})); +} + +//! Returns true if x < y (Less Than) +BOOL LT(INT, INT) +{ + ${RETURN}(bool(${args[0]} < ${args[1]})); +} + +//! Returns true if x > y (Greater Than) +BOOL GT(INT, INT) +{ + ${RETURN}(bool(${args[0]} > ${args[1]})); +} + +//! Returns true if x < y (Less Than) +BOOL LT(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} < ${args[1]})); +} + +//! Returns true if x > y (Greater Than) +BOOL GT(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} > ${args[1]})); +} + +//! Round x and return it as an integer +INT IROUND(DOUBLE) +{ + ${RETURN}(int(boost::math::iround(${args[0]}))); +} + +//! Returns true if x is a power of 2 +BOOL IS_PWR_OF_2(INT) +{ + if (${args[0]} < 0) return ${FALSE}; + int i = ${args[0]}; + while ( (i & 1) == 0 and (i > 1) ) { + i >>= 1; + } + ${RETURN}(bool(i == 1)); +} + +//! Returns floor(log2(x)). +INT LOG2(INT) +{ + if (${args[0]} < 0) { + throw uhd::runtime_error(str( + boost::format("In NocScript function ${func_name}: Cannot calculate log2() of negative number.") + )); + } + + int power_value = ${args[0]}; + int log2_value = 0; + while ( (power_value & 1) == 0 and (power_value > 1) ) { + power_value >>= 1; + log2_value++; + } + ${RETURN}(log2_value); +} + +//! Returns x % y +INT MODULO(INT, INT) +{ + ${RETURN}(${args[0]} % ${args[1]}); +} + +//! Returns true if x == y +BOOL EQUAL(INT, INT) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +//! Returns true if x == y +BOOL EQUAL(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +//! Returns true if x == y +BOOL EQUAL(STRING, STRING) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +CATEGORY: Bitwise Operations +//! Returns x >> y +INT SHIFT_RIGHT(INT, INT) +{ + ${RETURN}(${args[0]} >> ${args[1]}); +} + +//! Returns x << y +INT SHIFT_LEFT(INT, INT) +{ + ${RETURN}(${args[0]} << ${args[1]}); +} + +//! Returns x & y +INT BITWISE_AND(INT, INT) +{ + ${RETURN}(${args[0]} & ${args[1]}); +} + +//! Returns x | y +INT BITWISE_OR(INT, INT) +{ + ${RETURN}(${args[0]} | ${args[1]}); +} + +//! Returns x ^ y +INT BITWISE_XOR(INT, INT) +{ + ${RETURN}(${args[0]} ^ ${args[1]}); +} + +CATEGORY: Boolean Logic +//! Returns x xor y. +BOOL XOR(BOOL, BOOL) +{ + ${RETURN}(${args[0]} xor ${args[1]}); +} + +//! Returns !x +BOOL NOT(BOOL) +{ + ${RETURN}(not ${args[0]}); +} + +//! Always returns true +BOOL TRUE() +{ + return ${TRUE}; +} + +//! Always returns false +BOOL FALSE() +{ + return ${FALSE}; +} + +CATEGORY: Conditional Execution +//! Executes x, if true, execute y. Returns true if x is true. +BOOL IF(BOOL, BOOL) +{ + if (${args[0]}) { + ${args[1]}; + ${RETURN}(true); + } + ${RETURN}(false); +} + +//! Executes x, if true, execute y, otherwise, execute z. Returns true if x is true. +BOOL IF_ELSE(BOOL, BOOL, BOOL) +{ + if (${args[0]}) { + ${args[1]}; + ${RETURN}(true); + } else { + ${args[2]}; + } + ${RETURN}(false); +} + +CATEGORY: Execution Control +//! Sleep for x seconds. Fractions are allowed. Millisecond accuracy. +BOOL SLEEP(DOUBLE) +{ + int ms = ${args[0]} / 1000; + boost::this_thread::sleep(boost::posix_time::milliseconds(ms)); + ${RETURN}(true); +} +""" +# End of interesting part. The rest will take this and turn into a C++ +# header file. +############################################################################# + +HEADER = """<% import time %>// +/////////////////////////////////////////////////////////////////////// +// This file was generated by ${file} on ${time.strftime("%c")} +/////////////////////////////////////////////////////////////////////// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +/**************************************************************************** + * This file is autogenerated! Any manual changes in here will be + * overwritten by calling nocscript_gen_basic_funcs.py! + ***************************************************************************/ + +#include "expression.hpp" +#include "function_table.hpp" +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +${INCLUDE_LIST} + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { +""" + +# Not a Mako template: +FOOTER=""" +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP */ +""" + +# Not a Mako template: +FUNC_TEMPLATE = """ +expression_literal {NAME}(expression_container::expr_list_type &{ARGS}) +{BODY} +""" + +REGISTER_MACRO_TEMPLATE = """#define _REGISTER_ALL_FUNCS()${registry} +""" + +REGISTER_COMMANDS_TEMPLATE = """ + % if len(arglist): + expression_function::argtype_list_type ${func_name}_args = boost::assign::list_of + % for this_type in arglist: + (expression::TYPE_${this_type}) + % endfor + ; + % else: + expression_function::argtype_list_type ${func_name}_args; + % endif + register_function( + "${name}", + boost::bind(&${func_name}, _1), + expression::TYPE_${retval}, + ${func_name}_args + );""" + +DOXY_TEMPLATE = """/*! \page page_nocscript_funcs NocScript Function Reference +% for cat, func_by_name in func_list_tree.iteritems(): +- ${cat} +% for func_name, func_info_list in func_by_name.iteritems(): + - ${func_name}: ${func_info_list[0]['docstring']} +% for func_info in func_info_list: + - ${func_info['arglist']} -> ${func_info['retval']} +% endfor +% endfor +% endfor + +*/ +""" + +def parse_tmpl(_tmpl_text, **kwargs): + return Template(_tmpl_text).render(**kwargs) + +def make_cxx_func_name(func_dict): + """ + Creates a unique C++ function name from a function description + """ + return "{name}__{retval}__{arglist}".format( + name=func_dict['name'], + retval=func_dict['retval'], + arglist="_".join(func_dict['arglist']) + ) + +def make_cxx_func_body(func_dict): + """ + Formats the function body properly + """ + type_lookup_methods = { + 'INT': 'get_int', + 'DOUBLE': 'get_double', + 'BOOL': 'get_bool', + 'STRING': 'get_string', + } + args_lookup = [] + for idx, arg_type in enumerate(func_dict['arglist']): + args_lookup.append("args[{idx}]->eval().{getter}()".format(idx=idx, getter=type_lookup_methods[arg_type])) + return parse_tmpl( + func_dict['body'], + args=args_lookup, + FALSE='expression_literal(false)', + TRUE='expression_literal(true)', + RETURN='return expression_literal', + **func_dict + ) + +def prep_function_list(): + """ + - Remove all comments + - Split the function list into individual functions + - Split the functions into return value, name, argument list and body + """ + comment_remove_re = re.compile(r'^\s*#.*$', flags=re.MULTILINE) + func_list_wo_comments = comment_remove_re.sub('', FUNCTION_LIST) + func_splitter_re = re.compile(r'(?<=^})\s*$', flags=re.MULTILINE) + func_list_split = func_splitter_re.split(func_list_wo_comments) + func_list_split = [x.strip() for x in func_list_split if len(x.strip())] + func_list = [] + last_category = '' + for func in func_list_split: + split_regex = r'(^CATEGORY: (?P<cat>[^\n]*)\s*)?' \ + r'//!(?P<docstring>[^\n]*)\s*' + \ + r'(?P<retval>[A-Z][A-Z0-9_]*)\s+' + \ + r'(?P<funcname>[A-Z][A-Z0-9_]*)\s*\((?P<arglist>[^\)]*)\)\s*' + \ + r'(?P<funcbody>^{.*)' + split_re = re.compile(split_regex, flags=re.MULTILINE|re.DOTALL) + mo = split_re.match(func) + if mo.group('cat'): + last_category = mo.group('cat').strip() + func_dict = { + 'docstring': mo.group('docstring').strip(), + 'name': mo.group('funcname'), + 'retval': mo.group('retval'), + 'arglist': [x.strip() for x in mo.group('arglist').split(',') if len(x.strip())], + 'body': mo.group('funcbody'), + 'category': last_category, + } + func_dict['func_name'] = make_cxx_func_name(func_dict) + func_list.append(func_dict) + return func_list + +def write_function_header(output_filename): + """ + Create the .hpp file that defines all the NocScript functions in C++. + """ + func_list = prep_function_list() + # Step 1: Write the prototypes + func_prototypes = '' + registry_commands = '' + for func in func_list: + func_prototypes += FUNC_TEMPLATE.format( + NAME=func['func_name'], + BODY=make_cxx_func_body(func), + ARGS="args" if len(func['arglist']) else "" + ) + registry_commands += parse_tmpl( + REGISTER_COMMANDS_TEMPLATE, + **func + ) + # Step 2: Write the registry process + register_func = parse_tmpl(REGISTER_MACRO_TEMPLATE, registry=registry_commands) + register_func = register_func.replace('\n', ' \\\n') + + # Final step: Join parts and write to file + full_file = "\n".join(( + parse_tmpl(HEADER, file = os.path.basename(__file__), INCLUDE_LIST=INCLUDE_LIST), + func_prototypes, + register_func, + FOOTER, + )) + open(output_filename, 'w').write(full_file) + +def write_manual_file(output_filename): + """ + Write the Doxygen file for the NocScript functions. + """ + func_list = prep_function_list() + func_list_tree = {} + for func in func_list: + if not func_list_tree.has_key(func['category']): + func_list_tree[func['category']] = {} + if not func_list_tree[func['category']].has_key(func['name']): + func_list_tree[func['category']][func['name']] = [] + func_list_tree[func['category']][func['name']].append(func) + open(output_filename, 'w').write(parse_tmpl(DOXY_TEMPLATE, func_list_tree=func_list_tree)) + + +def main(): + if len(sys.argv) < 2: + print("No output file specified!") + exit(1) + outfile = sys.argv[1] + if os.path.splitext(outfile)[1] == '.dox': + write_manual_file(outfile) + else: + write_function_header(outfile) + +if __name__ == "__main__": + main() diff --git a/host/lib/rfnoc/nocscript/parser.cpp b/host/lib/rfnoc/nocscript/parser.cpp new file mode 100644 index 000000000..bb7ed6cdf --- /dev/null +++ b/host/lib/rfnoc/nocscript/parser.cpp @@ -0,0 +1,363 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "parser.hpp" +#include <uhd/utils/cast.hpp> +#include <boost/spirit/include/lex_lexertl.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/assign.hpp> +#include <boost/make_shared.hpp> +#include <sstream> +#include <stack> + +using namespace uhd::rfnoc::nocscript; +namespace lex = boost::spirit::lex; + +class parser_impl : public parser +{ + public: + /****************************************************************** + * Structors TODO make them protected + *****************************************************************/ + parser_impl( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter + ) : _ftable(ftable) + , _var_type_getter(var_type_getter) + , _var_value_getter(var_value_getter) + { + // nop + } + + ~parser_impl() {}; + + + /****************************************************************** + * Parsing + *****************************************************************/ + //! List of parser tokens + enum token_ids + { + ID_WHITESPACE = lex::min_token_id + 42, + ID_KEYWORD, + ID_ARG_SEP, + ID_PARENS_OPEN, + ID_PARENS_CLOSE, + ID_VARIABLE, + ID_LITERAL_DOUBLE, + ID_LITERAL_INT, + ID_LITERAL_HEX, + ID_LITERAL_STR, + ID_LITERAL_VECTOR_INT + }; + + //! The Lexer object used for NocScript + template <typename Lexer> + struct ns_lexer : lex::lexer<Lexer> + { + ns_lexer() + { + this->self.add + ("\\s+", ID_WHITESPACE) + (",", ID_ARG_SEP) + ("[A-Z][A-Z0-9_]*", ID_KEYWORD) + ("\\(", ID_PARENS_OPEN) + ("\\)", ID_PARENS_CLOSE) + ("\\$[a-z][a-z0-9_]*", ID_VARIABLE) + ("-?\\d+\\.\\d+", ID_LITERAL_DOUBLE) + ("-?\\d+", ID_LITERAL_INT) + ("0x[0-9A-F]+", ID_LITERAL_HEX) + ("\\\"[^\\\"]*\\\"", ID_LITERAL_STR) + ("'[^']*'", ID_LITERAL_STR) // both work + ("\\[[0-9]\\]", ID_LITERAL_VECTOR_INT) + ; + } + }; + + private: + struct grammar_props + { + function_table::sptr ftable; + expression_variable::type_getter_type var_type_getter; + expression_variable::value_getter_type var_value_getter; + + //! Store the last keyword + std::string function_name; + std::string error; + std::stack<expression_container::sptr> expr_stack; + + grammar_props( + function_table::sptr ftable_, + expression_variable::type_getter_type var_type_getter_, + expression_variable::value_getter_type var_value_getter_ + ) : ftable(ftable_), var_type_getter(var_type_getter_), var_value_getter(var_value_getter_), + function_name("") + { + UHD_ASSERT_THROW(expr_stack.empty()); + // Push an empty container to the stack to hold the result + expr_stack.push(expression_container::make()); + } + + expression::sptr get_result() + { + UHD_ASSERT_THROW(expr_stack.size() == 1); + return expr_stack.top(); + } + }; + + //! This isn't strictly a grammar, as it also includes semantic + // actions etc. I'm not going to spend ages thinking of a better + // name at this point. + struct grammar + { + // Implementation detail specific to boost::bind (see Boost::Spirit + // examples) + typedef bool result_type; + + static const int VALID_COMMA = 0x1; + static const int VALID_PARENS_OPEN = 0x2; + static const int VALID_PARENS_CLOSE = 0x4; + static const int VALID_EXPRESSION = 0x8 + 0x02; + static const int VALID_OPERATOR = 0x10; + + // !This function operator gets called for each of the matched tokens. + template <typename Token> + bool operator()(Token const& t, grammar_props &P, int &next_valid_state) const + { + //! This is totally not how Boost::Spirit is meant to be used, + // as there's token types etc. But for now let's just convert + // every token to a string, and then handle it as such. + std::stringstream sstr; + sstr << t.value(); + std::string val = sstr.str(); + //std::cout << "VAL: " << val << std::endl; + //std::cout << "Next valid states:\n" + //<< boost::format("VALID_COMMA [%s]\n") % ((next_valid_state & 0x1) ? "x" : " ") + //<< boost::format("VALID_PARENS_OPEN [%s]\n") % ((next_valid_state & 0x2) ? "x" : " ") + //<< boost::format("VALID_PARENS_CLOSE [%s]\n") % ((next_valid_state & 0x4) ? "x" : " ") + //<< boost::format("VALID_EXPRESSION [%s]\n") % ((next_valid_state & (0x8 + 0x02)) ? "x" : " ") + //<< boost::format("VALID_OPERATOR [%s]\n") % ((next_valid_state & 0x10) ? "x" : " ") + //<< std::endl; + + switch (t.id()) { + + case ID_WHITESPACE: + // Ignore + break; + + case ID_KEYWORD: + // Ambiguous, could be an operator (AND, OR) or a function name (ADD, MULT...). + // So first, check which it is: + if (val == "AND" or val == "OR") { + if (not (next_valid_state & VALID_OPERATOR)) { + P.error = str(boost::format("Unexpected operator: %s") % val); + return false; + } + next_valid_state = VALID_EXPRESSION; + try { + if (val == "AND") { + P.expr_stack.top()->set_combiner_safe(expression_container::COMBINE_AND); + } else if (val == "OR") { + P.expr_stack.top()->set_combiner_safe(expression_container::COMBINE_OR); + } + } catch (const uhd::syntax_error &e) { + P.error = str(boost::format("Operator %s is mixing operator types within this container.") % val); + } + // Right now, we can't have multiple operator types within a container. + // We might be able to change that, if there's enough demand. Either + // we keep track of multiple operators, or we open a new container. + // In the latter case, we'd need a way of keeping track of those containers, + // so it's a bit tricky. + break; + } + // If it's not a keyword, it has to be a function, so check the + // function table: + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression: %s") % val); + return false; + } + if (not P.ftable->function_exists(val)) { + P.error = str(boost::format("Unknown function: %s") % val); + return false; + } + P.function_name = val; + next_valid_state = VALID_PARENS_OPEN; + break; + + // Every () creates a new container, either a raw container or + // a function. + case ID_PARENS_OPEN: + if (not (next_valid_state & VALID_PARENS_OPEN)) { + P.error = str(boost::format("Unexpected parentheses.")); + return false; + } + if (not P.function_name.empty()) { + // We've already checked the function name exists + P.expr_stack.push(expression_function::make(P.function_name, P.ftable)); + P.function_name.clear(); + } else { + P.expr_stack.push(expression_container::make()); + } + // Push another empty container to hold the first element/argument + // in this container: + P.expr_stack.push(expression_container::make()); + next_valid_state = VALID_EXPRESSION | VALID_PARENS_CLOSE; + break; + + case ID_PARENS_CLOSE: + { + if (not (next_valid_state & VALID_PARENS_CLOSE)) { + P.error = str(boost::format("Unexpected parentheses.")); + return false; + } + if (P.expr_stack.size() < 2) { + P.error = str(boost::format("Unbalanced closing parentheses.")); + return false; + } + // First pop the last expression inside the parentheses, + // if it's not empty, add it to the top container (this also avoids + // adding arguments to functions if none were provided): + expression_container::sptr c = P.expr_stack.top(); + P.expr_stack.pop(); + if (not c->empty()) { + P.expr_stack.top()->add(c); + } + // At the end of (), either a function or container is complete, + // so pop that and add it to its top container: + expression_container::sptr c2 = P.expr_stack.top(); + P.expr_stack.pop(); + P.expr_stack.top()->add(c2); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + } + break; + + case ID_ARG_SEP: + { + if (not (next_valid_state & VALID_COMMA)) { + P.error = str(boost::format("Unexpected comma.")); + return false; + } + next_valid_state = VALID_EXPRESSION; + // If stack size is 1, we're on the base container, which means we + // simply string stuff. + if (P.expr_stack.size() == 1) { + break; + } + // Otherwise, a ',' always means we add the previous expression to + // the current container: + expression_container::sptr c = P.expr_stack.top(); + P.expr_stack.pop(); + P.expr_stack.top()->add(c); + // It also means another expression is following, so create another + // empty container for that: + P.expr_stack.push(expression_container::make()); + } + break; + + // All the atomic expressions just get added to the current container: + + case ID_VARIABLE: + { + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression.")); + return false; + } + expression_variable::sptr v = expression_variable::make(val, P.var_type_getter, P.var_value_getter); + P.expr_stack.top()->add(v); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + } + break; + + default: + // If we get here, we assume it's a literal expression + { + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression.")); + return false; + } + expression::type_t token_type; + switch (t.id()) { // A map lookup would be more elegant, but we'd need a nicer C++ for that + case ID_LITERAL_DOUBLE: token_type = expression::TYPE_DOUBLE; break; + case ID_LITERAL_INT: token_type = expression::TYPE_INT; break; + case ID_LITERAL_HEX: token_type = expression::TYPE_INT; break; + case ID_LITERAL_STR: token_type = expression::TYPE_STRING; break; + case ID_LITERAL_VECTOR_INT: token_type = expression::TYPE_INT_VECTOR; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + P.expr_stack.top()->add(boost::make_shared<expression_literal>(val, token_type)); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + break; + } + + } // end switch + return true; + } + }; + + public: + expression::sptr create_expr_tree(const std::string &code) + { + // Create empty stack and keyword states + grammar_props P(_ftable, _var_type_getter, _var_value_getter); + int next_valid_state = grammar::VALID_EXPRESSION; + + // Create a lexer instance + ns_lexer<lex::lexertl::lexer<> > lexer_functor; + + // Tokenize the string + char const* first = code.c_str(); + char const* last = &first[code.size()]; + bool r = lex::tokenize( + first, last, // Iterators + lexer_functor, // Lexer + boost::bind(grammar(), _1, boost::ref(P), boost::ref(next_valid_state)) // Function object + ); + + // Check the parsing worked: + if (not r or P.expr_stack.size() != 1) { + std::string rest(first, last); + throw uhd::syntax_error(str( + boost::format("Parsing stopped at: %s\nError message: %s") + % rest % P.error + )); + } + + // Clear stack and return result + return P.get_result(); + } + + private: + + function_table::sptr _ftable; + expression_variable::type_getter_type _var_type_getter; + expression_variable::value_getter_type _var_value_getter; +}; + +parser::sptr parser::make( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter +) { + return sptr(new parser_impl( + ftable, + var_type_getter, + var_value_getter + )); +} + diff --git a/host/lib/rfnoc/nocscript/parser.hpp b/host/lib/rfnoc/nocscript/parser.hpp new file mode 100644 index 000000000..32fecd2c0 --- /dev/null +++ b/host/lib/rfnoc/nocscript/parser.hpp @@ -0,0 +1,50 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expression.hpp" +#include "function_table.hpp" +#include <boost/shared_ptr.hpp> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +class parser +{ + public: + typedef boost::shared_ptr<parser> sptr; + + static sptr make( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter + ); + + /*! The main parsing call: Turn a string of code into an expression tree. + * + * Evaluating the returned object will execute the code. + * + * \throws uhd::syntax_error if \p code contains syntax errors + */ + virtual expression::sptr create_expr_tree(const std::string &code) = 0; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/node_ctrl_base.cpp b/host/lib/rfnoc/node_ctrl_base.cpp new file mode 100644 index 000000000..6e19d276a --- /dev/null +++ b/host/lib/rfnoc/node_ctrl_base.cpp @@ -0,0 +1,103 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd::rfnoc; + +std::string node_ctrl_base::unique_id() const +{ + // Most instantiations will override this, so we don't need anything + // more elegant here. + return str(boost::format("%08X") % size_t(this)); +} + +void node_ctrl_base::clear() +{ + UHD_RFNOC_BLOCK_TRACE() << "node_ctrl_base::clear() " << std::endl; + // Reset connections: + _upstream_nodes.clear(); + _downstream_nodes.clear(); +} + +void node_ctrl_base::_register_downstream_node( + node_ctrl_base::sptr, + size_t +) { + throw uhd::runtime_error("Attempting to register a downstream block on a non-source node."); +} + +void node_ctrl_base::_register_upstream_node( + node_ctrl_base::sptr, + size_t +) { + throw uhd::runtime_error("Attempting to register an upstream block on a non-sink node."); +} + +void node_ctrl_base::set_downstream_port( + const size_t this_port, + const size_t remote_port +) { + if (not _downstream_nodes.count(this_port) and remote_port != ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot set remote downstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + _downstream_ports[this_port] = remote_port; +} + +size_t node_ctrl_base::get_downstream_port(const size_t this_port) +{ + if (not _downstream_ports.count(this_port) + or not _downstream_nodes.count(this_port) + or _downstream_ports[this_port] == ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot retrieve remote downstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + return _downstream_ports[this_port]; +} + +void node_ctrl_base::set_upstream_port( + const size_t this_port, + const size_t remote_port +) { + if (not _upstream_nodes.count(this_port) and remote_port != ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot set remote upstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + _upstream_ports[this_port] = remote_port; +} + +size_t node_ctrl_base::get_upstream_port(const size_t this_port) +{ + if (not _upstream_ports.count(this_port) + or not _upstream_nodes.count(this_port) + or _upstream_ports[this_port] == ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot retrieve remote upstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + return _upstream_ports[this_port]; +} + diff --git a/host/lib/rfnoc/radio_ctrl_impl.cpp b/host/lib/rfnoc/radio_ctrl_impl.cpp new file mode 100644 index 000000000..1cc7a2472 --- /dev/null +++ b/host/lib/rfnoc/radio_ctrl_impl.cpp @@ -0,0 +1,368 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "wb_iface_adapter.hpp" +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <uhd/convert.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/direction.hpp> +#include "radio_ctrl_impl.hpp" +#include "../../transport/super_recv_packet_handler.hpp" + +using namespace uhd; +using namespace uhd::rfnoc; + +static const size_t BYTES_PER_SAMPLE = 4; + +/**************************************************************************** + * Structors and init + ***************************************************************************/ +// Note: block_ctrl_base must be called before this, but has to be called by +// the derived class because of virtual inheritance +radio_ctrl_impl::radio_ctrl_impl() : + _tick_rate(rfnoc::rate_node_ctrl::RATE_UNDEFINED) +{ + _num_rx_channels = get_output_ports().size(); + _num_tx_channels = get_input_ports().size(); + _continuous_streaming = std::vector<bool>(2, false); + + for (size_t i = 0; i < _num_rx_channels; i++) { + _rx_streamer_active[i] = false; + } + for (size_t i = 0; i < _num_tx_channels; i++) { + _tx_streamer_active[i] = false; + } + + ///////////////////////////////////////////////////////////////////////// + // Setup peripherals + ///////////////////////////////////////////////////////////////////////// + for (size_t i = 0; i < _get_num_radios(); i++) { + _register_loopback_self_test(i); + _perifs[i].ctrl = boost::make_shared<wb_iface_adapter>( + // poke32 functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const boost::uint32_t, const boost::uint32_t, const size_t) >(&block_ctrl_base::sr_write), + this, _1, _2, i + ), + // peek32 functor + boost::bind( + static_cast< boost::uint32_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read32), + this, + _1, i + ), + // peek64 functor + boost::bind( + static_cast< boost::uint64_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read64), + this, + _1, i + ), + // get_time functor + boost::bind( + static_cast< time_spec_t (block_ctrl_base::*)(const size_t) >(&block_ctrl_base::get_command_time), + this, i + ), + // set_time functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const time_spec_t&, const size_t) >(&block_ctrl_base::set_command_time), + this, + _1, i + ) + ); + + // FIXME there's currently no way to set the underflow policy + + if (i == 0) { + time_core_3000::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_now = regs::RB_TIME_NOW; + time64_rb_bases.rb_pps = regs::RB_TIME_PPS; + _time64 = time_core_3000::make(_perifs[i].ctrl, regs::sr_addr(regs::TIME), time64_rb_bases); + this->set_time_now(0.0); + } + + //Reset the RX control engine + sr_write(regs::RX_CTRL_HALT, 1, i); + } + + //////////////////////////////////////////////////////////////////// + // Register the time keeper + //////////////////////////////////////////////////////////////////// + if (not _tree->exists(fs_path("time") / "now")) { + _tree->create<time_spec_t>(fs_path("time") / "now") + .set_publisher(boost::bind(&radio_ctrl_impl::get_time_now, this)) + ; + } + if (not _tree->exists(fs_path("time") / "pps")) { + _tree->create<time_spec_t>(fs_path("time") / "pps") + .set_publisher(boost::bind(&radio_ctrl_impl::get_time_last_pps, this)) + ; + } + if (not _tree->exists(fs_path("time") / "cmd")) { + _tree->create<time_spec_t>(fs_path("time") / "cmd"); + } + _tree->access<time_spec_t>(fs_path("time") / "now") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::set_time_now, this, _1)) + ; + _tree->access<time_spec_t>(fs_path("time") / "pps") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::set_time_next_pps, this, _1)) + ; + for (size_t i = 0; i < _get_num_radios(); i++) { + _tree->access<time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, boost::ref(_tick_rate), i)) + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, i)) + ; + } + // spp gets created in the XML file + _tree->access<int>(get_arg_path("spp") / "value") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::_update_spp, this, _1)) + .update() + ; +} + +void radio_ctrl_impl::_register_loopback_self_test(size_t chan) +{ + UHD_MSG(status) << "[RFNoC Radio] Performing register loopback test... " << std::flush; + size_t hash = size_t(time(NULL)); + for (size_t i = 0; i < 100; i++) + { + boost::hash_combine(hash, i); + sr_write(regs::TEST, boost::uint32_t(hash), chan); + boost::uint32_t result = user_reg_read32(regs::RB_TEST, chan); + if (result != boost::uint32_t(hash)) { + UHD_MSG(status) << "fail" << std::endl; + UHD_MSG(status) << boost::format("expected: %x result: %x") % boost::uint32_t(hash) % result << std::endl; + return; // exit on any failure + } + } + UHD_MSG(status) << "pass" << std::endl; +} + +/**************************************************************************** + * API calls + ***************************************************************************/ +double radio_ctrl_impl::set_rate(double rate) +{ + boost::mutex::scoped_lock lock(_mutex); + _tick_rate = rate; + _time64->set_tick_rate(_tick_rate); + _time64->self_test(); + return _tick_rate; +} + +void radio_ctrl_impl::set_tx_antenna(const std::string &ant, const size_t chan) +{ + _tx_antenna[chan] = ant; +} + +void radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t chan) +{ + _rx_antenna[chan] = ant; +} + +double radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan) +{ + return _tx_freq[chan] = freq; +} + +double radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan) +{ + return _rx_freq[chan] = freq; +} + +double radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) +{ + return _tx_gain[chan] = gain; +} + +double radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) +{ + return _rx_gain[chan] = gain; +} + +void radio_ctrl_impl::set_time_sync(const uhd::time_spec_t &time) +{ + _time64->set_time_sync(time); +} + +double radio_ctrl_impl::get_rate() const +{ + return _tick_rate; +} + +std::string radio_ctrl_impl::get_tx_antenna(const size_t chan) /* const */ +{ + return _tx_antenna[chan]; +} + +std::string radio_ctrl_impl::get_rx_antenna(const size_t chan) /* const */ +{ + return _rx_antenna[chan]; +} + +double radio_ctrl_impl::get_tx_frequency(const size_t chan) /* const */ +{ + return _tx_freq[chan]; +} + +double radio_ctrl_impl::get_rx_frequency(const size_t chan) /* const */ +{ + return _rx_freq[chan]; +} + +double radio_ctrl_impl::get_tx_gain(const size_t chan) /* const */ +{ + return _tx_gain[chan]; +} + +double radio_ctrl_impl::get_rx_gain(const size_t chan) /* const */ +{ + return _rx_gain[chan]; +} + +/*********************************************************************** + * RX Streamer-related methods (from source_block_ctrl_base) + **********************************************************************/ +//! Pass stream commands to the radio +void radio_ctrl_impl::issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t chan) +{ + boost::mutex::scoped_lock lock(_mutex); + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::issue_stream_cmd() " << chan << " " << char(stream_cmd.stream_mode) << std::endl; + if (not _is_streamer_active(uhd::RX_DIRECTION, chan)) { + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::issue_stream_cmd() called on inactive channel. Skipping." << std::endl; + return; + } + UHD_ASSERT_THROW(stream_cmd.num_samps <= 0x0fffffff); + _continuous_streaming[chan] = (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + + //setup the mode to instruction flags + typedef boost::tuple<bool, bool, bool, bool> inst_t; + static const uhd::dict<stream_cmd_t::stream_mode_t, inst_t> mode_to_inst = boost::assign::map_list_of + //reload, chain, samps, stop + (stream_cmd_t::STREAM_MODE_START_CONTINUOUS, inst_t(true, true, false, false)) + (stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, inst_t(false, false, false, true)) + (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE, inst_t(false, false, true, false)) + (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE, inst_t(false, true, true, false)) + ; + + //setup the instruction flag values + bool inst_reload, inst_chain, inst_samps, inst_stop; + boost::tie(inst_reload, inst_chain, inst_samps, inst_stop) = mode_to_inst[stream_cmd.stream_mode]; + + //calculate the word from flags and length + boost::uint32_t cmd_word = 0; + cmd_word |= boost::uint32_t((stream_cmd.stream_now)? 1 : 0) << 31; + cmd_word |= boost::uint32_t((inst_chain)? 1 : 0) << 30; + cmd_word |= boost::uint32_t((inst_reload)? 1 : 0) << 29; + cmd_word |= boost::uint32_t((inst_stop)? 1 : 0) << 28; + cmd_word |= (inst_samps)? stream_cmd.num_samps : ((inst_stop)? 0 : 1); + + //issue the stream command + const boost::uint64_t ticks = (stream_cmd.stream_now)? 0 : stream_cmd.time_spec.to_ticks(get_rate()); + sr_write(regs::RX_CTRL_CMD, cmd_word, chan); + sr_write(regs::RX_CTRL_TIME_HI, boost::uint32_t(ticks >> 32), chan); + sr_write(regs::RX_CTRL_TIME_LO, boost::uint32_t(ticks >> 0), chan); //latches the command +} + +std::vector<size_t> radio_ctrl_impl::get_active_rx_ports() +{ + std::vector<size_t> active_rx_ports; + typedef std::map<size_t, bool> map_t; + BOOST_FOREACH(map_t::value_type &m, _rx_streamer_active) { + if (m.second) { + active_rx_ports.push_back(m.first); + } + } + return active_rx_ports; +} + +/*********************************************************************** + * Radio controls (radio_ctrl specific) + **********************************************************************/ +void radio_ctrl_impl::set_rx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::set_rx_streamer() " << port << " -> " << active << std::endl; + if (port > _num_rx_channels) { + throw uhd::value_error(str( + boost::format("[%s] Can't (un)register RX streamer on port %d (invalid port)") + % unique_id() % port + )); + } + _rx_streamer_active[port] = active; + if (not check_radio_config()) { + throw std::runtime_error(str( + boost::format("[%s]: Invalid radio configuration.") + % unique_id() + )); + } +} + +void radio_ctrl_impl::set_tx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::set_tx_streamer() " << port << " -> " << active << std::endl; + if (port > _num_tx_channels) { + throw uhd::value_error(str( + boost::format("[%s] Can't (un)register TX streamer on port %d (invalid port)") + % unique_id() % port + )); + } + _tx_streamer_active[port] = active; + if (not check_radio_config()) { + throw std::runtime_error(str( + boost::format("[%s]: Invalid radio configuration.") + % unique_id() + )); + } +} + +// Subscribers to block args: +// TODO move to nocscript +void radio_ctrl_impl::_update_spp(int spp) +{ + boost::mutex::scoped_lock lock(_mutex); + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::_update_spp(): Requested spp: " << spp << std::endl; + if (spp == 0) { + spp = DEFAULT_PACKET_SIZE / BYTES_PER_SAMPLE; + } + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::_update_spp(): Setting spp to: " << spp << std::endl; + for (size_t i = 0; i < _num_rx_channels; i++) { + sr_write(regs::RX_CTRL_MAXLEN, uint32_t(spp), i); + } +} + +void radio_ctrl_impl::set_time_now(const time_spec_t &time_spec) +{ + _time64->set_time_now(time_spec); +} + +void radio_ctrl_impl::set_time_next_pps(const time_spec_t &time_spec) +{ + _time64->set_time_next_pps(time_spec); +} + + +time_spec_t radio_ctrl_impl::get_time_now() +{ + return _time64->get_time_now(); +} + +time_spec_t radio_ctrl_impl::get_time_last_pps() +{ + return _time64->get_time_last_pps(); +} + diff --git a/host/lib/rfnoc/radio_ctrl_impl.hpp b/host/lib/rfnoc/radio_ctrl_impl.hpp new file mode 100644 index 000000000..4224ec5c4 --- /dev/null +++ b/host/lib/rfnoc/radio_ctrl_impl.hpp @@ -0,0 +1,209 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP + +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "gpio_atr_3000.hpp" +#include <uhd/rfnoc/radio_ctrl.hpp> +#include <uhd/types/direction.hpp> +#include <boost/thread.hpp> + +//! Shorthand for radio block constructor +#define UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(CLASS_NAME) \ + CLASS_NAME##_impl(const make_args_t &make_args); + +#define UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(CLASS_NAME) \ + CLASS_NAME##_impl::CLASS_NAME##_impl( \ + const make_args_t &make_args \ + ) : block_ctrl_base(make_args), radio_ctrl_impl() + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to a radio. + * + */ +class radio_ctrl_impl : public radio_ctrl +{ +public: + /************************************************************************ + * Structors + ***********************************************************************/ + radio_ctrl_impl(); + virtual ~radio_ctrl_impl() {}; + + /************************************************************************ + * Public Radio API calls + ***********************************************************************/ + virtual double set_rate(double rate); + virtual void set_tx_antenna(const std::string &ant, const size_t chan); + virtual void set_rx_antenna(const std::string &ant, const size_t chan); + virtual double set_tx_frequency(const double freq, const size_t chan); + virtual double set_rx_frequency(const double freq, const size_t chan); + virtual double set_tx_gain(const double gain, const size_t chan); + virtual double set_rx_gain(const double gain, const size_t chan); + virtual void set_time_sync(const uhd::time_spec_t &time); + + virtual double get_rate() const; + virtual std::string get_tx_antenna(const size_t chan) /* const */; + virtual std::string get_rx_antenna(const size_t chan) /* const */; + virtual double get_tx_frequency(const size_t) /* const */; + virtual double get_rx_frequency(const size_t) /* const */; + virtual double get_tx_gain(const size_t) /* const */; + virtual double get_rx_gain(const size_t) /* const */; + + void set_time_now(const time_spec_t &time_spec); + void set_time_next_pps(const time_spec_t &time_spec); + time_spec_t get_time_now(); + time_spec_t get_time_last_pps(); + + /*********************************************************************** + * Block control API calls + **********************************************************************/ + void set_rx_streamer(bool active, const size_t port); + void set_tx_streamer(bool active, const size_t port); + + void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t port); + + virtual double get_input_samp_rate(size_t /* port */) { return get_rate(); } + virtual double get_output_samp_rate(size_t /* port */) { return get_rate(); } + double _get_tick_rate() { return get_rate(); } + + std::vector<size_t> get_active_rx_ports(); + bool in_continuous_streaming_mode(const size_t chan) { return _continuous_streaming.at(chan); } + void rx_ctrl_clear_cmds(const size_t port) { sr_write(regs::RX_CTRL_CLEAR_CMDS, 0, port); } + +protected: // TODO see what's protected and what's private + void _register_loopback_self_test(size_t chan); + + /*********************************************************************** + * Registers + **********************************************************************/ + struct regs { + static inline boost::uint32_t sr_addr(const boost::uint32_t offset) + { + return offset * 4; + } + + static const uint32_t BASE = 128; + + // defined in radio_core_regs.vh + static const uint32_t TIME = 128; // time hi - 128, time lo - 129, ctrl - 130 + static const uint32_t CLEAR_CMDS = 131; // Any write to this reg clears the command FIFO + static const uint32_t LOOPBACK = 132; + static const uint32_t TEST = 133; + static const uint32_t CODEC_IDLE = 134; + static const uint32_t TX_CTRL_ERROR_POLICY = 144; + static const uint32_t RX_CTRL_CMD = 152; + static const uint32_t RX_CTRL_TIME_HI = 153; + static const uint32_t RX_CTRL_TIME_LO = 154; + static const uint32_t RX_CTRL_HALT = 155; + static const uint32_t RX_CTRL_MAXLEN = 156; + static const uint32_t RX_CTRL_CLEAR_CMDS = 157; + static const uint32_t MISC_OUTS = 160; + static const uint32_t DACSYNC = 161; + static const uint32_t SPI = 168; + static const uint32_t LEDS = 176; + static const uint32_t FP_GPIO = 184; + static const uint32_t GPIO = 192; + // NOTE: Upper 32 registers (224-255) are reserved for the output settings bus for use with + // device specific front end control + + // frontend control: needs rethinking TODO + //static const uint32_t TX_FRONT = BASE + 96; + //static const uint32_t RX_FRONT = BASE + 112; + //static const uint32_t READBACK = BASE + 127; + + static const uint32_t RB_TIME_NOW = 0; + static const uint32_t RB_TIME_PPS = 1; + static const uint32_t RB_TEST = 2; + static const uint32_t RB_CODEC_READBACK = 3; + static const uint32_t RB_RADIO_NUM = 4; + static const uint32_t RB_MISC_IO = 16; + static const uint32_t RB_SPI = 17; + static const uint32_t RB_LEDS = 18; + static const uint32_t RB_DB_GPIO = 19; + static const uint32_t RB_FP_GPIO = 20; + }; + + /*********************************************************************** + * Block control API calls + **********************************************************************/ + void _update_spp(int spp); + + inline size_t _get_num_radios() const { + return std::max(_num_rx_channels, _num_tx_channels); + } + + inline timed_wb_iface::sptr _get_ctrl(size_t radio_num) const { + return _perifs.at(radio_num).ctrl; + } + + inline bool _is_streamer_active(uhd::direction_t dir, const size_t chan) const { + switch (dir) { + case uhd::TX_DIRECTION: + return _tx_streamer_active.at(chan); + case uhd::RX_DIRECTION: + return _rx_streamer_active.at(chan); + case uhd::DX_DIRECTION: + return _rx_streamer_active.at(chan) and _tx_streamer_active.at(chan); + default: + return false; + } + } + + virtual bool check_radio_config() { return true; }; + + //! There is always only one time core per radio + time_core_3000::sptr _time64; + + boost::mutex _mutex; + +private: + /************************************************************************ + * Peripherals + ***********************************************************************/ + //! Stores pointers to all streaming-related radio cores + struct radio_perifs_t + { + timed_wb_iface::sptr ctrl; + }; + std::map<size_t, radio_perifs_t> _perifs; + + size_t _num_tx_channels; + size_t _num_rx_channels; + + // Cached values + double _tick_rate; + std::map<size_t, std::string> _tx_antenna; + std::map<size_t, std::string> _rx_antenna; + std::map<size_t, double> _tx_freq; + std::map<size_t, double> _rx_freq; + std::map<size_t, double> _tx_gain; + std::map<size_t, double> _rx_gain; + + std::vector<bool> _continuous_streaming; +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/rate_node_ctrl.cpp b/host/lib/rfnoc/rate_node_ctrl.cpp new file mode 100644 index 000000000..5e3117e23 --- /dev/null +++ b/host/lib/rfnoc/rate_node_ctrl.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <boost/bind.hpp> + +using namespace uhd::rfnoc; + +const double rate_node_ctrl::RATE_UNDEFINED = -1.0; + +static double _get_input_samp_rate(rate_node_ctrl::sptr node, size_t port) +{ + return node->get_input_samp_rate(port); +} + +static double _get_output_samp_rate(rate_node_ctrl::sptr node, size_t port) +{ + return node->get_output_samp_rate(port); +} + + +// FIXME add recursion limiters (i.e. list of explored nodes) +double rate_node_ctrl::get_input_samp_rate( + size_t /* port */ +) { + try { + return find_downstream_unique_property<rate_node_ctrl, double>( + boost::bind(_get_input_samp_rate, _1, _2), + RATE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple sampling rates downstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} + +double rate_node_ctrl::get_output_samp_rate( + size_t /* port */ +) { + try { + return find_upstream_unique_property<rate_node_ctrl, double>( + boost::bind(_get_output_samp_rate, _1, _2), + RATE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple sampling rates upstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} diff --git a/host/lib/rfnoc/rx_stream_terminator.cpp b/host/lib/rfnoc/rx_stream_terminator.cpp new file mode 100644 index 000000000..b2a2d5a64 --- /dev/null +++ b/host/lib/rfnoc/rx_stream_terminator.cpp @@ -0,0 +1,131 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "rx_stream_terminator.hpp" +#include "radio_ctrl_impl.hpp" +#include "../transport/super_recv_packet_handler.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> + +using namespace uhd::rfnoc; + +size_t rx_stream_terminator::_count = 0; + +rx_stream_terminator::rx_stream_terminator() : + _term_index(_count), + _samp_rate(rate_node_ctrl::RATE_UNDEFINED), + _tick_rate(tick_node_ctrl::RATE_UNDEFINED) +{ + _count++; +} + +std::string rx_stream_terminator::unique_id() const +{ + return str(boost::format("RX Terminator %d") % _term_index); +} + +void rx_stream_terminator::set_tx_streamer(bool, const size_t) +{ + /* nop */ +} + +void rx_stream_terminator::set_rx_streamer(bool active, const size_t) +{ + // TODO this is identical to source_node_ctrl::set_rx_streamer() -> factor out + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::set_rx_streamer() " << active << std::endl; + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, _upstream_nodes) { + source_node_ctrl::sptr curr_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + if (curr_upstream_block_ctrl) { + curr_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(upstream_node.first) + ); + } + } +} + +void rx_stream_terminator::handle_overrun(boost::weak_ptr<uhd::rx_streamer> streamer, const size_t) +{ + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> > upstream_radio_nodes = + find_upstream_node<uhd::rfnoc::radio_ctrl_impl>(); + const size_t n_radios = upstream_radio_nodes.size(); + if (n_radios == 0) { + return; + } + + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::handle_overrun()" << std::endl; + boost::shared_ptr<uhd::transport::sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<uhd::transport::sph::recv_packet_streamer>(streamer.lock()); + if (not my_streamer) return; //If the rx_streamer has expired then overflow handling makes no sense. + + bool in_continuous_streaming_mode = true; + int num_channels = 0; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + num_channels += node->get_active_rx_ports().size(); + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + in_continuous_streaming_mode = in_continuous_streaming_mode && node->in_continuous_streaming_mode(port); + } + } + if (num_channels == 0) { + return; + } + + if (num_channels == 1 and in_continuous_streaming_mode) { + std::vector<size_t> active_rx_ports = upstream_radio_nodes[0]->get_active_rx_ports(); + if (active_rx_ports.empty()) { + return; + } + const size_t port = active_rx_ports[0]; + upstream_radio_nodes[0]->issue_stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS, port); + return; + } + + ///////////////////////////////////////////////////////////// + // MIMO overflow recovery time + ///////////////////////////////////////////////////////////// + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + // check all the ports on all the radios + node->rx_ctrl_clear_cmds(port); + node->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, port); + } + } + //flush transports + my_streamer->flush_all(0.001); // TODO flushing will probably have to go away. + //restart streaming on all channels + if (in_continuous_streaming_mode) { + stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + stream_cmd.stream_now = false; + stream_cmd.time_spec = upstream_radio_nodes[0]->get_time_now() + time_spec_t(0.05); + + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + node->issue_stream_cmd(stream_cmd, port); + } + } + } +} + +rx_stream_terminator::~rx_stream_terminator() +{ + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::~rx_stream_terminator() " << std::endl; + set_rx_streamer(false, 0); +} + diff --git a/host/lib/rfnoc/rx_stream_terminator.hpp b/host/lib/rfnoc/rx_stream_terminator.hpp new file mode 100644 index 000000000..5159cd34a --- /dev/null +++ b/host/lib/rfnoc/rx_stream_terminator.hpp @@ -0,0 +1,86 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP +#define INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP + +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <uhd/rfnoc/terminator_node_ctrl.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> // For the block macros + +namespace uhd { + namespace rfnoc { + +/*! \brief Terminator node for Rx streamers. + * + * This node is only used by rx_streamers. It terminates the flow graph + * inside the streamer and does not have a counterpart on the FPGA. + */ +class rx_stream_terminator : + public sink_node_ctrl, + public rate_node_ctrl, + public tick_node_ctrl, + public scalar_node_ctrl, + public terminator_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(rx_stream_terminator) + + static sptr make() + { + return sptr(new rx_stream_terminator); + } + + // If this is called, then by a send terminator at the other end + // of a flow graph. + double get_input_samp_rate(size_t) { return _samp_rate; }; + + // Same for the scaling factor + double get_input_scale_factor(size_t) { return scalar_node_ctrl::SCALE_UNDEFINED; }; + + std::string unique_id() const; + + void set_rx_streamer(bool active, const size_t port); + + void set_tx_streamer(bool active, const size_t port); + + virtual ~rx_stream_terminator(); + + void handle_overrun(boost::weak_ptr<uhd::rx_streamer>, const size_t); + +protected: + rx_stream_terminator(); + + virtual double _get_tick_rate() { return _tick_rate; }; + +private: + //! Every terminator has a unique index + const size_t _term_index; + static size_t _count; + + double _samp_rate; + double _tick_rate; + +}; /* class rx_stream_terminator */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/scalar_node_ctrl.cpp b/host/lib/rfnoc/scalar_node_ctrl.cpp new file mode 100644 index 000000000..56cc4dcf2 --- /dev/null +++ b/host/lib/rfnoc/scalar_node_ctrl.cpp @@ -0,0 +1,66 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <boost/bind.hpp> + +using namespace uhd::rfnoc; + +const double scalar_node_ctrl::SCALE_UNDEFINED = -1.0; + +static double _get_input_factor(scalar_node_ctrl::sptr node, size_t port) +{ + return node->get_input_scale_factor(port); +} + +static double _get_output_factor(scalar_node_ctrl::sptr node, size_t port) +{ + return node->get_output_scale_factor(port); +} + +// FIXME add recursion limiters (i.e. list of explored nodes) +double scalar_node_ctrl::get_input_scale_factor( + size_t /* port */ +) { + try { + return find_downstream_unique_property<scalar_node_ctrl, double>( + boost::bind(_get_input_factor, _1, _2), + SCALE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple scaling factors rates downstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} + +double scalar_node_ctrl::get_output_scale_factor( + size_t /* port */ +) { + try { + return find_upstream_unique_property<scalar_node_ctrl, double>( + boost::bind(_get_output_factor, _1, _2), + SCALE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple scaling factors rates upstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} diff --git a/host/lib/rfnoc/sink_block_ctrl_base.cpp b/host/lib/rfnoc/sink_block_ctrl_base.cpp new file mode 100644 index 000000000..56755a269 --- /dev/null +++ b/host/lib/rfnoc/sink_block_ctrl_base.cpp @@ -0,0 +1,111 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "utils.hpp" +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + + +/*********************************************************************** + * Stream signatures + **********************************************************************/ +stream_sig_t sink_block_ctrl_base::get_input_signature(size_t block_port) const +{ + if (not _tree->exists(_root_path / "ports" / "in" / block_port)) { + throw uhd::runtime_error(str( + boost::format("Invalid port number %d for block %s") + % block_port % unique_id() + )); + } + + return _resolve_port_def( + _tree->access<blockdef::port_t>(_root_path / "ports" / "in" / block_port).get() + ); +} + +std::vector<size_t> sink_block_ctrl_base::get_input_ports() const +{ + std::vector<size_t> input_ports; + input_ports.reserve(_tree->list(_root_path / "ports" / "in").size()); + BOOST_FOREACH(const std::string port, _tree->list(_root_path / "ports" / "in")) { + input_ports.push_back(boost::lexical_cast<size_t>(port)); + } + return input_ports; +} + +/*********************************************************************** + * FPGA Configuration + **********************************************************************/ +size_t sink_block_ctrl_base::get_fifo_size(size_t block_port) const { + if (_tree->exists(_root_path / "input_buffer_size" / str(boost::format("%d") % block_port))) { + return _tree->access<size_t>(_root_path / "input_buffer_size" / str(boost::format("%d") % block_port)).get(); + } + return 0; +} + +void sink_block_ctrl_base::configure_flow_control_in( + size_t cycles, + size_t packets, + size_t block_port +) { + UHD_RFNOC_BLOCK_TRACE() << boost::format("sink_block_ctrl_base::configure_flow_control_in(cycles=%d, packets=%d)") % cycles % packets << std::endl; + boost::uint32_t cycles_word = 0; + if (cycles) { + cycles_word = (1<<31) | cycles; + } + sr_write(SR_FLOW_CTRL_CYCS_PER_ACK, cycles_word, block_port); + + boost::uint32_t packets_word = 0; + if (packets) { + packets_word = (1<<31) | packets; + } + sr_write(SR_FLOW_CTRL_PKTS_PER_ACK, packets_word, block_port); +} + +void sink_block_ctrl_base::set_error_policy( + const std::string &policy +) { + if (policy == "next_packet") + { + sr_write(SR_ERROR_POLICY, (1 << 1) | 1); + } + else if (policy == "next_burst") + { + sr_write(SR_ERROR_POLICY, (1 << 2) | 1); + } + else if (policy == "wait") + { + sr_write(SR_ERROR_POLICY, 1); + } + else throw uhd::value_error("Block input cannot handle requested error policy: " + policy); +} + +/*********************************************************************** + * Hooks + **********************************************************************/ +size_t sink_block_ctrl_base::_request_input_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + const std::set<size_t> valid_input_ports = utils::str_list_to_set<size_t>(_tree->list(_root_path / "ports" / "in")); + return utils::node_map_find_first_free(_upstream_nodes, suggested_port, valid_input_ports); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/sink_node_ctrl.cpp b/host/lib/rfnoc/sink_node_ctrl.cpp new file mode 100644 index 000000000..8398641fd --- /dev/null +++ b/host/lib/rfnoc/sink_node_ctrl.cpp @@ -0,0 +1,92 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "utils.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t sink_node_ctrl::connect_upstream( + node_ctrl_base::sptr upstream_node, + size_t port, + const uhd::device_addr_t &args +) { + boost::mutex::scoped_lock lock(_input_mutex); + port = _request_input_port(port, args); + _register_upstream_node(upstream_node, port); + return port; +} + +void sink_node_ctrl::set_tx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "sink_node_ctrl::set_tx_streamer() " << active << " " << port << std::endl; + + /* Enable all downstream connections: + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t downstream_node, list_downstream_nodes()) { + sptr curr_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node.second.lock()); + if (curr_downstream_block_ctrl) { + curr_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(downstream_node.first) + ); + } + } + */ + + // Only enable 1:1 + if (list_downstream_nodes().count(port)) { + sink_node_ctrl::sptr this_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(list_downstream_nodes().at(port).lock()); + if (this_downstream_block_ctrl) { + this_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(port) + ); + } + } + + _tx_streamer_active[port] = active; +} + +size_t sink_node_ctrl::_request_input_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + return utils::node_map_find_first_free(_upstream_nodes, suggested_port); +} + +void sink_node_ctrl::_register_upstream_node( + node_ctrl_base::sptr upstream_node, + size_t port +) { + // Do all the checks: + if (port == ANY_PORT) { + throw uhd::type_error("Invalid input port number."); + } + if (_upstream_nodes.count(port) and not _upstream_nodes[port].expired()) { + throw uhd::runtime_error(str(boost::format("On node %s, input port %d is already connected.") % unique_id() % port)); + } + if (not boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node)) { + throw uhd::type_error("Attempting to register a non-source block as upstream."); + } + // Alles klar, Herr Kommissar :) + + _upstream_nodes[port] = boost::weak_ptr<node_ctrl_base>(upstream_node); +} diff --git a/host/lib/rfnoc/source_block_ctrl_base.cpp b/host/lib/rfnoc/source_block_ctrl_base.cpp new file mode 100644 index 000000000..72845ad64 --- /dev/null +++ b/host/lib/rfnoc/source_block_ctrl_base.cpp @@ -0,0 +1,137 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "utils.hpp" +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/constants.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +/*********************************************************************** + * Streaming operations + **********************************************************************/ +void source_block_ctrl_base::issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd, + const size_t chan +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::issue_stream_cmd()" << std::endl; + if (_upstream_nodes.empty()) { + UHD_MSG(warning) << "issue_stream_cmd() not implemented for " << get_block_id() << std::endl; + return; + } + + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, _upstream_nodes) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + this_upstream_block_ctrl->issue_stream_cmd(stream_cmd, chan); + } +} + +/*********************************************************************** + * Stream signatures + **********************************************************************/ +stream_sig_t source_block_ctrl_base::get_output_signature(size_t block_port) const +{ + if (not _tree->exists(_root_path / "ports" / "out" / block_port)) { + throw uhd::runtime_error(str( + boost::format("Invalid port number %d for block %s") + % block_port % unique_id() + )); + } + + return _resolve_port_def( + _tree->access<blockdef::port_t>(_root_path / "ports" / "out" / block_port).get() + ); +} + +std::vector<size_t> source_block_ctrl_base::get_output_ports() const +{ + std::vector<size_t> output_ports; + output_ports.reserve(_tree->list(_root_path / "ports" / "out").size()); + BOOST_FOREACH(const std::string port, _tree->list(_root_path / "ports" / "out")) { + output_ports.push_back(boost::lexical_cast<size_t>(port)); + } + return output_ports; +} + +/*********************************************************************** + * FPGA Configuration + **********************************************************************/ +void source_block_ctrl_base::set_destination( + boost::uint32_t next_address, + size_t output_block_port +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::set_destination() " << uhd::sid_t(next_address) << std::endl; + sid_t new_sid(next_address); + new_sid.set_src(get_address(output_block_port)); + UHD_RFNOC_BLOCK_TRACE() << " Setting SID: " << new_sid << std::endl << " "; + sr_write(SR_NEXT_DST_SID, (1<<16) | next_address, output_block_port); +} + +void source_block_ctrl_base::configure_flow_control_out( + size_t buf_size_pkts, + size_t block_port, + UHD_UNUSED(const uhd::sid_t &sid) +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::configure_flow_control_out() buf_size_pkts==" << buf_size_pkts << std::endl; + if (buf_size_pkts < 2) { + throw uhd::runtime_error(str( + boost::format("Invalid window size %d for block %s. Window size must at least be 2.") + % buf_size_pkts % unique_id() + )); + } + + //Disable the window and let all upstream data flush out + //We need to do this every time the window is changed because + //a) We don't know what state the flow-control module was left in + // in the previous run (it should still be enabled) + //b) Changing the window size where data is buffered upstream may + // result in stale packets entering the stream. + sr_write(SR_FLOW_CTRL_WINDOW_EN, 0, block_port); + + //Wait for data to flush out. + //In the FPGA we are guaranteed that all buffered packets are more-or-less consecutive. + //1ms@200MHz = 200,000 cycles of "flush time". + //200k cycles = 200k * 8 bytes (64 bits) = 1.6MB of data that can be flushed. + //Typically in the FPGA we have buffering in the order of kilobytes so waiting for 1MB + //to flush is more than enough time. + //TODO: Enhancement. We should get feedback from the FPGA about when the source_flow_control + // module is done flushing. + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + + //Resize the FC window. + //Precondition: No data can be buffered upstream. + sr_write(SR_FLOW_CTRL_WINDOW_SIZE, buf_size_pkts, block_port); + + //Enable the FC window. + //Precondition: The window size must be set. + sr_write(SR_FLOW_CTRL_WINDOW_EN, (buf_size_pkts != 0), block_port); +} + +/*********************************************************************** + * Hooks + **********************************************************************/ +size_t source_block_ctrl_base::_request_output_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + const std::set<size_t> valid_output_ports = utils::str_list_to_set<size_t>(_tree->list(_root_path / "ports" / "out")); + return utils::node_map_find_first_free(_downstream_nodes, suggested_port, valid_output_ports); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/source_node_ctrl.cpp b/host/lib/rfnoc/source_node_ctrl.cpp new file mode 100644 index 000000000..c97c72354 --- /dev/null +++ b/host/lib/rfnoc/source_node_ctrl.cpp @@ -0,0 +1,96 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "utils.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t source_node_ctrl::connect_downstream( + node_ctrl_base::sptr downstream_node, + size_t port, + const uhd::device_addr_t &args +) { + boost::mutex::scoped_lock lock(_output_mutex); + port = _request_output_port(port, args); + _register_downstream_node(downstream_node, port); + return port; +} + +void source_node_ctrl::set_rx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "source_node_ctrl::set_rx_streamer() " << port << " -> " << active << std::endl; + + /* This will enable all upstream blocks: + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, list_upstream_nodes()) { + sptr curr_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + if (curr_upstream_block_ctrl) { + curr_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(upstream_node.first) + ); + } + } + */ + + // This only enables 1:1 (if output 1 is enabled, enable what's connected to input 1) + if (list_upstream_nodes().count(port)) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(list_upstream_nodes().at(port).lock()); + if (this_upstream_block_ctrl) { + this_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(port) + ); + } + } + + _rx_streamer_active[port] = active; +} + +size_t source_node_ctrl::_request_output_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + return utils::node_map_find_first_free(_downstream_nodes, suggested_port); +} + +void source_node_ctrl::_register_downstream_node( + node_ctrl_base::sptr downstream_node, + size_t port +) { + // Do all the checks: + if (port == ANY_PORT) { + throw uhd::type_error(str( + boost::format("[%s] Invalid output port number (ANY).") + % unique_id() + )); + } + if (_downstream_nodes.count(port) and not _downstream_nodes[port].expired()) { + throw uhd::runtime_error(str(boost::format("On node %s, output port %d is already connected.") % unique_id() % port)); + } + if (not boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node)) { + throw uhd::type_error("Attempting to register a non-sink block as downstream."); + } + // Alles klar, Herr Kommissar :) + + _downstream_nodes[port] = boost::weak_ptr<node_ctrl_base>(downstream_node); +} + diff --git a/host/lib/rfnoc/stream_sig.cpp b/host/lib/rfnoc/stream_sig.cpp new file mode 100644 index 000000000..3d953bcb2 --- /dev/null +++ b/host/lib/rfnoc/stream_sig.cpp @@ -0,0 +1,85 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/stream_sig.hpp> +#include <uhd/convert.hpp> +#include <boost/format.hpp> + +using namespace uhd::rfnoc; + +stream_sig_t::stream_sig_t() : + item_type(""), + vlen(0), + packet_size(0), + is_bursty(false) +{ + // nop +} + +std::string stream_sig_t::to_string() +{ + return str( + boost::format( + "%s,vlen=%d,packet_size=%d" + ) % item_type % vlen % packet_size + ); +} + +std::string stream_sig_t::to_pp_string() +{ + return str( + boost::format( + "Data type: %s | Vector Length: %d | Packet size: %d" + ) % item_type % vlen % packet_size + ); +} + +size_t stream_sig_t::get_bytes_per_item() const +{ + if (item_type == "") { + return 0; + } + + return uhd::convert::get_bytes_per_item(item_type); +} + +bool stream_sig_t::is_compatible(const stream_sig_t &output_sig, const stream_sig_t &input_sig) +{ + /// Item types: + if (not (input_sig.item_type.empty() or output_sig.item_type.empty()) + and input_sig.item_type != output_sig.item_type) { + return false; + } + + /// Vector lengths + if (output_sig.vlen and input_sig.vlen) { + if (input_sig.vlen != output_sig.vlen) { + return false; + } + } + + /// Packet sizes + if (output_sig.packet_size and input_sig.packet_size) { + if (input_sig.packet_size != output_sig.packet_size) { + return false; + } + } + + // You may pass + return true; +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/tick_node_ctrl.cpp b/host/lib/rfnoc/tick_node_ctrl.cpp new file mode 100644 index 000000000..fa5c7b6a1 --- /dev/null +++ b/host/lib/rfnoc/tick_node_ctrl.cpp @@ -0,0 +1,75 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/tick_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +const double tick_node_ctrl::RATE_UNDEFINED = 0; + +double tick_node_ctrl::get_tick_rate( + const std::set< node_ctrl_base::sptr > &_explored_nodes +) { + // First, see if we've implemented _get_tick_rate() + { + double my_tick_rate = _get_tick_rate(); + if (my_tick_rate != RATE_UNDEFINED) { + return my_tick_rate; + } + } + + // If not, we ask all our neighbours for the tick rate. + // This will fail if we get different values. + std::set< node_ctrl_base::sptr > explored_nodes(_explored_nodes); + explored_nodes.insert(shared_from_this()); + // Here, we need all up- and downstream nodes + std::vector< sptr > neighbouring_tick_nodes = find_downstream_node<tick_node_ctrl>(); + { + std::vector< sptr > upstream_neighbouring_tick_nodes = find_upstream_node<tick_node_ctrl>(); + neighbouring_tick_nodes.insert( + neighbouring_tick_nodes.end(), + upstream_neighbouring_tick_nodes.begin(), + upstream_neighbouring_tick_nodes.end() + ); + } // neighbouring_tick_nodes is now initialized + double ret_val = RATE_UNDEFINED; + BOOST_FOREACH(const sptr &node, neighbouring_tick_nodes) { + if (_explored_nodes.count(node)) { + continue; + } + double tick_rate = node->get_tick_rate(explored_nodes); + if (tick_rate == RATE_UNDEFINED) { + continue; + } + if (ret_val == RATE_UNDEFINED) { + ret_val = tick_rate; + // TODO: Remember name of this node so we can make the throw message more descriptive. + continue; + } + if (tick_rate != ret_val) { + throw uhd::runtime_error( + str( + // TODO add node names + boost::format("Conflicting tick rates: One neighbouring block specifies %d MHz, another %d MHz.") + % tick_rate % ret_val + ) + ); + } + } + return ret_val; +} + diff --git a/host/lib/rfnoc/tx_stream_terminator.cpp b/host/lib/rfnoc/tx_stream_terminator.cpp new file mode 100644 index 000000000..2746fc4d8 --- /dev/null +++ b/host/lib/rfnoc/tx_stream_terminator.cpp @@ -0,0 +1,65 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "tx_stream_terminator.hpp" +#include <boost/format.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t tx_stream_terminator::_count = 0; + +tx_stream_terminator::tx_stream_terminator() : + _term_index(_count), + _samp_rate(rate_node_ctrl::RATE_UNDEFINED), + _tick_rate(tick_node_ctrl::RATE_UNDEFINED) +{ + _count++; +} + +std::string tx_stream_terminator::unique_id() const +{ + return str(boost::format("TX Terminator %d") % _term_index); +} + +void tx_stream_terminator::set_rx_streamer(bool, const size_t) +{ + /* nop */ +} + +void tx_stream_terminator::set_tx_streamer(bool active, const size_t /* port */) +{ + // TODO this is identical to sink_node_ctrl::set_tx_streamer() -> factor out + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::set_tx_streamer() " << active << std::endl; + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t downstream_node, _downstream_nodes) { + sink_node_ctrl::sptr curr_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node.second.lock()); + if (curr_downstream_block_ctrl) { + curr_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(downstream_node.first) + ); + } + } +} + +tx_stream_terminator::~tx_stream_terminator() +{ + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::~tx_stream_terminator() " << std::endl; + set_tx_streamer(false, 0); +} + diff --git a/host/lib/rfnoc/tx_stream_terminator.hpp b/host/lib/rfnoc/tx_stream_terminator.hpp new file mode 100644 index 000000000..169d7cd6a --- /dev/null +++ b/host/lib/rfnoc/tx_stream_terminator.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP +#define INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP + +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <uhd/rfnoc/terminator_node_ctrl.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> // For the block macros +#include <uhd/utils/msg.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Terminator node for Tx streamers. + * + * This node is only used by tx_streamers. It terminates the flow graph + * inside the streamer and does not have a counterpart on the FPGA. + */ +class tx_stream_terminator : + public source_node_ctrl, + public rate_node_ctrl, + public tick_node_ctrl, + public scalar_node_ctrl, + public terminator_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(tx_stream_terminator) + + static sptr make() + { + return sptr(new tx_stream_terminator); + } + + void issue_stream_cmd(const uhd::stream_cmd_t &, const size_t) + { + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::issue_stream_cmd()" << std::endl; + } + + // If this is called, then by a send terminator at the other end + // of a flow graph. + double get_output_samp_rate(size_t) { return _samp_rate; }; + + // Same for the scaling factor + double get_output_scale_factor(size_t) { return scalar_node_ctrl::SCALE_UNDEFINED; }; + + std::string unique_id() const; + + void set_rx_streamer(bool active, const size_t port); + + void set_tx_streamer(bool active, const size_t port); + + virtual ~tx_stream_terminator(); + +protected: + tx_stream_terminator(); + + virtual double _get_tick_rate() { return _tick_rate; }; + +private: + //! Every terminator has a unique index + const size_t _term_index; + static size_t _count; + + double _samp_rate; + double _tick_rate; + +}; /* class tx_stream_terminator */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/utils.hpp b/host/lib/rfnoc/utils.hpp new file mode 100644 index 000000000..ecd3d7cfb --- /dev/null +++ b/host/lib/rfnoc/utils.hpp @@ -0,0 +1,77 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_UTILS_HPP +#define INCLUDED_LIBUHD_RFNOC_UTILS_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <boost/lexical_cast.hpp> +#include <set> + +namespace uhd { namespace rfnoc { namespace utils { + + /*! If \p suggested_port equals ANY_PORT, return the first available + * port number on \p nodes. Otherwise, return \p suggested_port. + * + * If \p allowed_ports is given, another condition is that the port + * number must be listed in here. + * If \p allowed_ports is not specified or empty, the assumption is + * that all ports are valid. + * + * On failure, ANY_PORT is returned. + */ + static size_t node_map_find_first_free( + node_ctrl_base::node_map_t nodes, + const size_t suggested_port, + const std::set<size_t> allowed_ports=std::set<size_t>() + ) { + size_t port = suggested_port; + if (port == ANY_PORT) { + if (allowed_ports.empty()) { + port = 0; + while (nodes.count(port) and (port != ANY_PORT)) { + port++; + } + } else { + BOOST_FOREACH(const size_t allowed_port, allowed_ports) { + if (not nodes.count(port)) { + return allowed_port; + } + return ANY_PORT; + } + } + } else { + if (not (allowed_ports.empty() or allowed_ports.count(port))) { + return ANY_PORT; + } + } + return port; + } + + template <typename T> + static std::set<T> str_list_to_set(const std::vector<std::string> &list) { + std::set<T> return_set; + BOOST_FOREACH(const std::string &S, list) { + return_set.insert(boost::lexical_cast<T>(S)); + } + return return_set; + } + +}}}; /* namespace uhd::rfnoc::utils */ + +#endif /* INCLUDED_LIBUHD_RFNOC_UTILS_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/wb_iface_adapter.cpp b/host/lib/rfnoc/wb_iface_adapter.cpp new file mode 100644 index 000000000..6688fe86b --- /dev/null +++ b/host/lib/rfnoc/wb_iface_adapter.cpp @@ -0,0 +1,71 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "wb_iface_adapter.hpp" + +using namespace uhd::rfnoc; + +wb_iface_adapter::wb_iface_adapter( + const poke32_type &poke32_functor_, + const peek32_type &peek32_functor_, + const peek64_type &peek64_functor_, + const gettime_type &gettime_functor_, + const settime_type &settime_functor_ +) : poke32_functor(poke32_functor_) + , peek32_functor(peek32_functor_) + , peek64_functor(peek64_functor_) + , gettime_functor(gettime_functor_) + , settime_functor(settime_functor_) +{ + // nop +} + +wb_iface_adapter::wb_iface_adapter( + const poke32_type &poke32_functor_, + const peek32_type &peek32_functor_, + const peek64_type &peek64_functor_ +) : poke32_functor(poke32_functor_) + , peek32_functor(peek32_functor_) + , peek64_functor(peek64_functor_) +{ + // nop +} + +void wb_iface_adapter::poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + poke32_functor(addr / 4, data); // FIXME remove the requirement for /4 +} + +boost::uint32_t wb_iface_adapter::peek32(const wb_addr_type addr) +{ + return peek32_functor(addr); +} + +boost::uint64_t wb_iface_adapter::peek64(const wb_addr_type addr) +{ + return peek64_functor(addr); +} + +uhd::time_spec_t wb_iface_adapter::get_time(void) +{ + return gettime_functor(); +} + +void wb_iface_adapter::set_time(const uhd::time_spec_t& t) +{ + settime_functor(t); +} diff --git a/host/lib/rfnoc/wb_iface_adapter.hpp b/host/lib/rfnoc/wb_iface_adapter.hpp new file mode 100644 index 000000000..04623d203 --- /dev/null +++ b/host/lib/rfnoc/wb_iface_adapter.hpp @@ -0,0 +1,70 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP +#define INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP + +#include <uhd/config.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/function.hpp> + +namespace uhd { + namespace rfnoc { + +class UHD_API wb_iface_adapter : public uhd::timed_wb_iface +{ +public: + typedef boost::shared_ptr<wb_iface_adapter> sptr; + typedef boost::function<void(wb_addr_type, boost::uint32_t)> poke32_type; + typedef boost::function<boost::uint32_t(wb_addr_type)> peek32_type; + typedef boost::function<boost::uint64_t(wb_addr_type)> peek64_type; + typedef boost::function<time_spec_t(void)> gettime_type; + typedef boost::function<void(const time_spec_t&)> settime_type; + + wb_iface_adapter( + const poke32_type &, + const peek32_type &, + const peek64_type &, + const gettime_type &, + const settime_type & + ); + + wb_iface_adapter( + const poke32_type &, + const peek32_type &, + const peek64_type & + ); + + virtual ~wb_iface_adapter(void) {}; + + virtual void poke32(const wb_addr_type addr, const boost::uint32_t data); + virtual boost::uint32_t peek32(const wb_addr_type addr); + virtual boost::uint64_t peek64(const wb_addr_type addr); + virtual time_spec_t get_time(void); + virtual void set_time(const time_spec_t& t); + +private: + const poke32_type poke32_functor; + const peek32_type peek32_functor; + const peek64_type peek64_functor; + const gettime_type gettime_functor; + const settime_type settime_functor; +}; + +}} // namespace uhd::rfnoc + +#endif /* INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP */ diff --git a/host/lib/rfnoc/xports.hpp b/host/lib/rfnoc/xports.hpp new file mode 100644 index 000000000..7872f2e1b --- /dev/null +++ b/host/lib/rfnoc/xports.hpp @@ -0,0 +1,36 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/sid.hpp> +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { + + /*! Holds all necessary items for a bidirectional link + */ + struct both_xports_t + { + uhd::transport::zero_copy_if::sptr recv; + uhd::transport::zero_copy_if::sptr send; + size_t recv_buff_size; + size_t send_buff_size; + uhd::sid_t send_sid; + uhd::sid_t recv_sid; + }; + +}; + diff --git a/host/lib/transport/CMakeLists.txt b/host/lib/transport/CMakeLists.txt index 6abc399b4..44c8d59af 100644 --- a/host/lib/transport/CMakeLists.txt +++ b/host/lib/transport/CMakeLists.txt @@ -22,17 +22,15 @@ ######################################################################## # Include subdirectories (different than add) ######################################################################## -INCLUDE_SUBDIRECTORY(nirio) +IF(ENABLE_X300) + INCLUDE_SUBDIRECTORY(nirio) +ENDIF(ENABLE_X300) ######################################################################## # Setup libusb ######################################################################## -MESSAGE(STATUS "") -FIND_PACKAGE(USB1) - -LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF OFF) - IF(ENABLE_USB) + MESSAGE(STATUS "") MESSAGE(STATUS "USB support enabled via libusb.") INCLUDE_DIRECTORIES(${LIBUSB_INCLUDE_DIRS}) LIBUHD_APPEND_LIBS(${LIBUSB_LIBRARIES}) @@ -124,14 +122,21 @@ LIBUHD_PYTHON_GEN_SOURCE( ) LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/zero_copy_recv_offload.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tcp_zero_copy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/buffer_pool.cpp ${CMAKE_CURRENT_SOURCE_DIR}/if_addrs.cpp ${CMAKE_CURRENT_SOURCE_DIR}/udp_simple.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nirio_zero_copy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/chdr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/muxed_zero_copy_if.cpp ) +IF(ENABLE_X300) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/nirio_zero_copy.cpp + ) +ENDIF(ENABLE_X300) + # Verbose Debug output for send/recv SET( UHD_TXRX_DEBUG_PRINTS OFF CACHE BOOL "Use verbose debug output for send/recv" ) OPTION( UHD_TXRX_DEBUG_PRINTS "Use verbose debug output for send/recv" "" ) diff --git a/host/lib/transport/libusb1_base.cpp b/host/lib/transport/libusb1_base.cpp index f92117a9e..7b9e11da9 100644 --- a/host/lib/transport/libusb1_base.cpp +++ b/host/lib/transport/libusb1_base.cpp @@ -47,10 +47,7 @@ public: task_handler = task::make(boost::bind(&libusb_session_impl::libusb_event_handler_task, this, _context)); } - ~libusb_session_impl(void){ - task_handler.reset(); - libusb_exit(_context); - } + virtual ~libusb_session_impl(void); libusb_context *get_context(void) const{ return _context; @@ -86,6 +83,11 @@ private: } }; +libusb_session_impl::~libusb_session_impl(void){ + task_handler.reset(); + libusb_exit(_context); +} + libusb::session::sptr libusb::session::get_global_session(void){ static boost::weak_ptr<session> global_session; @@ -121,9 +123,7 @@ public: _dev = dev; } - ~libusb_device_impl(void){ - libusb_unref_device(this->get()); - } + virtual ~libusb_device_impl(void); libusb_device *get(void) const{ return _dev; @@ -134,6 +134,10 @@ private: libusb_device *_dev; }; +libusb_device_impl::~libusb_device_impl(void){ + libusb_unref_device(this->get()); +} + /*********************************************************************** * libusb device list **********************************************************************/ @@ -160,6 +164,8 @@ public: libusb_free_device_list(dev_list, false/*dont unref*/); } + virtual ~libusb_device_list_impl(void); + size_t size(void) const{ return _devs.size(); } @@ -172,6 +178,10 @@ private: std::vector<libusb::device::sptr> _devs; }; +libusb_device_list_impl::~libusb_device_list_impl(void){ + /* NOP */ +} + libusb::device_list::sptr libusb::device_list::make(void){ return sptr(new libusb_device_list_impl()); } @@ -190,6 +200,8 @@ public: UHD_ASSERT_THROW(libusb_get_device_descriptor(_dev->get(), &_desc) == 0); } + virtual ~libusb_device_descriptor_impl(void); + const libusb_device_descriptor &get(void) const{ return _desc; } @@ -207,12 +219,12 @@ public: ); unsigned char buff[512]; - ssize_t ret = libusb_get_string_descriptor_ascii( - handle->get(), off, buff, sizeof(buff) + int ret = libusb_get_string_descriptor_ascii( + handle->get(), off, buff, int(sizeof(buff)) ); if (ret < 0) return ""; //on error, just return empty string - std::string string_descriptor((char *)buff, ret); + std::string string_descriptor((char *)buff, size_t(ret)); byte_vector_t string_vec(string_descriptor.begin(), string_descriptor.end()); std::string out; BOOST_FOREACH(boost::uint8_t byte, string_vec){ @@ -227,6 +239,10 @@ private: libusb_device_descriptor _desc; }; +libusb_device_descriptor_impl::~libusb_device_descriptor_impl(void){ + /* NOP */ +} + libusb::device_descriptor::sptr libusb::device_descriptor::make(device::sptr dev){ return sptr(new libusb_device_descriptor_impl(dev)); } @@ -245,13 +261,7 @@ public: UHD_ASSERT_THROW(libusb_open(_dev->get(), &_handle) == 0); } - ~libusb_device_handle_impl(void){ - //release all claimed interfaces - for (size_t i = 0; i < _claimed.size(); i++){ - libusb_release_interface(this->get(), _claimed[i]); - } - libusb_close(_handle); - } + virtual ~libusb_device_handle_impl(void); libusb_device_handle *get(void) const{ return _handle; @@ -283,6 +293,14 @@ private: std::vector<int> _claimed; }; +libusb_device_handle_impl::~libusb_device_handle_impl(void){ + //release all claimed interfaces + for (size_t i = 0; i < _claimed.size(); i++){ + libusb_release_interface(this->get(), _claimed[i]); + } + libusb_close(_handle); +} + libusb::device_handle::sptr libusb::device_handle::get_cached_handle(device::sptr dev){ static uhd::dict<libusb_device *, boost::weak_ptr<device_handle> > handles; @@ -327,6 +345,8 @@ public: _dev = dev; } + virtual ~libusb_special_handle_impl(void); + libusb::device::sptr get_device(void) const{ return _dev; } @@ -361,6 +381,10 @@ private: libusb::device::sptr _dev; //always keep a reference to device }; +libusb_special_handle_impl::~libusb_special_handle_impl(void){ + /* NOP */ +} + libusb::special_handle::sptr libusb::special_handle::make(device::sptr dev){ return sptr(new libusb_special_handle_impl(dev)); } @@ -368,6 +392,10 @@ libusb::special_handle::sptr libusb::special_handle::make(device::sptr dev){ /*********************************************************************** * list device handles implementations **********************************************************************/ +usb_device_handle::~usb_device_handle(void) { + /* NOP */ +} + std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list( boost::uint16_t vid, boost::uint16_t pid ){ diff --git a/host/lib/transport/libusb1_base.hpp b/host/lib/transport/libusb1_base.hpp index 2ff1291d9..6d104bc75 100644 --- a/host/lib/transport/libusb1_base.hpp +++ b/host/lib/transport/libusb1_base.hpp @@ -67,7 +67,7 @@ namespace libusb { public: typedef boost::shared_ptr<session> sptr; - virtual ~session(void) = 0; + virtual ~session(void); /*! * Level 0: no messages ever printed by the library (default) @@ -92,7 +92,7 @@ namespace libusb { public: typedef boost::shared_ptr<device> sptr; - virtual ~device(void) = 0; + virtual ~device(void); //! get the underlying device pointer virtual libusb_device *get(void) const = 0; @@ -106,7 +106,7 @@ namespace libusb { public: typedef boost::shared_ptr<device_list> sptr; - virtual ~device_list(void) = 0; + virtual ~device_list(void); //! make a new device list static sptr make(void); @@ -125,7 +125,7 @@ namespace libusb { public: typedef boost::shared_ptr<device_descriptor> sptr; - virtual ~device_descriptor(void) = 0; + virtual ~device_descriptor(void); //! make a new descriptor from a device reference static sptr make(device::sptr); @@ -143,7 +143,7 @@ namespace libusb { public: typedef boost::shared_ptr<device_handle> sptr; - virtual ~device_handle(void) = 0; + virtual ~device_handle(void); //! get a cached handle or make a new one given the device static sptr get_cached_handle(device::sptr); @@ -172,7 +172,7 @@ namespace libusb { public: typedef boost::shared_ptr<special_handle> sptr; - virtual ~special_handle(void) = 0; + virtual ~special_handle(void); //! make a new special handle from device static sptr make(device::sptr); diff --git a/host/lib/transport/libusb1_control.cpp b/host/lib/transport/libusb1_control.cpp index 00c113163..a18f657d9 100644 --- a/host/lib/transport/libusb1_control.cpp +++ b/host/lib/transport/libusb1_control.cpp @@ -30,19 +30,21 @@ usb_control::~usb_control(void){ **********************************************************************/ class libusb_control_impl : public usb_control { public: - libusb_control_impl(libusb::device_handle::sptr handle, const size_t interface): + libusb_control_impl(libusb::device_handle::sptr handle, const int interface): _handle(handle) { _handle->claim_interface(interface); } - ssize_t submit(boost::uint8_t request_type, - boost::uint8_t request, - boost::uint16_t value, - boost::uint16_t index, - unsigned char *buff, - boost::uint16_t length, - boost::int32_t libusb_timeout = 0 + virtual ~libusb_control_impl(void); + + int submit(boost::uint8_t request_type, + boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length, + boost::uint32_t libusb_timeout = 0 ){ boost::mutex::scoped_lock lock(_mutex); return libusb_control_transfer(_handle->get(), @@ -60,10 +62,14 @@ private: boost::mutex _mutex; }; +libusb_control_impl::~libusb_control_impl(void) { + /* NOP */ +} + /*********************************************************************** * USB control public make functions **********************************************************************/ -usb_control::sptr usb_control::make(usb_device_handle::sptr handle, const size_t interface){ +usb_control::sptr usb_control::make(usb_device_handle::sptr handle, const int interface){ return sptr(new libusb_control_impl(libusb::device_handle::get_cached_handle( boost::static_pointer_cast<libusb::special_handle>(handle)->get_device() ), interface)); diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp index 53e345009..c32b96b63 100644 --- a/host/lib/transport/libusb1_zero_copy.cpp +++ b/host/lib/transport/libusb1_zero_copy.cpp @@ -125,19 +125,21 @@ public: _ctx(libusb::session::get_global_session()->get_context()), _lut(lut), _frame_size(frame_size) { /* NOP */ } + virtual ~libusb_zero_copy_mb(void); + void release(void){ _release_cb(this); } UHD_INLINE void submit(void) { - _lut->length = (_is_recv)? _frame_size : size(); //always set length + _lut->length = int((_is_recv)? _frame_size : size()); //always set length #ifdef UHD_TXRX_DEBUG_PRINTS result.start_time = boost::get_system_time().time_of_day().total_microseconds(); result.buff_num = num(); result.is_recv = _is_recv; #endif - const int ret = libusb_submit_transfer(_lut); + int ret = libusb_submit_transfer(_lut); if (ret != LIBUSB_SUCCESS) throw uhd::usb_error(ret, str(boost::format( "usb %s submit failed: %s") % _name % libusb_error_name(ret))); @@ -152,7 +154,7 @@ public: throw uhd::io_error(str(boost::format("usb %s transfer status: %d") % _name % libusb_error_name(result.status))); result.completed = 0; - return make(reinterpret_cast<buffer_type *>(this), _lut->buffer, (_is_recv)? result.actual_length : _frame_size); + return make(reinterpret_cast<buffer_type *>(this), _lut->buffer, (_is_recv)? size_t(result.actual_length) : _frame_size); } return typename buffer_type::sptr(); } @@ -189,6 +191,10 @@ private: const size_t _frame_size; }; +libusb_zero_copy_mb::~libusb_zero_copy_mb(void) { + /* NOP */ +} + /*********************************************************************** * USB zero_copy device class **********************************************************************/ @@ -197,7 +203,7 @@ class libusb_zero_copy_single public: libusb_zero_copy_single( libusb::device_handle::sptr handle, - const size_t interface, const size_t endpoint, + const int interface, const unsigned char endpoint, const size_t num_frames, const size_t frame_size ): _handle(handle), @@ -221,7 +227,7 @@ public: _handle->get(), // dev_handle endpoint, // endpoint static_cast<unsigned char *>(buff), - sizeof(buff), + int(sizeof(buff)), &transfered, //bytes xfered 10 //timeout ms ); @@ -243,7 +249,7 @@ public: _handle->get(), // dev_handle endpoint, // endpoint static_cast<unsigned char *>(_buffer_pool->at(i)), // buffer - this->get_frame_size(), // length + int(this->get_frame_size()), // length libusb_transfer_cb_fn(&libusb_async_cb), // callback static_cast<void *>(&_mb_pool.back()->result), // user_data 0 // timeout (ms) @@ -372,10 +378,10 @@ struct libusb_zero_copy_impl : usb_zero_copy { libusb_zero_copy_impl( libusb::device_handle::sptr handle, - const size_t recv_interface, - const size_t recv_endpoint, - const size_t send_interface, - const size_t send_endpoint, + const int recv_interface, + const unsigned char recv_endpoint, + const int send_interface, + const unsigned char send_endpoint, const device_addr_t &hints ){ _recv_impl.reset(new libusb_zero_copy_single( @@ -388,6 +394,8 @@ struct libusb_zero_copy_impl : usb_zero_copy size_t(hints.cast<double>("send_frame_size", DEFAULT_XFER_SIZE)))); } + virtual ~libusb_zero_copy_impl(void); + managed_recv_buffer::sptr get_recv_buff(double timeout) { boost::mutex::scoped_lock l(_recv_mutex); @@ -410,15 +418,26 @@ struct libusb_zero_copy_impl : usb_zero_copy boost::mutex _recv_mutex, _send_mutex; }; +libusb_zero_copy_impl::~libusb_zero_copy_impl(void) { + /* NOP */ +} + +/*********************************************************************** + * USB zero_copy destructor + **********************************************************************/ +usb_zero_copy::~usb_zero_copy(void) { + /* NOP */ +} + /*********************************************************************** * USB zero_copy make functions **********************************************************************/ usb_zero_copy::sptr usb_zero_copy::make( usb_device_handle::sptr handle, - const size_t recv_interface, - const size_t recv_endpoint, - const size_t send_interface, - const size_t send_endpoint, + const int recv_interface, + const unsigned char recv_endpoint, + const int send_interface, + const unsigned char send_endpoint, const device_addr_t &hints ){ libusb::device_handle::sptr dev_handle(libusb::device_handle::get_cached_handle( diff --git a/host/lib/transport/muxed_zero_copy_if.cpp b/host/lib/transport/muxed_zero_copy_if.cpp new file mode 100644 index 000000000..996db3c98 --- /dev/null +++ b/host/lib/transport/muxed_zero_copy_if.cpp @@ -0,0 +1,250 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/muxed_zero_copy_if.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread.hpp> +#include <boost/thread/locks.hpp> +#include <map> + +using namespace uhd; +using namespace uhd::transport; + +class muxed_zero_copy_if_impl : public muxed_zero_copy_if, + public boost::enable_shared_from_this<muxed_zero_copy_if_impl> +{ +public: + typedef boost::shared_ptr<muxed_zero_copy_if_impl> sptr; + + muxed_zero_copy_if_impl( + zero_copy_if::sptr base_xport, + stream_classifier_fn classify_fn, + size_t max_streams + ): + _base_xport(base_xport), _classify(classify_fn), + _max_num_streams(max_streams), _num_dropped_frames(0) + { + //Create the receive thread to poll the underlying transport + //and classify packets into queues + _recv_thread = boost::thread( + boost::bind(&muxed_zero_copy_if_impl::_update_queues, this)); + } + + virtual ~muxed_zero_copy_if_impl() + { + UHD_SAFE_CALL( + //Interrupt buffer updater loop + _recv_thread.interrupt(); + //Wait for loop to finish + //No timeout on join. The recv loop is guaranteed + //to terminate in a reasonable amount of time because + //there are no timed blocks on the underlying. + _recv_thread.join(); + //Flush base transport + while (_base_xport->get_recv_buff(0.0001)) /*NOP*/; + //Release child streams + //Note that this will not delete or flush the child streams + //until the owners of the streams have released the respective + //shared pointers. This ensures that packets are not dropped. + _streams.clear(); + ); + } + + virtual zero_copy_if::sptr make_stream(const uint32_t stream_num) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (_streams.size() >= _max_num_streams) { + throw uhd::runtime_error("muxed_zero_copy_if: stream capacity exceeded. cannot create more streams."); + } + stream_impl::sptr stream = boost::make_shared<stream_impl>(this->shared_from_this(), stream_num); + _streams[stream_num] = stream; + return stream; + } + + virtual size_t get_num_dropped_frames() const + { + return _num_dropped_frames; + } + + void remove_stream(const uint32_t stream_num) + { + boost::lock_guard<boost::mutex> lock(_mutex); + _streams.erase(stream_num); + } + +private: + class stream_impl : public zero_copy_if + { + public: + typedef boost::shared_ptr<stream_impl> sptr; + typedef boost::weak_ptr<stream_impl> wptr; + + stream_impl(muxed_zero_copy_if_impl::sptr muxed_xport, const uint32_t stream_num): + _stream_num(stream_num), _muxed_xport(muxed_xport), + _buff_queue(muxed_xport->base_xport()->get_num_recv_frames()) + { + } + + ~stream_impl(void) + { + //First remove the stream from muxed transport + //so no more frames are pushed in + _muxed_xport->remove_stream(_stream_num); + //Flush the transport + managed_recv_buffer::sptr buff; + while (_buff_queue.pop_with_haste(buff)) { + //NOP + } + } + + size_t get_num_recv_frames(void) const { + return _muxed_xport->base_xport()->get_num_recv_frames(); + } + + size_t get_recv_frame_size(void) const { + return _muxed_xport->base_xport()->get_recv_frame_size(); + } + + managed_recv_buffer::sptr get_recv_buff(double timeout) { + managed_recv_buffer::sptr buff; + if (_buff_queue.pop_with_timed_wait(buff, timeout)) { + return buff; + } else { + return managed_recv_buffer::sptr(); + } + + } + + void push_recv_buff(managed_recv_buffer::sptr buff) { + _buff_queue.push_with_wait(buff); + } + + size_t get_num_send_frames(void) const { + return _muxed_xport->base_xport()->get_num_send_frames(); + } + + size_t get_send_frame_size(void) const { + return _muxed_xport->base_xport()->get_send_frame_size(); + } + + managed_send_buffer::sptr get_send_buff(double timeout) + { + return _muxed_xport->base_xport()->get_send_buff(timeout); + } + + private: + const uint32_t _stream_num; + muxed_zero_copy_if_impl::sptr _muxed_xport; + bounded_buffer<managed_recv_buffer::sptr> _buff_queue; + }; + + inline zero_copy_if::sptr& base_xport() { return _base_xport; } + + void _update_queues() + { + //Run forever: + // - Pull packets from the base transport + // - Classify them + // - Push them to the appropriate receive queue + while (true) { + { //Uninterruptable block of code + boost::this_thread::disable_interruption interrupt_disabler; + if (not _process_next_buffer()) { + //Be a good citizen and yield if no packet is processed + static const size_t MIN_DUR = 1; + boost::this_thread::sleep_for(boost::chrono::nanoseconds(MIN_DUR)); + //We call sleep(MIN_DUR) above instead of yield() to ensure that we + //relinquish the current scheduler time slot. + //yield() is a hint to the scheduler to end the time + //slice early and schedule in another thread that is ready to run. + //However in most situations, there will be no other thread and + //this thread will continue to run which will rail a CPU core. + //We call sleep(MIN_DUR=1) instead which will sleep for a minimum time. + //Ideally we would like to use boost::chrono::.*seconds::min() but that + //is bound to 0, which causes the sleep_for call to be a no-op and + //thus useless to actually force a sleep. + + //**************************************************************** + //NOTE: This behavior makes this transport a poor choice for + // low latency communication. + //**************************************************************** + } + } + //Check if the master thread has requested a shutdown + if (boost::this_thread::interruption_requested()) break; + } + } + + bool _process_next_buffer() + { + managed_recv_buffer::sptr buff = _base_xport->get_recv_buff(0.0); + if (buff) { + stream_impl::sptr stream; + try { + const uint32_t stream_num = _classify(buff->cast<void*>(), _base_xport->get_recv_frame_size()); + { + //Hold the stream mutex long enough to pull a bounded buffer + //and lock it (increment its ref count). + boost::lock_guard<boost::mutex> lock(_mutex); + stream_map_t::iterator str_iter = _streams.find(stream_num); + if (str_iter != _streams.end()) { + stream = (*str_iter).second.lock(); + } + } + } catch (std::exception&) { + //If _classify throws we simply drop the frame + } + //Once a bounded buffer is acquired, we can rely on its + //thread safety to serialize with the consumer. + if (stream.get()) { + stream->push_recv_buff(buff); + } else { + boost::lock_guard<boost::mutex> lock(_mutex); + _num_dropped_frames++; + } + //We processed a packet, and there could be more coming + //Don't yield in the next iteration. + return true; + } else { + //The base transport is idle. Return false to let the + //thread yield. + return false; + } + } + + typedef std::map<uint32_t, stream_impl::wptr> stream_map_t; + + zero_copy_if::sptr _base_xport; + stream_classifier_fn _classify; + stream_map_t _streams; + const size_t _max_num_streams; + size_t _num_dropped_frames; + boost::thread _recv_thread; + boost::mutex _mutex; +}; + +muxed_zero_copy_if::sptr muxed_zero_copy_if::make( + zero_copy_if::sptr base_xport, + muxed_zero_copy_if::stream_classifier_fn classify_fn, + size_t max_streams +) { + return boost::make_shared<muxed_zero_copy_if_impl>(base_xport, classify_fn, max_streams); +} diff --git a/host/lib/transport/nirio/lvbitx/CMakeLists.txt b/host/lib/transport/nirio/lvbitx/CMakeLists.txt index b9a2a9f15..5741a12f8 100644 --- a/host/lib/transport/nirio/lvbitx/CMakeLists.txt +++ b/host/lib/transport/nirio/lvbitx/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2013 Ettus Research LLC +# Copyright 2013,2015 Ettus Research LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,8 +30,8 @@ MACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM lvbitx binfile) SET(IMAGES_PATH_OPT --uhd-images-path=${UHD_IMAGES_DIR}) ADD_CUSTOM_COMMAND( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/process-lvbitx.py DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/template_lvbitx.hpp DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/template_lvbitx.cpp @@ -41,6 +41,7 @@ MACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM lvbitx binfile) ) #make libuhd depend on the output file + LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp) LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp) ENDMACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM) diff --git a/host/lib/transport/nirio/rpc/rpc_client.cpp b/host/lib/transport/nirio/rpc/rpc_client.cpp index bbaf9f235..3d62b57ae 100644 --- a/host/lib/transport/nirio/rpc/rpc_client.cpp +++ b/host/lib/transport/nirio/rpc/rpc_client.cpp @@ -1,5 +1,5 @@ /// -// Copyright 2013 Ettus Research LLC +// Copyright 2013,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -55,22 +55,7 @@ rpc_client::rpc_client ( tcp::resolver::query::flags query_flags(tcp::resolver::query::passive); tcp::resolver::query query(tcp::v4(), server, port, query_flags); tcp::resolver::iterator iterator = resolver.resolve(query); - - #if BOOST_VERSION < 104700 - // default constructor creates end iterator - tcp::resolver::iterator end; - - boost::system::error_code error = boost::asio::error::host_not_found; - while (error && iterator != end) - { - _socket.close(); - _socket.connect(*iterator++, error); - } - if (error) - throw boost::system::system_error(error); - #else - boost::asio::connect(_socket, iterator); - #endif + boost::asio::connect(_socket, iterator); UHD_LOG << "rpc_client connected to server." << std::endl; @@ -109,11 +94,6 @@ rpc_client::rpc_client ( } catch (boost::exception&) { UHD_LOG << "rpc_client connection request cancelled/aborted." << std::endl; _exec_err.assign(boost::asio::error::connection_aborted, boost::asio::error::get_system_category()); -#if BOOST_VERSION < 104700 - } catch (std::exception& e) { - UHD_LOG << "rpc_client connection error: " << e.what() << std::endl; - _exec_err.assign(boost::asio::error::connection_aborted, boost::asio::error::get_system_category()); -#endif } } diff --git a/host/lib/transport/super_recv_packet_handler.hpp b/host/lib/transport/super_recv_packet_handler.hpp index 8bfa1973a..5ca1da687 100644 --- a/host/lib/transport/super_recv_packet_handler.hpp +++ b/host/lib/transport/super_recv_packet_handler.hpp @@ -24,18 +24,19 @@ #include <uhd/stream.hpp> #include <uhd/utils/msg.hpp> #include <uhd/utils/tasks.hpp> -#include <uhd/utils/atomic.hpp> #include <uhd/utils/byteswap.hpp> #include <uhd/types/metadata.hpp> #include <uhd/transport/vrt_if_packet.hpp> #include <uhd/transport/zero_copy.hpp> +#ifdef DEVICE3_STREAMER +# include "../rfnoc/rx_stream_terminator.hpp" +#endif #include <boost/dynamic_bitset.hpp> #include <boost/foreach.hpp> #include <boost/function.hpp> #include <boost/format.hpp> #include <boost/bind.hpp> #include <boost/make_shared.hpp> -#include <boost/thread/barrier.hpp> #include <iostream> #include <vector> @@ -92,22 +93,15 @@ public: } ~recv_packet_handler(void){ - _task_barrier.interrupt(); - _task_handlers.clear(); + /* NOP */ } //! Resize the number of transport channels void resize(const size_t size){ if (this->size() == size) return; - _task_handlers.clear(); _props.resize(size); //re-initialize all buffers infos by re-creating the vector _buffers_infos = std::vector<buffers_info_type>(4, buffers_info_type(size)); - _task_barrier.resize(size); - _task_handlers.resize(size); - for (size_t i = 1/*skip 0*/; i < size; i++){ - _task_handlers[i] = task::make(boost::bind(&recv_packet_handler::converter_thread_task, this, i)); - }; } //! Get the channel width of this handler @@ -121,13 +115,42 @@ public: _header_offset_words32 = header_offset_words32; } + ////////////////// RFNOC /////////////////////////// + //! Set the stream ID for a specific channel (or no SID) + void set_xport_chan_sid(const size_t xport_chan, const bool has_sid, const boost::uint32_t sid = 0){ + _props.at(xport_chan).has_sid = has_sid; + _props.at(xport_chan).sid = sid; + } + + //! Get the stream ID for a specific channel (or zero if no SID) + boost::uint32_t get_xport_chan_sid(const size_t xport_chan) const { + if (_props.at(xport_chan).has_sid) { + return _props.at(xport_chan).sid; + } else { + return 0; + } + } + + #ifdef DEVICE3_STREAMER + void set_terminator(uhd::rfnoc::rx_stream_terminator::sptr terminator) + { + _terminator = terminator; + } + + uhd::rfnoc::rx_stream_terminator::sptr get_terminator() + { + return _terminator; + } + #endif + ////////////////// RFNOC /////////////////////////// + /*! * Set the threshold for alignment failure. * How many packets throw out before giving up? * \param threshold number of packets per channel */ void set_alignment_failure_threshold(const size_t threshold){ - _alignment_faulure_threshold = threshold*this->size(); + _alignment_failure_threshold = threshold*this->size(); } //! Set the rate of ticks per second @@ -203,6 +226,13 @@ public: //! Overload call to issue stream commands void issue_stream_cmd(const stream_cmd_t &stream_cmd) { + // RFNoC: This needs to be checked by the radio block, once it's done. TODO remove this. + //if (stream_cmd.stream_now + //and stream_cmd.stream_mode != stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS + //and _props.size() > 1) { + //throw uhd::runtime_error("Attempting to do multi-channel receive with stream_now == true will result in misaligned channels. Aborting."); + //} + for (size_t i = 0; i < _props.size(); i++) { if (_props[i].issue_stream_cmd) _props[i].issue_stream_cmd(stream_cmd); @@ -234,7 +264,7 @@ public: buffs, nsamps_per_buff, metadata, timeout ); - if (one_packet){ + if (one_packet or metadata.end_of_burst){ #ifdef UHD_TXRX_DEBUG_PRINTS dbg_gather_data(nsamps_per_buff, accum_num_samps, metadata, timeout, one_packet); #endif @@ -242,7 +272,9 @@ public: } //first recv had an error code set, return immediately - if (metadata.error_code != rx_metadata_t::ERROR_CODE_NONE) return accum_num_samps; + if (metadata.error_code != rx_metadata_t::ERROR_CODE_NONE) { + return accum_num_samps; + } //loop until buffer is filled or error code while(accum_num_samps < nsamps_per_buff){ @@ -256,10 +288,16 @@ public: _queue_error_for_next_call = true; break; } + accum_num_samps += num_samps; + + //return immediately if end of burst + if (_queue_metadata.end_of_burst) { + break; + } } #ifdef UHD_TXRX_DEBUG_PRINTS - dbg_gather_data(nsamps_per_buff, accum_num_samps, metadata, timeout, one_packet); + dbg_gather_data(nsamps_per_buff, accum_num_samps, metadata, timeout, one_packet); #endif return accum_num_samps; } @@ -269,7 +307,7 @@ private: size_t _header_offset_words32; double _tick_rate, _samp_rate; bool _queue_error_for_next_call; - size_t _alignment_faulure_threshold; + size_t _alignment_failure_threshold; rx_metadata_t _queue_metadata; struct xport_chan_props_type{ xport_chan_props_type(void): @@ -283,6 +321,10 @@ private: handle_overflow_type handle_overflow; handle_flowctrl_type handle_flowctrl; size_t fc_update_window; + /////// RFNOC /////////// + bool has_sid; + boost::uint32_t sid; + /////// RFNOC /////////// }; std::vector<xport_chan_props_type> _props; size_t _num_outputs; @@ -355,6 +397,10 @@ private: int recvd_packets; #endif + #ifdef DEVICE3_STREAMER + uhd::rfnoc::rx_stream_terminator::sptr _terminator; + #endif + /******************************************************************* * Get and process a single packet from the transport: * Receive a single packet at the given index. @@ -421,6 +467,7 @@ private: const size_t expected_packet_count = _props[index].packet_count; _props[index].packet_count = (info.ifpi.packet_count + 1) & seq_mask; if (expected_packet_count != info.ifpi.packet_count){ + //UHD_MSG(status) << "expected: " << expected_packet_count << " got: " << info.ifpi.packet_count << std::endl; if (_props[index].handle_flowctrl) { // Always update flow control in this case, because we don't // know which packet was dropped and what state the upstream @@ -442,6 +489,10 @@ private: void _flush_all(double timeout) { + get_prev_buffer_info().reset(); + get_curr_buffer_info().reset(); + get_next_buffer_info().reset(); + for (size_t i = 0; i < _props.size(); i++) { per_buffer_info_type prev_buffer_info, curr_buffer_info; @@ -462,9 +513,6 @@ private: curr_buffer_info.reset(); } } - get_prev_buffer_info().reset(); - get_curr_buffer_info().reset(); - get_next_buffer_info().reset(); } /******************************************************************* @@ -562,20 +610,27 @@ private: curr_info.metadata.time_spec = next_info[index].time; curr_info.metadata.error_code = rx_metadata_t::error_code_t(get_context_code(next_info[index].vrt_hdr, next_info[index].ifpi)); if (curr_info.metadata.error_code == rx_metadata_t::ERROR_CODE_OVERFLOW){ + // Not sending flow control would cause timeouts due to source flow control locking up. + // Send first as the overrun handler may flush the receive buffers which could contain + // packets with sequence numbers after this packet's sequence number! + if(_props[index].handle_flowctrl) { + _props[index].handle_flowctrl(next_info[index].ifpi.packet_count); + } + rx_metadata_t metadata = curr_info.metadata; _props[index].handle_overflow(); curr_info.metadata = metadata; UHD_MSG(fastpath) << "O"; - - // Not sending flow control would cause timeouts due to source flow control locking up - if(_props[index].handle_flowctrl) { - _props[index].handle_flowctrl(next_info[index].ifpi.packet_count); - } } + curr_info[index].buff.reset(); + curr_info[index].copy_buff = NULL; return; case PACKET_TIMEOUT_ERROR: std::swap(curr_info, next_info); //save progress from curr -> next + if(_props[index].handle_flowctrl) { + _props[index].handle_flowctrl(next_info[index].ifpi.packet_count); + } curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_TIMEOUT; return; @@ -593,7 +648,7 @@ private: } //too many iterations: detect alignment failure - if (iterations++ > _alignment_faulure_threshold){ + if (iterations++ > _alignment_failure_threshold){ UHD_MSG(error) << boost::format( "The receive packet handler failed to time-align packets.\n" "%u received packets were processed by the handler.\n" @@ -657,7 +712,9 @@ private: _convert_bytes_to_copy = bytes_to_copy; //perform N channels of conversion - converter_thread_task(0); + for (size_t i = 0; i < this->size(); i++) { + convert_to_out_buff(i); + } //update the copy buffer's availability info.data_bytes_to_copy -= bytes_to_copy; @@ -670,15 +727,15 @@ private: return nsamps_to_copy_per_io_buff; } - /******************************************************************* - * Perform one thread's work of the conversion task. - * The entry and exit use a dual synchronization barrier, - * to wait for data to become ready and block until completion. - ******************************************************************/ - UHD_INLINE void converter_thread_task(const size_t index) + /*! Run the conversion from the internal buffers to the user's output + * buffer. + * + * - Calls the converter + * - Releases internal data buffers + * - Updates read/write pointers + */ + inline void convert_to_out_buff(const size_t index) { - _task_barrier.wait(); - //shortcut references to local data structures buffers_info_type &buff_info = get_curr_buffer_info(); per_buffer_info_type &info = buff_info[index]; @@ -702,13 +759,9 @@ private: if (buff_info.data_bytes_to_copy == _convert_bytes_to_copy){ info.buff.reset(); //effectively a release } - - if (index == 0) _task_barrier.wait_others(); } //! Shared variables for the worker threads - reusable_barrier _task_barrier; - std::vector<task::sptr> _task_handlers; size_t _convert_nsamps; const rx_streamer::buffs_type *_convert_buffs; size_t _convert_buffer_offset_bytes; diff --git a/host/lib/transport/super_send_packet_handler.hpp b/host/lib/transport/super_send_packet_handler.hpp index c2810842e..5e81fb442 100644 --- a/host/lib/transport/super_send_packet_handler.hpp +++ b/host/lib/transport/super_send_packet_handler.hpp @@ -24,11 +24,14 @@ #include <uhd/stream.hpp> #include <uhd/utils/msg.hpp> #include <uhd/utils/tasks.hpp> -#include <uhd/utils/atomic.hpp> #include <uhd/utils/byteswap.hpp> #include <uhd/types/metadata.hpp> #include <uhd/transport/vrt_if_packet.hpp> #include <uhd/transport/zero_copy.hpp> +#ifdef DEVICE3_STREAMER +# include "../rfnoc/tx_stream_terminator.hpp" +#endif +#include <boost/thread/thread.hpp> #include <boost/thread/thread_time.hpp> #include <boost/foreach.hpp> #include <boost/function.hpp> @@ -74,22 +77,15 @@ public: } ~send_packet_handler(void){ - _task_barrier.interrupt(); - _task_handlers.clear(); + /* NOP */ } //! Resize the number of transport channels void resize(const size_t size){ if (this->size() == size) return; - _task_handlers.clear(); _props.resize(size); static const boost::uint64_t zero = 0; _zero_buffs.resize(size, &zero); - _task_barrier.resize(size); - _task_handlers.resize(size); - for (size_t i = 1/*skip 0*/; i < size; i++){ - _task_handlers[i] = task::make(boost::bind(&send_packet_handler::converter_thread_task, this, i)); - }; } //! Get the channel width of this handler @@ -109,6 +105,29 @@ public: _props.at(xport_chan).sid = sid; } + ///////// RFNOC /////////////////// + //! Get the stream ID for a specific channel (or zero if no SID) + boost::uint32_t get_xport_chan_sid(const size_t xport_chan) const { + if (_props.at(xport_chan).has_sid) { + return _props.at(xport_chan).sid; + } else { + return 0; + } + } + + #ifdef DEVICE3_STREAMER + void set_terminator(uhd::rfnoc::tx_stream_terminator::sptr terminator) + { + _terminator = terminator; + } + + uhd::rfnoc::tx_stream_terminator::sptr get_terminator() + { + return _terminator; + } + #endif + ///////// RFNOC /////////////////// + void set_enable_trailer(const bool enable) { _has_tlr = enable; @@ -303,6 +322,10 @@ private: bool _cached_metadata; uhd::tx_metadata_t _metadata_cache; + #ifdef DEVICE3_STREAMER + uhd::rfnoc::tx_stream_terminator::sptr _terminator; + #endif + #ifdef UHD_TXRX_DEBUG_PRINTS struct dbg_send_stat_t { dbg_send_stat_t(long wc, size_t nspb, size_t nss, uhd::tx_metadata_t md, double to, double rate): @@ -377,21 +400,23 @@ private: _convert_if_packet_info = &if_packet_info; //perform N channels of conversion - converter_thread_task(0); + for (size_t i = 0; i < this->size(); i++) { + convert_to_in_buff(i); + } _next_packet_seq++; //increment sequence after commits return nsamps_per_buff; } - /******************************************************************* - * Perform one thread's work of the conversion task. - * The entry and exit use a dual synchronization barrier, - * to wait for data to become ready and block until completion. - ******************************************************************/ - UHD_INLINE void converter_thread_task(const size_t index) + /*! Run the conversion from the internal buffers to the user's input + * buffer. + * + * - Calls the converter + * - Releases internal data buffers + * - Updates read/write pointers + */ + UHD_INLINE void convert_to_in_buff(const size_t index) { - _task_barrier.wait(); - //shortcut references to local data structures managed_send_buffer::sptr &buff = _props[index].buff; vrt::if_packet_info_t if_packet_info = *_convert_if_packet_info; @@ -419,13 +444,9 @@ private: const size_t num_vita_words32 = _header_offset_words32+if_packet_info.num_packet_words32; buff->commit(num_vita_words32*sizeof(boost::uint32_t)); buff.reset(); //effectively a release - - if (index == 0) _task_barrier.wait_others(); } //! Shared variables for the worker threads - reusable_barrier _task_barrier; - std::vector<task::sptr> _task_handlers; size_t _convert_nsamps; const tx_streamer::buffs_type *_convert_buffs; size_t _convert_buffer_offset_bytes; diff --git a/host/lib/transport/usb_dummy_impl.cpp b/host/lib/transport/usb_dummy_impl.cpp index ce8c306e4..b53b6f590 100644 --- a/host/lib/transport/usb_dummy_impl.cpp +++ b/host/lib/transport/usb_dummy_impl.cpp @@ -31,12 +31,20 @@ std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list(boost::u return std::vector<usb_device_handle::sptr>(); //empty list } -usb_control::sptr usb_control::make(usb_device_handle::sptr, const size_t){ +usb_control::sptr usb_control::make( + usb_device_handle::sptr, + const int +) { throw uhd::not_implemented_error("no usb support -> usb_control::make not implemented"); } usb_zero_copy::sptr usb_zero_copy::make( - usb_device_handle::sptr, const size_t, const size_t, const size_t, const size_t, const device_addr_t & + usb_device_handle::sptr, + const int, + const unsigned char, + const int, + const unsigned char, + const device_addr_t & ){ throw uhd::not_implemented_error("no usb support -> usb_zero_copy::make not implemented"); } diff --git a/host/lib/transport/zero_copy_recv_offload.cpp b/host/lib/transport/zero_copy_recv_offload.cpp new file mode 100644 index 000000000..e8b013abc --- /dev/null +++ b/host/lib/transport/zero_copy_recv_offload.cpp @@ -0,0 +1,158 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/zero_copy_recv_offload.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/thread.hpp> +#include <boost/bind.hpp> + +using namespace uhd; +using namespace uhd::transport; + +typedef bounded_buffer<managed_recv_buffer::sptr> bounded_buffer_t; + +/*********************************************************************** + * Zero copy offload transport: + * An intermediate transport that utilizes threading to free + * the main thread from any receive work. + **********************************************************************/ +class zero_copy_recv_offload_impl : public zero_copy_recv_offload { +public: + typedef boost::shared_ptr<zero_copy_recv_offload_impl> sptr; + + zero_copy_recv_offload_impl(zero_copy_if::sptr transport, + const double timeout) : + _transport(transport), _timeout(timeout), + _inbox(transport->get_num_recv_frames()), + _recv_done(false) + { + UHD_LOG << "Created threaded transport" << std::endl; + + // Create the receive and send threads to offload + // the system calls onto other threads + _recv_thread = boost::thread( + boost::bind(&zero_copy_recv_offload_impl::enqueue_recv, this) + ); + } + + // Receive thread flags + void set_recv_done() + { + boost::lock_guard<boost::mutex> guard(_recv_mutex); + _recv_done = true; + } + + bool is_recv_done() + { + boost::lock_guard<boost::mutex> guard(_recv_mutex); + return _recv_done; + } + + ~zero_copy_recv_offload_impl() + { + // Signal the threads we're finished + set_recv_done(); + + // Wait for them to join + UHD_SAFE_CALL( + _recv_thread.join(); + ) + } + + // The receive thread function is responsible for + // pulling pointers to managed receiver buffers quickly + void enqueue_recv() + { + while (not is_recv_done()) { + managed_recv_buffer::sptr buff = _transport->get_recv_buff(_timeout); + if (not buff) continue; + _inbox.push_with_timed_wait(buff, _timeout); + } + } + + /******************************************************************* + * Receive implementation: + * Pop the receive buffer pointer from the underlying transport + ******************************************************************/ + managed_recv_buffer::sptr get_recv_buff(double timeout) + { + managed_recv_buffer::sptr ptr; + _inbox.pop_with_timed_wait(ptr, timeout); + return ptr; + } + + size_t get_num_recv_frames() const + { + return _transport->get_num_recv_frames(); + } + + size_t get_recv_frame_size() const + { + return _transport->get_recv_frame_size(); + } + + /******************************************************************* + * Send implementation: + * Pass the send buffer pointer from the underlying transport + ******************************************************************/ + managed_send_buffer::sptr get_send_buff(double timeout) + { + return _transport->get_send_buff(timeout); + } + + size_t get_num_send_frames() const + { + return _transport->get_num_send_frames(); + } + + size_t get_send_frame_size() const + { + return _transport->get_send_frame_size(); + } + +private: + // The linked transport + zero_copy_if::sptr _transport; + + const double _timeout; + + // Shared buffers + bounded_buffer_t _inbox; + + // Threading + bool _recv_done; + boost::thread _recv_thread; + boost::mutex _recv_mutex; +}; + +zero_copy_recv_offload::sptr zero_copy_recv_offload::make( + zero_copy_if::sptr transport, + const double timeout) +{ + zero_copy_recv_offload_impl::sptr zero_copy_recv_offload( + new zero_copy_recv_offload_impl(transport, timeout) + ); + + return zero_copy_recv_offload; +} diff --git a/host/lib/types/device_addr.cpp b/host/lib/types/device_addr.cpp index 1554c3e4e..747f61b8d 100644 --- a/host/lib/types/device_addr.cpp +++ b/host/lib/types/device_addr.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -26,8 +26,8 @@ using namespace uhd; -static const std::string arg_delim = ","; -static const std::string pair_delim = "="; +static const char* arg_delim = ","; +static const char* pair_delim = "="; static std::string trim(const std::string &in){ return boost::algorithm::trim_copy(in); @@ -35,7 +35,7 @@ static std::string trim(const std::string &in){ #define tokenizer(inp, sep) \ boost::tokenizer<boost::char_separator<char> > \ - (inp, boost::char_separator<char>(sep.c_str())) + (inp, boost::char_separator<char>(sep)) device_addr_t::device_addr_t(const std::string &args){ BOOST_FOREACH(const std::string &pair, tokenizer(args, arg_delim)){ diff --git a/host/lib/types/sensors.cpp b/host/lib/types/sensors.cpp index 52a63d14c..0406e35d4 100644 --- a/host/lib/types/sensors.cpp +++ b/host/lib/types/sensors.cpp @@ -69,6 +69,12 @@ sensor_value_t::sensor_value_t( /* NOP */ } +sensor_value_t::sensor_value_t(const sensor_value_t& source) +{ + *this = source; +} + + std::string sensor_value_t::to_pp_string(void) const{ switch(type){ case BOOLEAN: @@ -92,3 +98,12 @@ signed sensor_value_t::to_int(void) const{ double sensor_value_t::to_real(void) const{ return boost::lexical_cast<double>(value); } + +sensor_value_t& sensor_value_t::operator=(const sensor_value_t& rhs) +{ + this->name = rhs.name; + this->value = rhs.value; + this->unit = rhs.unit; + this->type = rhs.type; + return *this; +} diff --git a/host/lib/types/serial.cpp b/host/lib/types/serial.cpp index 9b8336dd8..52961691c 100644 --- a/host/lib/types/serial.cpp +++ b/host/lib/types/serial.cpp @@ -40,7 +40,8 @@ spi_config_t::spi_config_t(edge_t edge): mosi_edge(edge), miso_edge(edge) { - /* NOP */ + // By default don't use a custom clock speed for the transaction + use_custom_divider = false; } void i2c_iface::write_eeprom( diff --git a/host/lib/uhd.rc.in b/host/lib/uhd.rc.in index 051511327..24177a00a 100644 --- a/host/lib/uhd.rc.in +++ b/host/lib/uhd.rc.in @@ -1,8 +1,8 @@ #include <windows.h> VS_VERSION_INFO VERSIONINFO - FILEVERSION @TRIMMED_VERSION_MAJOR@,@TRIMMED_VERSION_MINOR@,@RC_TRIMMED_VERSION_PATCH@,@UHD_GIT_COUNT@ - PRODUCTVERSION @TRIMMED_VERSION_MAJOR@,@TRIMMED_VERSION_MINOR@,@RC_TRIMMED_VERSION_PATCH@,@UHD_GIT_COUNT@ + FILEVERSION @TRIMMED_VERSION_MAJOR_API@,@TRIMMED_VERSION_ABI@,@RC_TRIMMED_VERSION_PATCH@,@UHD_GIT_COUNT@ + PRODUCTVERSION @TRIMMED_VERSION_MAJOR_API@,@TRIMMED_VERSION_ABI@,@RC_TRIMMED_VERSION_PATCH@,@UHD_GIT_COUNT@ FILEFLAGSMASK 0x3fL #ifndef NDEBUG FILEFLAGS 0x0L diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index 5c9592970..f769417d9 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -18,8 +18,6 @@ ######################################################################## # This file included, use CMake directory variables ######################################################################## -find_package(GPSD) - INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) LIBUHD_APPEND_SOURCES( @@ -32,6 +30,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/mboard_eeprom.cpp ${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/subdev_spec.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fe_connection.cpp ) IF(ENABLE_C_API) @@ -43,8 +42,6 @@ IF(ENABLE_C_API) ) ENDIF(ENABLE_C_API) -LIBUHD_REGISTER_COMPONENT("GPSD" ENABLE_GPSD OFF "ENABLE_LIBUHD;ENABLE_GPSD;LIBGPS_FOUND" OFF OFF) - IF(ENABLE_GPSD) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/gpsd_iface.cpp @@ -55,6 +52,7 @@ ENDIF(ENABLE_GPSD) INCLUDE_SUBDIRECTORY(cores) INCLUDE_SUBDIRECTORY(dboard) INCLUDE_SUBDIRECTORY(common) +INCLUDE_SUBDIRECTORY(device3) INCLUDE_SUBDIRECTORY(usrp1) INCLUDE_SUBDIRECTORY(usrp2) INCLUDE_SUBDIRECTORY(b100) @@ -62,3 +60,4 @@ INCLUDE_SUBDIRECTORY(e100) INCLUDE_SUBDIRECTORY(e300) INCLUDE_SUBDIRECTORY(x300) INCLUDE_SUBDIRECTORY(b200) +INCLUDE_SUBDIRECTORY(n230) diff --git a/host/lib/usrp/b100/CMakeLists.txt b/host/lib/usrp/b100/CMakeLists.txt index 1558cd974..66129458c 100644 --- a/host/lib/usrp/b100/CMakeLists.txt +++ b/host/lib/usrp/b100/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the B100 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_B100) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/b100_impl.cpp diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp index c4279913c..eec9f0e9a 100644 --- a/host/lib/usrp/b100/b100_impl.cpp +++ b/host/lib/usrp/b100/b100_impl.cpp @@ -281,7 +281,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tree->create<std::string>(mb_path / "name").set("B100"); _tree->create<std::string>(mb_path / "codename").set("B-Hundo"); _tree->create<std::string>(mb_path / "load_eeprom") - .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); //////////////////////////////////////////////////////////////////// // setup the mboard eeprom @@ -289,20 +289,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, B100_EEPROM_MAP_KEY); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&b100_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////////// //^^^ clock created up top, just reg props here... ^^^ _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) - .subscribe(boost::bind(&b100_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_tick_rate, this, _1)); - //subscribe the command time while we are at it + //add_coerced_subscriber the command time while we are at it _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create codec control objects @@ -313,20 +313,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(b100_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&b100_impl::update_rx_codec_gain, this, _1)) + .set_coercer(boost::bind(&b100_impl::update_rx_codec_gain, this, _1)) .set(0.0); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(b100_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) - .publish(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)) + .add_coerced_subscriber(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) + .set_publisher(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)) .set(0.0); //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&b100_impl::get_ref_locked, this)); + .set_publisher(boost::bind(&b100_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // create frontend control objects @@ -335,27 +335,27 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE)); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////////// @@ -374,20 +374,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _rx_dsps[dspno]->set_link_rate(B100_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) - .subscribe(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////////// @@ -398,17 +398,17 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ ); _tx_dsp->set_link_rate(B100_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) - .subscribe(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); //////////////////////////////////////////////////////////////////// // create time control objects @@ -422,21 +422,21 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _time64, _1)); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&b100_impl::update_clock_source, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_clock_source, this, _1)); static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); @@ -445,7 +445,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _user, _1)); //////////////////////////////////////////////////////////////////// // create dboard control objects @@ -463,32 +463,31 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1)); //create a new dboard interface and manager - _dboard_iface = make_b100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); _dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_b100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1)); } //initialize io handling @@ -503,8 +502,8 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// this->update_rates(); - _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter - .subscribe(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") //now add_coerced_subscriber the clock rate setter + .add_coerced_subscriber(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); //reset cordic rates and their properties to zero BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ diff --git a/host/lib/usrp/b100/b100_impl.hpp b/host/lib/usrp/b100/b100_impl.hpp index 5a8f70d73..7f37030d2 100644 --- a/host/lib/usrp/b100/b100_impl.hpp +++ b/host/lib/usrp/b100/b100_impl.hpp @@ -126,7 +126,6 @@ private: //dboard stuff uhd::usrp::dboard_manager::sptr _dboard_manager; - uhd::usrp::dboard_iface::sptr _dboard_iface; bool _ignore_cal_file; std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; diff --git a/host/lib/usrp/b100/dboard_iface.cpp b/host/lib/usrp/b100/dboard_iface.cpp index 325efeec1..9829f3f09 100644 --- a/host/lib/usrp/b100/dboard_iface.cpp +++ b/host/lib/usrp/b100/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011,2015,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -66,12 +66,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -97,6 +101,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn); private: timed_wb_iface::sptr _wb_iface; @@ -127,6 +132,7 @@ void b100_dboard_iface::set_clock_rate(unit_t unit, double rate){ switch(unit){ case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + case UNIT_BOTH: set_clock_rate(UNIT_RX, rate); set_clock_rate(UNIT_TX, rate); return; } } @@ -142,14 +148,15 @@ double b100_dboard_iface::get_clock_rate(unit_t unit){ switch(unit){ case UNIT_RX: return _clock->get_rx_clock_rate(); case UNIT_TX: return _clock->get_tx_clock_rate(); + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void b100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ switch(unit){ case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } @@ -160,28 +167,40 @@ double b100_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ -void b100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void b100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void b100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t b100_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void b100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void b100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t b100_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t b100_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void b100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t b100_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void b100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void b100_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void b100_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t b100_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t b100_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -196,8 +215,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ switch(unit){ case dboard_iface::UNIT_TX: return B100_SPI_SS_TX_DB; case dboard_iface::UNIT_RX: return B100_SPI_SS_RX_DB; + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void b100_dboard_iface::write_spi( @@ -268,3 +287,8 @@ uhd::time_spec_t b100_dboard_iface::get_command_time(void) { return _wb_iface->get_time(); } + +void b100_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/b200/CMakeLists.txt b/host/lib/usrp/b200/CMakeLists.txt index 76710dc65..d953acb19 100644 --- a/host/lib/usrp/b200/CMakeLists.txt +++ b/host/lib/usrp/b200/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the B200 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_B200) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/b200_image_loader.cpp @@ -32,5 +30,6 @@ IF(ENABLE_B200) ${CMAKE_CURRENT_SOURCE_DIR}/b200_io_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/b200_uart.cpp ${CMAKE_CURRENT_SOURCE_DIR}/b200_cores.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/b200_radio_ctrl_core.cpp ) ENDIF(ENABLE_B200) diff --git a/host/lib/usrp/b200/b200_iface.cpp b/host/lib/usrp/b200/b200_iface.cpp index 207c418fc..218f8fd0e 100644 --- a/host/lib/usrp/b200/b200_iface.cpp +++ b/host/lib/usrp/b200/b200_iface.cpp @@ -142,7 +142,7 @@ public: boost::uint16_t index, unsigned char *buff, boost::uint16_t length, - boost::int32_t timeout = 0) { + boost::uint32_t timeout = 0) { return _usb_ctrl->submit(VRT_VENDOR_OUT, // bmReqeustType request, // bRequest value, // wValue @@ -157,7 +157,7 @@ public: boost::uint16_t index, unsigned char *buff, boost::uint16_t length, - boost::int32_t timeout = 0) { + boost::uint32_t timeout = 0) { return _usb_ctrl->submit(VRT_VENDOR_IN, // bmReqeustType request, // bRequest value, // wValue diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp index d7663c68e..9526ae2d1 100644 --- a/host/lib/usrp/b200/b200_impl.cpp +++ b/host/lib/usrp/b200/b200_impl.cpp @@ -42,6 +42,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace uhd::transport; static const boost::posix_time::milliseconds REENUMERATION_TIMEOUT_MS(3000); @@ -364,7 +365,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s const mboard_eeprom_t mb_eeprom(*_iface, "B200"); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&b200_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&b200_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // Identify the device type @@ -465,7 +466,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s //////////////////////////////////////////////////////////////////// // Local control endpoint //////////////////////////////////////////////////////////////////// - _local_ctrl = radio_ctrl_core_3000::make(false/*lilE*/, _ctrl_transport, zero_copy_if::sptr()/*null*/, B200_LOCAL_CTRL_SID); + _local_ctrl = b200_radio_ctrl_core::make(false/*lilE*/, _ctrl_transport, zero_copy_if::sptr()/*null*/, B200_LOCAL_CTRL_SID); _local_ctrl->hold_task(_async_task); _async_task_data->local_ctrl = _local_ctrl; //weak this->check_fpga_compat(); @@ -502,7 +503,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s BOOST_FOREACH(const std::string &name, _gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _gps, name)); } } else @@ -579,9 +580,9 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s // create clock control objects //////////////////////////////////////////////////////////////////// _tree->create<double>(mb_path / "tick_rate") - .coerce(boost::bind(&b200_impl::set_tick_rate, this, _1)) - .publish(boost::bind(&b200_impl::get_tick_rate, this)) - .subscribe(boost::bind(&b200_impl::update_tick_rate, this, _1)); + .set_coercer(boost::bind(&b200_impl::set_tick_rate, this, _1)) + .set_publisher(boost::bind(&b200_impl::get_tick_rate, this)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_tick_rate, this, _1)); _tree->create<time_spec_t>(mb_path / "time" / "cmd"); _tree->create<bool>(mb_path / "auto_tick_rate").set(false); @@ -589,7 +590,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") - .publish(boost::bind(&b200_impl::get_ref_locked, this)); + .set_publisher(boost::bind(&b200_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // create frontend mapping @@ -598,13 +599,13 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .coerce(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) .set(subdev_spec_t()) - .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .coerce(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) .set(subdev_spec_t()) - .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1)); //////////////////////////////////////////////////////////////////// // setup radio control @@ -619,27 +620,31 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s for (size_t i = 0; i < _radio_perifs.size(); i++) this->setup_radio(i); - //now test each radio module's connection to the codec interface BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) { - _codec_mgr->loopback_self_test(perif.ctrl, TOREG(SR_CODEC_IDLE), RB64_CODEC_READBACK); + _codec_mgr->loopback_self_test( + boost::bind( + &b200_radio_ctrl_core::poke32, perif.ctrl, TOREG(SR_CODEC_IDLE), _1 + ), + boost::bind(&b200_radio_ctrl_core::peek64, perif.ctrl, RB64_CODEC_READBACK) + ); } //register time now and pps onto available radio cores _tree->create<time_spec_t>(mb_path / "time" / "now") - .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) - .subscribe(boost::bind(&b200_impl::set_time, this, _1)) + .set_publisher(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&b200_impl::set_time, this, _1)) .set(0.0); //re-sync the times when the tick rate changes _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&b200_impl::sync_times, this)); + .add_coerced_subscriber(boost::bind(&b200_impl::sync_times, this)); _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)); + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)); BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) { _tree->access<time_spec_t>(mb_path / "time" / "pps") - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, perif.time64, _1)); + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, perif.time64, _1)); } //setup time source props @@ -649,8 +654,8 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options") .set(time_sources); _tree->create<std::string>(mb_path / "time_source" / "value") - .coerce(boost::bind(&check_option_valid, "time source", time_sources, _1)) - .subscribe(boost::bind(&b200_impl::update_time_source, this, _1)); + .set_coercer(boost::bind(&check_option_valid, "time source", time_sources, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_time_source, this, _1)); //setup reference source props static const std::vector<std::string> clock_sources = (_gpsdo_capable) ? boost::assign::list_of("internal")("external")("gpsdo") : @@ -658,21 +663,21 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options") .set(clock_sources); _tree->create<std::string>(mb_path / "clock_source" / "value") - .coerce(boost::bind(&check_option_valid, "clock source", clock_sources, _1)) - .subscribe(boost::bind(&b200_impl::update_clock_source, this, _1)); + .set_coercer(boost::bind(&check_option_valid, "clock source", clock_sources, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_clock_source, this, _1)); //////////////////////////////////////////////////////////////////// // front panel gpio //////////////////////////////////////////////////////////////////// - _radio_perifs[0].fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); + _radio_perifs[0].fp_gpio = gpio_atr_3000::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) { _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr.second) .set(0) - .subscribe(boost::bind(&b200_impl::set_fp_gpio, this, _radio_perifs[0].fp_gpio, attr.first, _1)); + .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, _radio_perifs[0].fp_gpio, attr.first, _1)); } _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") - .publish(boost::bind(&b200_impl::get_fp_gpio, this, _radio_perifs[0].fp_gpio)); + .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, _radio_perifs[0].fp_gpio)); //////////////////////////////////////////////////////////////////// // dboard eeproms but not really @@ -685,10 +690,14 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s //////////////////////////////////////////////////////////////////// // do some post-init tasks //////////////////////////////////////////////////////////////////// - - //init the clock rate to something reasonable - double default_tick_rate = device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE); + // Init the clock rate and the auto mcr appropriately + if (not device_addr.has_key("master_clock_rate")) { + UHD_MSG(status) << "Setting master clock rate selection to 'automatic'." << std::endl; + } + // We can automatically choose a master clock rate, but not if the user specifies one + const double default_tick_rate = device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE); _tree->access<double>(mb_path / "tick_rate").set(default_tick_rate); + _tree->access<bool>(mb_path / "auto_tick_rate").set(not device_addr.has_key("master_clock_rate")); //subdev spec contains full width of selections subdev_spec_t rx_spec, tx_spec; @@ -712,12 +721,6 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _radio_perifs[i].ddc->set_host_rate(default_tick_rate / ad936x_manager::DEFAULT_DECIM); _radio_perifs[i].duc->set_host_rate(default_tick_rate / ad936x_manager::DEFAULT_INTERP); } - // We can automatically choose a master clock rate, but not if the user specifies one - _tree->access<bool>(mb_path / "auto_tick_rate").set(not device_addr.has_key("master_clock_rate")); - if (not device_addr.has_key("master_clock_rate")) { - UHD_MSG(status) << "Setting master clock rate selection to 'automatic'." << std::endl; - } - } b200_impl::~b200_impl(void) @@ -753,7 +756,7 @@ void b200_impl::setup_radio(const size_t dspno) //////////////////////////////////////////////////////////////////// // radio control //////////////////////////////////////////////////////////////////// - perif.ctrl = radio_ctrl_core_3000::make( + perif.ctrl = b200_radio_ctrl_core::make( false/*lilE*/, _ctrl_transport, zero_copy_if::sptr()/*null*/, @@ -761,22 +764,23 @@ void b200_impl::setup_radio(const size_t dspno) perif.ctrl->hold_task(_async_task); _async_task_data->radio_ctrl[dspno] = perif.ctrl; //weak _tree->access<time_spec_t>(mb_path / "time" / "cmd") - .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); + .add_coerced_subscriber(boost::bind(&b200_radio_ctrl_core::set_time, perif.ctrl, _1)); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); + .add_coerced_subscriber(boost::bind(&b200_radio_ctrl_core::set_tick_rate, perif.ctrl, _1)); this->register_loopback_self_test(perif.ctrl); //////////////////////////////////////////////////////////////////// // Set up peripherals //////////////////////////////////////////////////////////////////// - perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_ATR)); + perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, TOREG(SR_ATR)); + perif.atr->set_atr_mode(MODE_ATR, 0xFFFFFFFF); // create rx dsp control objects perif.framer = rx_vita_core_3000::make(perif.ctrl, TOREG(SR_RX_CTRL)); perif.ddc = rx_dsp_core_3000::make(perif.ctrl, TOREG(SR_RX_DSP), true /*is_b200?*/); perif.ddc->set_link_rate(10e9/8); //whatever - perif.ddc->set_mux("IQ", false, dspno == 1 ? true : false, dspno == 1 ? true : false); + perif.ddc->set_mux(usrp::fe_connection_t(dspno == 1 ? "IbQb" : "IQ")); perif.ddc->set_freq(rx_dsp_core_3000::DEFAULT_CORDIC_FREQ); - perif.deframer = tx_vita_core_3000::make(perif.ctrl, TOREG(SR_TX_CTRL)); + perif.deframer = tx_vita_core_3000::make_no_radio_buff(perif.ctrl, TOREG(SR_TX_CTRL)); perif.duc = tx_dsp_core_3000::make(perif.ctrl, TOREG(SR_TX_DSP)); perif.duc->set_link_rate(10e9/8); //whatever perif.duc->set_freq(tx_dsp_core_3000::DEFAULT_CORDIC_FREQ); @@ -796,15 +800,15 @@ void b200_impl::setup_radio(const size_t dspno) perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); _tree->create<bool>(rx_dsp_path / "rate" / "set").set(false); _tree->access<double>(rx_dsp_path / "rate" / "value") - .coerce(boost::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, _1)) - .subscribe(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), rx_dsp_path / "rate" / "set", true, _1)) - .subscribe(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, _1)) + .add_coerced_subscriber(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), rx_dsp_path / "rate" / "set", true, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1)) ; _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) - .subscribe(boost::bind(&b200_impl::update_rx_dsp_tick_rate, this, _1, perif.ddc, rx_dsp_path)) + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_rx_dsp_tick_rate, this, _1, perif.ddc, rx_dsp_path)) ; //////////////////////////////////////////////////////////////////// @@ -814,13 +818,12 @@ void b200_impl::setup_radio(const size_t dspno) perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); _tree->create<bool>(tx_dsp_path / "rate" / "set").set(false); _tree->access<double>(tx_dsp_path / "rate" / "value") - .coerce(boost::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, _1)) - .subscribe(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), tx_dsp_path / "rate" / "set", true, _1)) - .subscribe(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, _1)) + .add_coerced_subscriber(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), tx_dsp_path / "rate" / "set", true, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1)) ; _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1)) - .subscribe(boost::bind(&b200_impl::update_tx_dsp_tick_rate, this, _1, perif.duc, tx_dsp_path)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_tx_dsp_tick_rate, this, _1, perif.duc, tx_dsp_path)) ; //////////////////////////////////////////////////////////////////// @@ -840,17 +843,17 @@ void b200_impl::setup_radio(const size_t dspno) // Now connect all the b200_impl-specific items _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked") - .publish(boost::bind(&b200_impl::get_fe_pll_locked, this, dir == TX_DIRECTION)) + .set_publisher(boost::bind(&b200_impl::get_fe_pll_locked, this, dir == TX_DIRECTION)) ; _tree->access<double>(rf_fe_path / "freq" / "value") - .subscribe(boost::bind(&b200_impl::update_bandsel, this, key, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_bandsel, this, key, _1)) ; if (dir == RX_DIRECTION) { static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2"); _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants); _tree->create<std::string>(rf_fe_path / "antenna" / "value") - .subscribe(boost::bind(&b200_impl::update_antenna_sel, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_antenna_sel, this, dspno, _1)) .set("RX2") ; @@ -986,27 +989,6 @@ void b200_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom) mb_eeprom.commit(*_iface, "B200"); } - -boost::uint32_t b200_impl::get_fp_gpio(gpio_core_200::sptr gpio) -{ - return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); -} - -void b200_impl::set_fp_gpio(gpio_core_200::sptr gpio, const gpio_attr_t attr, const boost::uint32_t value) -{ - switch (attr) - { - case GPIO_CTRL: return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); - case GPIO_DDR: return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); - case GPIO_OUT: return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); - case GPIO_ATR_0X: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); - case GPIO_ATR_RX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); - case GPIO_ATR_TX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); - case GPIO_ATR_XX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); - default: UHD_THROW_INVALID_CODE_PATH(); - } -} - /*********************************************************************** * Reference time and clock **********************************************************************/ @@ -1189,11 +1171,11 @@ void b200_impl::update_atrs(void) if (enb_rx and enb_tx) fd = STATE_FDX1_TXRX; if (enb_rx and not enb_tx) fd = rxonly; if (not enb_rx and enb_tx) fd = txonly; - gpio_core_200_32wo::sptr atr = perif.atr; - atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, STATE_OFF); - atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rxonly); - atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, txonly); - atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd); + gpio_atr_3000::sptr atr = perif.atr; + atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF); + atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly); + atr->set_atr_reg(ATR_REG_TX_ONLY, txonly); + atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd); } if (_radio_perifs.size() > _fe2 and _radio_perifs[_fe2].atr) { @@ -1207,11 +1189,11 @@ void b200_impl::update_atrs(void) if (enb_rx and enb_tx) fd = STATE_FDX2_TXRX; if (enb_rx and not enb_tx) fd = rxonly; if (not enb_rx and enb_tx) fd = txonly; - gpio_core_200_32wo::sptr atr = perif.atr; - atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, STATE_OFF); - atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rxonly); - atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, txonly); - atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd); + gpio_atr_3000::sptr atr = perif.atr; + atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF); + atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly); + atr->set_atr_reg(ATR_REG_TX_ONLY, txonly); + atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd); } } diff --git a/host/lib/usrp/b200/b200_impl.hpp b/host/lib/usrp/b200/b200_impl.hpp index 6c36e883d..3ca76fce6 100644 --- a/host/lib/usrp/b200/b200_impl.hpp +++ b/host/lib/usrp/b200/b200_impl.hpp @@ -27,8 +27,8 @@ #include "rx_vita_core_3000.hpp" #include "tx_vita_core_3000.hpp" #include "time_core_3000.hpp" -#include "gpio_core_200.hpp" -#include "radio_ctrl_core_3000.hpp" +#include "gpio_atr_3000.hpp" +#include "b200_radio_ctrl_core.hpp" #include "rx_dsp_core_3000.hpp" #include "tx_dsp_core_3000.hpp" #include <uhd/device.hpp> @@ -49,8 +49,8 @@ #include "recv_packet_demuxer_3000.hpp" static const boost::uint8_t B200_FW_COMPAT_NUM_MAJOR = 8; static const boost::uint8_t B200_FW_COMPAT_NUM_MINOR = 0; -static const boost::uint16_t B200_FPGA_COMPAT_NUM = 13; -static const boost::uint16_t B205_FPGA_COMPAT_NUM = 4; +static const boost::uint16_t B200_FPGA_COMPAT_NUM = 14; +static const boost::uint16_t B205_FPGA_COMPAT_NUM = 5; static const double B200_BUS_CLOCK_RATE = 100e6; static const boost::uint32_t B200_GPSDO_ST_NONE = 0x83; static const size_t B200_MAX_RATE_USB2 = 53248000; // bytes/s @@ -131,7 +131,7 @@ private: //controllers b200_iface::sptr _iface; - radio_ctrl_core_3000::sptr _local_ctrl; + b200_radio_ctrl_core::sptr _local_ctrl; uhd::usrp::ad9361_ctrl::sptr _codec_ctrl; uhd::usrp::ad936x_manager::sptr _codec_mgr; b200_local_spi_core::sptr _spi_iface; @@ -154,8 +154,8 @@ private: struct AsyncTaskData { boost::shared_ptr<async_md_type> async_md; - boost::weak_ptr<radio_ctrl_core_3000> local_ctrl; - boost::weak_ptr<radio_ctrl_core_3000> radio_ctrl[2]; + boost::weak_ptr<b200_radio_ctrl_core> local_ctrl; + boost::weak_ptr<b200_radio_ctrl_core> radio_ctrl[2]; b200_uart::sptr gpsdo_uart; }; boost::shared_ptr<AsyncTaskData> _async_task_data; @@ -179,9 +179,9 @@ private: //perifs in the radio core struct radio_perifs_t { - radio_ctrl_core_3000::sptr ctrl; - gpio_core_200_32wo::sptr atr; - gpio_core_200::sptr fp_gpio; + b200_radio_ctrl_core::sptr ctrl; + uhd::usrp::gpio_atr::gpio_atr_3000::sptr atr; + uhd::usrp::gpio_atr::gpio_atr_3000::sptr fp_gpio; time_core_3000::sptr time64; rx_vita_core_3000::sptr framer; rx_dsp_core_3000::sptr ddc; @@ -224,14 +224,10 @@ private: enum time_source_t {GPSDO=0,EXTERNAL=1,INTERNAL=2,NONE=3,UNKNOWN=4} _time_source; void update_gpio_state(void); - void reset_codec_dcm(void); void update_enables(void); void update_atrs(void); - boost::uint32_t get_fp_gpio(gpio_core_200::sptr); - void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t); - double _tick_rate; double get_tick_rate(void){return _tick_rate;} double set_tick_rate(const double rate); diff --git a/host/lib/usrp/b200/b200_io_impl.cpp b/host/lib/usrp/b200/b200_io_impl.cpp index d0d769504..d186ec907 100644 --- a/host/lib/usrp/b200/b200_io_impl.cpp +++ b/host/lib/usrp/b200/b200_io_impl.cpp @@ -158,7 +158,6 @@ void b200_impl::update_tick_rate(const double new_tick_rate) boost::shared_ptr<sph::send_packet_streamer> my_streamer = boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock()); if (my_streamer) my_streamer->set_tick_rate(new_tick_rate); - perif.deframer->set_tick_rate(new_tick_rate); } } @@ -319,7 +318,7 @@ boost::optional<uhd::msg_task::msg_type_t> b200_impl::handle_async_task( case B200_RESP1_MSG_SID: case B200_LOCAL_RESP_SID: { - radio_ctrl_core_3000::sptr ctrl; + b200_radio_ctrl_core::sptr ctrl; if (sid == B200_RESP0_MSG_SID) ctrl = data->radio_ctrl[0].lock(); if (sid == B200_RESP1_MSG_SID) ctrl = data->radio_ctrl[1].lock(); if (sid == B200_LOCAL_RESP_SID) ctrl = data->local_ctrl.lock(); diff --git a/host/lib/usrp/b200/b200_radio_ctrl_core.cpp b/host/lib/usrp/b200/b200_radio_ctrl_core.cpp new file mode 100644 index 000000000..b6d8f95df --- /dev/null +++ b/host/lib/usrp/b200/b200_radio_ctrl_core.cpp @@ -0,0 +1,347 @@ +// +// Copyright 2012-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "b200_radio_ctrl_core.hpp" +#include "async_packet_handler.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <queue> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const double ACK_TIMEOUT = 2.0; //supposed to be worst case practical timeout +static const double MASSIVE_TIMEOUT = 10.0; //for when we wait on a timed command +static const size_t SR_READBACK = 32; + +b200_radio_ctrl_core::~b200_radio_ctrl_core(void){ + /* NOP */ +} + +class b200_radio_ctrl_core_impl: public b200_radio_ctrl_core +{ +public: + + b200_radio_ctrl_core_impl(const bool big_endian, + uhd::transport::zero_copy_if::sptr ctrl_xport, + uhd::transport::zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, const std::string &name) : + _link_type(vrt::if_packet_info_t::LINK_TYPE_CHDR), _packet_type( + vrt::if_packet_info_t::PACKET_TYPE_CONTEXT), _bige( + big_endian), _ctrl_xport(ctrl_xport), _resp_xport( + resp_xport), _sid(sid), _name(name), _seq_out(0), _timeout( + ACK_TIMEOUT), _resp_queue(128/*max response msgs*/), _resp_queue_size( + _resp_xport ? _resp_xport->get_num_recv_frames() : 15) + { + if (resp_xport) + { + while (resp_xport->get_recv_buff(0.0)) {} //flush + } + this->set_time(uhd::time_spec_t(0.0)); + this->set_tick_rate(1.0); //something possible but bogus + } + + ~b200_radio_ctrl_core_impl(void) + { + _timeout = ACK_TIMEOUT; //reset timeout to something small + UHD_SAFE_CALL( + this->peek32(0);//dummy peek with the purpose of ack'ing all packets + _async_task.reset();//now its ok to release the task + ) + } + + /******************************************************************* + * Peek and poke 32 bit implementation + ******************************************************************/ + void poke32(const wb_addr_type addr, const boost::uint32_t data) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(addr/4, data); + this->wait_for_ack(false); + } + + boost::uint32_t peek32(const wb_addr_type addr) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(SR_READBACK, addr/8); + const boost::uint64_t res = this->wait_for_ack(true); + const boost::uint32_t lo = boost::uint32_t(res & 0xffffffff); + const boost::uint32_t hi = boost::uint32_t(res >> 32); + return ((addr/4) & 0x1)? hi : lo; + } + + boost::uint64_t peek64(const wb_addr_type addr) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(SR_READBACK, addr/8); + return this->wait_for_ack(true); + } + + /******************************************************************* + * Update methods for time + ******************************************************************/ + void set_time(const uhd::time_spec_t &time) + { + boost::mutex::scoped_lock lock(_mutex); + _time = time; + _use_time = _time != uhd::time_spec_t(0.0); + if (_use_time) _timeout = MASSIVE_TIMEOUT; //permanently sets larger timeout + } + + uhd::time_spec_t get_time(void) + { + boost::mutex::scoped_lock lock(_mutex); + return _time; + } + + void set_tick_rate(const double rate) + { + boost::mutex::scoped_lock lock(_mutex); + _tick_rate = rate; + } + +private: + // This is the buffer type for messages in radio control core. + struct resp_buff_type + { + boost::uint32_t data[8]; + }; + + /******************************************************************* + * Primary control and interaction private methods + ******************************************************************/ + UHD_INLINE void send_pkt(const boost::uint32_t addr, const boost::uint32_t data = 0) + { + managed_send_buffer::sptr buff = _ctrl_xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("fifo ctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.link_type = _link_type; + packet_info.packet_type = _packet_type; + packet_info.num_payload_words32 = 2; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = _seq_out; + packet_info.tsf = _time.to_ticks(_tick_rate); + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = _sid; + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = _use_time; + packet_info.has_tlr = false; + + //load header + if (_bige) vrt::if_hdr_pack_be(pkt, packet_info); + else vrt::if_hdr_pack_le(pkt, packet_info); + + //load payload + pkt[packet_info.num_header_words32+0] = (_bige)? uhd::htonx(addr) : uhd::htowx(addr); + pkt[packet_info.num_header_words32+1] = (_bige)? uhd::htonx(data) : uhd::htowx(data); + //UHD_MSG(status) << boost::format("0x%08x, 0x%08x\n") % addr % data; + //send the buffer over the interface + _outstanding_seqs.push(_seq_out); + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); + + _seq_out++;//inc seq for next call + } + + UHD_INLINE boost::uint64_t wait_for_ack(const bool readback) + { + while (readback or (_outstanding_seqs.size() >= _resp_queue_size)) + { + //get seq to ack from outstanding packets list + UHD_ASSERT_THROW(not _outstanding_seqs.empty()); + const size_t seq_to_ack = _outstanding_seqs.front(); + _outstanding_seqs.pop(); + + //parse the packet + vrt::if_packet_info_t packet_info; + resp_buff_type resp_buff; + memset(&resp_buff, 0x00, sizeof(resp_buff)); + boost::uint32_t const *pkt = NULL; + managed_recv_buffer::sptr buff; + + //get buffer from response endpoint - or die in timeout + if (_resp_xport) + { + buff = _resp_xport->get_recv_buff(_timeout); + try + { + UHD_ASSERT_THROW(bool(buff)); + UHD_ASSERT_THROW(buff->size() > 0); + } + catch(const std::exception &ex) + { + throw uhd::io_error(str(boost::format("Radio ctrl (%s) no response packet - %s") % _name % ex.what())); + } + pkt = buff->cast<const boost::uint32_t *>(); + packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + } + + //get buffer from response endpoint - or die in timeout + else + { + /* + * Couldn't get message with haste. + * Now check both possible queues for messages. + * Messages should come in on _resp_queue, + * but could end up in dump_queue. + * If we don't get a message --> Die in timeout. + */ + double accum_timeout = 0.0; + const double short_timeout = 0.005; // == 5ms + while(not ((_resp_queue.pop_with_haste(resp_buff)) + || (check_dump_queue(resp_buff)) + || (_resp_queue.pop_with_timed_wait(resp_buff, short_timeout)) + )){ + /* + * If a message couldn't be received within a given timeout + * --> throw AssertionError! + */ + accum_timeout += short_timeout; + UHD_ASSERT_THROW(accum_timeout < _timeout); + } + + pkt = resp_buff.data; + packet_info.num_packet_words32 = sizeof(resp_buff)/sizeof(boost::uint32_t); + } + + //parse the buffer + try + { + packet_info.link_type = _link_type; + if (_bige) vrt::if_hdr_unpack_be(pkt, packet_info); + else vrt::if_hdr_unpack_le(pkt, packet_info); + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "Radio ctrl bad VITA packet: " << ex.what() << std::endl; + if (buff){ + UHD_VAR(buff->size()); + } + else{ + UHD_MSG(status) << "buff is NULL" << std::endl; + } + UHD_MSG(status) << std::hex << pkt[0] << std::dec << std::endl; + UHD_MSG(status) << std::hex << pkt[1] << std::dec << std::endl; + UHD_MSG(status) << std::hex << pkt[2] << std::dec << std::endl; + UHD_MSG(status) << std::hex << pkt[3] << std::dec << std::endl; + } + + //check the buffer + try + { + UHD_ASSERT_THROW(packet_info.has_sid); + UHD_ASSERT_THROW(packet_info.sid == boost::uint32_t((_sid >> 16) | (_sid << 16))); + UHD_ASSERT_THROW(packet_info.packet_count == (seq_to_ack & 0xfff)); + UHD_ASSERT_THROW(packet_info.num_payload_words32 == 2); + UHD_ASSERT_THROW(packet_info.packet_type == _packet_type); + } + catch(const std::exception &ex) + { + throw uhd::io_error(str(boost::format("Radio ctrl (%s) packet parse error - %s") % _name % ex.what())); + } + + //return the readback value + if (readback and _outstanding_seqs.empty()) + { + const boost::uint64_t hi = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+0]) : uhd::wtohx(pkt[packet_info.num_header_words32+0]); + const boost::uint64_t lo = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+1]) : uhd::wtohx(pkt[packet_info.num_header_words32+1]); + return ((hi << 32) | lo); + } + } + + return 0; + } + + /* + * If ctrl_core waits for a message that didn't arrive it can search for it in the dump queue. + * This actually happens during shutdown. + * handle_async_task can't access radio_ctrl_cores queue anymore thus it returns the corresponding message. + * msg_task class implements a dump_queue to store such messages. + * With check_dump_queue we can check if a message we are waiting for got stranded there. + * If a message got stuck we get it here and push it onto our own message_queue. + */ + bool check_dump_queue(resp_buff_type& b) { + const size_t min_buff_size = 8; // Same value as in b200_io_impl->handle_async_task + boost::uint32_t recv_sid = (((_sid)<<16)|((_sid)>>16)); + uhd::msg_task::msg_payload_t msg; + do{ + msg = _async_task->get_msg_from_dump_queue(recv_sid); + } + while(msg.size() < min_buff_size && msg.size() != 0); + + if(msg.size() >= min_buff_size) { + memcpy(b.data, &msg.front(), std::min(msg.size(), sizeof(b.data))); + return true; + } + return false; + } + + void push_response(const boost::uint32_t *buff) + { + resp_buff_type resp_buff; + std::memcpy(resp_buff.data, buff, sizeof(resp_buff)); + _resp_queue.push_with_haste(resp_buff); + } + + void hold_task(uhd::msg_task::sptr task) + { + _async_task = task; + } + + const vrt::if_packet_info_t::link_type_t _link_type; + const vrt::if_packet_info_t::packet_type_t _packet_type; + const bool _bige; + const uhd::transport::zero_copy_if::sptr _ctrl_xport; + const uhd::transport::zero_copy_if::sptr _resp_xport; + uhd::msg_task::sptr _async_task; + const boost::uint32_t _sid; + const std::string _name; + boost::mutex _mutex; + size_t _seq_out; + uhd::time_spec_t _time; + bool _use_time; + double _tick_rate; + double _timeout; + std::queue<size_t> _outstanding_seqs; + bounded_buffer<resp_buff_type> _resp_queue; + const size_t _resp_queue_size; +}; + +b200_radio_ctrl_core::sptr b200_radio_ctrl_core::make(const bool big_endian, + zero_copy_if::sptr ctrl_xport, zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, const std::string &name) +{ + return sptr( + new b200_radio_ctrl_core_impl(big_endian, ctrl_xport, resp_xport, + sid, name)); +} diff --git a/host/lib/usrp/b200/b200_radio_ctrl_core.hpp b/host/lib/usrp/b200/b200_radio_ctrl_core.hpp new file mode 100644 index 000000000..51f7e3301 --- /dev/null +++ b/host/lib/usrp/b200/b200_radio_ctrl_core.hpp @@ -0,0 +1,64 @@ +// +// Copyright 2012-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_B200_RADIO_CTRL_HPP +#define INCLUDED_LIBUHD_USRP_B200_RADIO_CTRL_HPP + +#include <uhd/utils/msg_task.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <string> + +/*! + * Provide access to peek, poke for the radio ctrl module + */ +class b200_radio_ctrl_core : public uhd::timed_wb_iface +{ +public: + typedef boost::shared_ptr<b200_radio_ctrl_core> sptr; + + virtual ~b200_radio_ctrl_core(void) = 0; + + //! Make a new control object + static sptr make( + const bool big_endian, + uhd::transport::zero_copy_if::sptr ctrl_xport, + uhd::transport::zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, + const std::string &name = "0" + ); + + //! Hold a ref to a task thats feeding push response + virtual void hold_task(uhd::msg_task::sptr task) = 0; + + //! Push a response externall (resp_xport is NULL) + virtual void push_response(const boost::uint32_t *buff) = 0; + + //! Set the command time that will activate + virtual void set_time(const uhd::time_spec_t &time) = 0; + + //! Get the command time that will activate + virtual uhd::time_spec_t get_time(void) = 0; + + //! Set the tick rate (converting time into ticks) + virtual void set_tick_rate(const double rate) = 0; +}; + +#endif /* INCLUDED_LIBUHD_USRP_B200_RADIO_CTRL_HPP */ diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 9dabc4e0b..9f4cf09b5 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -29,7 +29,8 @@ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver") LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/adf4001_ctrl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/adf435x_common.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf5355.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp @@ -37,4 +38,5 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/fifo_ctrl_excelsior.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrp3_fw_ctrl_iface.cpp ) diff --git a/host/lib/usrp/common/ad9361_ctrl.cpp b/host/lib/usrp/common/ad9361_ctrl.cpp index 54f0fcdbf..654311424 100644 --- a/host/lib/usrp/common/ad9361_ctrl.cpp +++ b/host/lib/usrp/common/ad9361_ctrl.cpp @@ -93,18 +93,30 @@ class ad9361_ctrl_impl : public ad9361_ctrl { public: ad9361_ctrl_impl(ad9361_params::sptr client_settings, ad9361_io::sptr io_iface): - _device(client_settings, io_iface) + _device(client_settings, io_iface), _safe_spi(io_iface), _timed_spi(io_iface) { _device.initialize(); } + void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) + { + _timed_spi = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); + _use_timed_spi(); + } + + void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) + { + _safe_spi = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); + } + double set_gain(const std::string &which, const double value) { boost::lock_guard<boost::mutex> lock(_mutex); ad9361_device_t::direction_t direction = _get_direction_from_antenna(which); ad9361_device_t::chain_t chain =_get_chain_from_antenna(which); - return _device.set_gain(direction, chain, value); + double return_val = _device.set_gain(direction, chain, value); + return return_val; } void set_agc(const std::string &which, bool enable) @@ -112,7 +124,7 @@ public: boost::lock_guard<boost::mutex> lock(_mutex); ad9361_device_t::chain_t chain =_get_chain_from_antenna(which); - _device.set_agc(chain, enable); + _device.set_agc(chain, enable); } void set_agc_mode(const std::string &which, const std::string &mode) @@ -133,6 +145,9 @@ public: { boost::lock_guard<boost::mutex> lock(_mutex); + // Changing clock rate will disrupt AD9361's sample clock + _use_safe_spi(); + //clip to known bounds const meta_range_t clock_rate_range = ad9361_ctrl::get_clock_rate_range(); const double clipped_rate = clock_rate_range.clip(rate); @@ -144,7 +159,11 @@ public: ) % (rate/1e6) % (clipped_rate/1e6) << std::endl; } - return _device.set_clock_rate(clipped_rate); + double return_rate = _device.set_clock_rate(clipped_rate); + + _use_timed_spi(); + + return return_rate; } //! set which RX and TX chains/antennas are active @@ -152,7 +171,11 @@ public: { boost::lock_guard<boost::mutex> lock(_mutex); + // If both RX chains are disabled then the AD9361's sample clock is disabled + _use_safe_spi(); _device.set_active_chains(tx1, tx2, rx1, rx2); + _use_timed_spi(); + } //! tune the given frontend, return the exact value @@ -166,7 +189,8 @@ public: const double value = ad9361_ctrl::get_rf_freq_range().clip(clipped_freq); ad9361_device_t::direction_t direction = _get_direction_from_antenna(which); - return _device.tune(direction, value); + double return_val = _device.tune(direction, value); + return return_val; } //! get the current frequency for the given frontend @@ -283,16 +307,28 @@ private: return ad9361_device_t::CHAIN_1; } - ad9361_device_t _device; - boost::mutex _mutex; + void _use_safe_spi() { + _device.set_io_iface(_safe_spi); + } + + void _use_timed_spi() { + _device.set_io_iface(_timed_spi); + } + + ad9361_device_t _device; + ad9361_io::sptr _safe_spi; // SPI core that uses an always available clock + ad9361_io::sptr _timed_spi; // SPI core that has a dependency on the AD9361's sample clock (i.e. radio clk) + boost::mutex _mutex; }; //---------------------------------------------------------------------- // Make an instance of the AD9361 Control interface //---------------------------------------------------------------------- ad9361_ctrl::sptr ad9361_ctrl::make_spi( - ad9361_params::sptr client_settings, uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) -{ + ad9361_params::sptr client_settings, + uhd::spi_iface::sptr spi_iface, + boost::uint32_t slave_num +) { boost::shared_ptr<ad9361_io_spi> spi_io_iface = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); return sptr(new ad9361_ctrl_impl(client_settings, spi_io_iface)); } diff --git a/host/lib/usrp/common/ad9361_ctrl.hpp b/host/lib/usrp/common/ad9361_ctrl.hpp index 5c438ee9c..5770c3ec4 100644 --- a/host/lib/usrp/common/ad9361_ctrl.hpp +++ b/host/lib/usrp/common/ad9361_ctrl.hpp @@ -56,7 +56,13 @@ public: //! make a new codec control object static sptr make_spi( - ad9361_params::sptr client_settings, uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num); + ad9361_params::sptr client_settings, + uhd::spi_iface::sptr spi_iface, + boost::uint32_t slave_num + ); + + virtual void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) = 0; + virtual void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) = 0; //! Get a list of gain names for RX or TX static std::vector<std::string> get_gain_names(const std::string &/*which*/) @@ -89,8 +95,10 @@ public: //! get the clock rate range for the frontend static uhd::meta_range_t get_clock_rate_range(void) { - //return uhd::meta_range_t(220e3, 61.44e6); - return uhd::meta_range_t(5e6, ad9361_device_t::AD9361_MAX_CLOCK_RATE); //5 MHz DCM low end + return uhd::meta_range_t( + ad9361_device_t::AD9361_MIN_CLOCK_RATE, + ad9361_device_t::AD9361_MAX_CLOCK_RATE + ); } //! set the filter bandwidth for the frontend's analog low pass diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp index 0a8a61575..095017bb6 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp @@ -91,6 +91,7 @@ int get_num_taps(int max_num_taps) { } const double ad9361_device_t::AD9361_MAX_GAIN = 89.75; +const double ad9361_device_t::AD9361_MIN_CLOCK_RATE = 220e3; const double ad9361_device_t::AD9361_MAX_CLOCK_RATE = 61.44e6; const double ad9361_device_t::AD9361_CAL_VALID_WINDOW = 100e6; // Max bandwdith is due to filter rolloff in analog filter stage @@ -770,7 +771,7 @@ void ad9361_device_t::_calibrate_rf_dc_offset() size_t count = 0; _io_iface->poke8(0x016, 0x02); while (_io_iface->peek8(0x016) & 0x02) { - if (count > 100) { + if (count > 200) { throw uhd::runtime_error("[ad9361_device_t] RF DC Offset Calibration Failure"); break; } @@ -821,7 +822,7 @@ void ad9361_device_t::_calibrate_rx_quadrature() size_t count = 0; _io_iface->poke8(0x016, 0x20); while (_io_iface->peek8(0x016) & 0x20) { - if (count > 100) { + if (count > 1000) { throw uhd::runtime_error("[ad9361_device_t] Rx Quadrature Calibration Failure"); break; } @@ -1564,6 +1565,12 @@ void ad9361_device_t::initialize() _io_iface->poke8(0x000, 0x00); boost::this_thread::sleep(boost::posix_time::milliseconds(20)); + /* Check device ID to make sure iface works */ + boost::uint32_t device_id = (_io_iface->peek8(0x037) & 0x8); + if (device_id != 0x8) { + throw uhd::runtime_error(str(boost::format("[ad9361_device_t::initialize] Device ID readback failure. Expected: 0x8, Received: 0x%x") % device_id)); + } + /* There is not a WAT big enough for this. */ _io_iface->poke8(0x3df, 0x01); @@ -1773,6 +1780,10 @@ void ad9361_device_t::initialize() _io_iface->poke8(0x014, 0x21); } +void ad9361_device_t::set_io_iface(ad9361_io::sptr io_iface) +{ + _io_iface = io_iface; +} /* This function sets the RX / TX rate between AD9361 and the FPGA, and * thus determines the interpolation / decimation required in the FPGA to diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.h b/host/lib/usrp/common/ad9361_driver/ad9361_device.h index 66bc2e8b9..d0e8a7e39 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.h +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.h @@ -75,6 +75,9 @@ public: /* Initialize the AD9361 codec. */ void initialize(); + /* Set SPI interface */ + void set_io_iface(ad9361_io::sptr io_iface); + /* This function sets the RX / TX rate between AD9361 and the FPGA, and * thus determines the interpolation / decimation required in the FPGA to * achieve the user's requested rate. @@ -157,6 +160,7 @@ public: //Constants static const double AD9361_MAX_GAIN; static const double AD9361_MAX_CLOCK_RATE; + static const double AD9361_MIN_CLOCK_RATE; static const double AD9361_CAL_VALID_WINDOW; static const double AD9361_RECOMMENDED_MAX_BANDWIDTH; static const double DEFAULT_RX_FREQ; diff --git a/host/lib/usrp/common/ad936x_manager.cpp b/host/lib/usrp/common/ad936x_manager.cpp index de8c4c7ab..e7af411fa 100644 --- a/host/lib/usrp/common/ad936x_manager.cpp +++ b/host/lib/usrp/common/ad936x_manager.cpp @@ -93,14 +93,12 @@ class ad936x_manager_impl : public ad936x_manager // worst case conditions to stress the interface. // void loopback_self_test( - wb_iface::sptr iface, - wb_iface::wb_addr_type codec_idle_addr, - wb_iface::wb_addr_type codec_readback_addr + boost::function<void(uint32_t)> poker_functor, + boost::function<uint64_t()> peeker_functor ) { // Put AD936x in loopback mode _codec_ctrl->data_port_loopback(true); UHD_MSG(status) << "Performing CODEC loopback test... " << std::flush; - UHD_ASSERT_THROW(bool(iface)); size_t hash = size_t(time(NULL)); // Allow some time for AD936x to enter loopback mode. @@ -118,10 +116,10 @@ class ad936x_manager_impl : public ad936x_manager const boost::uint32_t word32 = boost::uint32_t(hash) & 0xfff0fff0; // Write test word to codec_idle idle register (on TX side) - iface->poke32(codec_idle_addr, word32); + poker_functor(word32); // Read back values - TX is lower 32-bits and RX is upper 32-bits - const boost::uint64_t rb_word64 = iface->peek64(codec_readback_addr); + const boost::uint64_t rb_word64 = peeker_functor(); const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); @@ -136,7 +134,7 @@ class ad936x_manager_impl : public ad936x_manager UHD_MSG(status) << "pass" << std::endl; // Zero out the idle data. - iface->poke32(codec_idle_addr, 0); + poker_functor(0); // Take AD936x out of loopback mode _codec_ctrl->data_port_loopback(false); @@ -211,11 +209,11 @@ class ad936x_manager_impl : public ad936x_manager // Sensors subtree->create<sensor_value_t>("sensors/temp") - .publish(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl)) + .set_publisher(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl)) ; if (dir == RX_DIRECTION) { subtree->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key)) + .set_publisher(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key)) ; } @@ -226,7 +224,7 @@ class ad936x_manager_impl : public ad936x_manager .set(ad9361_ctrl::get_gain_range(key)); subtree->create<double>(uhd::fs_path("gains") / name / "value") .set(ad936x_manager::DEFAULT_GAIN) - .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) + .set_coercer(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) ; } @@ -238,19 +236,19 @@ class ad936x_manager_impl : public ad936x_manager // Analog Bandwidths subtree->create<double>("bandwidth/value") .set(ad936x_manager::DEFAULT_BANDWIDTH) - .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) + .set_coercer(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) ; subtree->create<meta_range_t>("bandwidth/range") - .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)) + .set_publisher(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)) ; // LO Tuning subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range)) + .set_publisher(boost::bind(&ad9361_ctrl::get_rf_freq_range)) ; subtree->create<double>("freq/value") - .publish(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key)) - .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) + .set_publisher(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key)) + .set_coercer(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) ; // Frontend corrections @@ -258,21 +256,21 @@ class ad936x_manager_impl : public ad936x_manager { subtree->create<bool>("dc_offset/enable" ) .set(ad936x_manager::DEFAULT_AUTO_DC_OFFSET) - .subscribe(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1)) ; subtree->create<bool>("iq_balance/enable" ) .set(ad936x_manager::DEFAULT_AUTO_IQ_BALANCE) - .subscribe(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1)) ; // AGC setup const std::list<std::string> mode_strings = boost::assign::list_of("slow")("fast"); subtree->create<bool>("gain/agc/enable") .set(DEFAULT_AGC_ENABLE) - .subscribe(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1)) ; subtree->create<std::string>("gain/agc/mode/value") - .subscribe(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front()) + .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front()) ; subtree->create< std::list<std::string> >("gain/agc/mode/options") .set(mode_strings) @@ -282,8 +280,8 @@ class ad936x_manager_impl : public ad936x_manager // Frontend filters BOOST_FOREACH(const std::string &filter_name, _codec_ctrl->get_filter_names(key)) { subtree->create<filter_info_base::sptr>(uhd::fs_path("filters") / filter_name / "value" ) - .publish(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name)) - .subscribe(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1)); + .set_publisher(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1)); } } diff --git a/host/lib/usrp/common/ad936x_manager.hpp b/host/lib/usrp/common/ad936x_manager.hpp index 9b4a351c6..c456715e3 100644 --- a/host/lib/usrp/common/ad936x_manager.hpp +++ b/host/lib/usrp/common/ad936x_manager.hpp @@ -80,9 +80,8 @@ public: * \throws a uhd::runtime_error if the loopback value didn't match. */ virtual void loopback_self_test( - wb_iface::sptr iface, - wb_iface::wb_addr_type codec_idle_addr, - wb_iface::wb_addr_type codec_readback_addr + boost::function<void(uint32_t)> poker_functor, + boost::function<uint64_t()> peeker_functor ) = 0; /*! Determine a tick rate that will work with a given sampling rate diff --git a/host/lib/usrp/common/adf435x.cpp b/host/lib/usrp/common/adf435x.cpp new file mode 100644 index 000000000..f1ba6ad05 --- /dev/null +++ b/host/lib/usrp/common/adf435x.cpp @@ -0,0 +1,34 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "adf435x.hpp" + +using namespace uhd; + +adf435x_iface::~adf435x_iface() +{ +} + +adf435x_iface::sptr adf435x_iface::make_adf4350(write_fn_t write) +{ + return sptr(new adf435x_impl<adf4350_regs_t>(write)); +} + +adf435x_iface::sptr adf435x_iface::make_adf4351(write_fn_t write) +{ + return sptr(new adf435x_impl<adf4351_regs_t>(write)); +} diff --git a/host/lib/usrp/common/adf435x.hpp b/host/lib/usrp/common/adf435x.hpp new file mode 100644 index 000000000..d08c6b9dd --- /dev/null +++ b/host/lib/usrp/common/adf435x.hpp @@ -0,0 +1,364 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_ADF435X_HPP +#define INCLUDED_ADF435X_HPP + +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/log.hpp> +#include <boost/function.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <vector> +#include "adf4350_regs.hpp" +#include "adf4351_regs.hpp" + +class adf435x_iface +{ +public: + typedef boost::shared_ptr<adf435x_iface> sptr; + typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t; + + static sptr make_adf4350(write_fn_t write); + static sptr make_adf4351(write_fn_t write); + + virtual ~adf435x_iface() = 0; + + enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; + + enum prescaler_t { PRESCALER_4_5, PRESCALER_8_9 }; + + enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; + + enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; + + enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD }; + + virtual void set_reference_freq(double fref) = 0; + + virtual void set_prescaler(prescaler_t prescaler) = 0; + + virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; + + virtual void set_output_power(output_power_t power) = 0; + + virtual void set_output_enable(output_t output, bool enable) = 0; + + virtual void set_muxout_mode(muxout_t mode) = 0; + + virtual uhd::range_t get_int_range() = 0; + + virtual double set_frequency(double target_freq, bool int_n_mode, bool flush = false) = 0; + + virtual void commit(void) = 0; +}; + +template <typename adf435x_regs_t> +class adf435x_impl : public adf435x_iface +{ +public: + adf435x_impl(write_fn_t write_fn) : + _write_fn(write_fn), + _regs(), + _fb_after_divider(false), + _reference_freq(0.0), + _N_min(-1) + {} + + virtual ~adf435x_impl() {}; + + void set_reference_freq(double fref) + { + _reference_freq = fref; + } + + void set_feedback_select(feedback_sel_t fb_sel) + { + _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); + } + + void set_prescaler(prescaler_t prescaler) + { + if (prescaler == PRESCALER_8_9) { + _regs.prescaler = adf435x_regs_t::PRESCALER_8_9; + _N_min = 75; + } else { + _regs.prescaler = adf435x_regs_t::PRESCALER_4_5; + _N_min = 23; + } + } + + void set_output_power(output_power_t power) + { + switch (power) { + case OUTPUT_POWER_M4DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M4DBM; break; + case OUTPUT_POWER_M1DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M1DBM; break; + case OUTPUT_POWER_2DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_2DBM; break; + case OUTPUT_POWER_5DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_5DBM; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + void set_output_enable(output_t output, bool enable) + { + switch (output) { + case RF_OUTPUT_A: _regs.rf_output_enable = enable ? adf435x_regs_t::RF_OUTPUT_ENABLE_ENABLED: + adf435x_regs_t::RF_OUTPUT_ENABLE_DISABLED; + break; + case RF_OUTPUT_B: _regs.aux_output_enable = enable ? adf435x_regs_t::AUX_OUTPUT_ENABLE_ENABLED: + adf435x_regs_t::AUX_OUTPUT_ENABLE_DISABLED; + break; + } + } + + void set_muxout_mode(muxout_t mode) + { + switch (mode) { + case MUXOUT_3STATE: _regs.muxout = adf435x_regs_t::MUXOUT_3STATE; break; + case MUXOUT_DVDD: _regs.muxout = adf435x_regs_t::MUXOUT_DVDD; break; + case MUXOUT_DGND: _regs.muxout = adf435x_regs_t::MUXOUT_DGND; break; + case MUXOUT_RDIV: _regs.muxout = adf435x_regs_t::MUXOUT_RDIV; break; + case MUXOUT_NDIV: _regs.muxout = adf435x_regs_t::MUXOUT_NDIV; break; + case MUXOUT_ALD: _regs.muxout = adf435x_regs_t::MUXOUT_ANALOG_LD; break; + case MUXOUT_DLD: _regs.muxout = adf435x_regs_t::MUXOUT_DLD; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + uhd::range_t get_int_range() + { + if (_N_min < 0) throw uhd::runtime_error("set_prescaler must be called before get_int_range"); + return uhd::range_t(_N_min, 4095); + } + + double set_frequency(double target_freq, bool int_n_mode, bool flush = false) + { + static const double REF_DOUBLER_THRESH_FREQ = 12.5e6; + static const double PFD_FREQ_MAX = 25.0e6; + static const double BAND_SEL_FREQ_MAX = 100e3; + static const double VCO_FREQ_MIN = 2.2e9; + static const double VCO_FREQ_MAX = 4.4e9; + + //Default invalid value for actual_freq + double actual_freq = 0; + + uhd::range_t rf_divider_range = _get_rfdiv_range(); + uhd::range_t int_range = get_int_range(); + + double pfd_freq = 0; + boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0; + boost::uint16_t RFdiv = static_cast<boost::uint16_t>(rf_divider_range.start()); + bool D = false, T = false; + + //Reference doubler for 50% duty cycle + D = (_reference_freq <= REF_DOUBLER_THRESH_FREQ); + + //increase RF divider until acceptable VCO frequency + double vco_freq = target_freq; + while (vco_freq < VCO_FREQ_MIN && RFdiv < static_cast<boost::uint16_t>(rf_divider_range.stop())) { + vco_freq *= 2; + RFdiv *= 2; + } + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exits when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_actual = f_vco/RFdiv) + */ + double feedback_freq = _fb_after_divider ? target_freq : vco_freq; + + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = _reference_freq*(D?2:1)/(R*(T?2:1)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > PFD_FREQ_MAX) continue; + + //First, ignore fractional part of tuning + N = boost::uint16_t(std::floor(feedback_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < static_cast<boost::uint16_t>(int_range.start())) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below band_sel_freq_max + //constraint on band select clock + if (pfd_freq/BS > BAND_SEL_FREQ_MAX) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD)); + if (int_n_mode) { + if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target + N++; + } + FRAC = 0; + } + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0) { + T = true; + R /= 2; + } + + //Typical phase resync time documented in data sheet pg.24 + static const double PHASE_RESYNC_TIME = 400e-6; + + //If feedback after divider, then compensation for the divider is pulled into the INT value + int rf_div_compensation = _fb_after_divider ? 1 : RFdiv; + + //Compute the actual frequency in terms of _reference_freq, N, FRAC, MOD, D, R and T. + actual_freq = ( + double((N + (double(FRAC)/double(MOD))) * + (_reference_freq*(D?2:1)/(R*(T?2:1)))) + ) / rf_div_compensation; + + _regs.frac_12_bit = FRAC; + _regs.int_16_bit = N; + _regs.mod_12_bit = MOD; + _regs.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD))); + _regs.feedback_select = _fb_after_divider ? + adf435x_regs_t::FEEDBACK_SELECT_DIVIDED : + adf435x_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + _regs.clock_div_mode = _fb_after_divider ? + adf435x_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : + adf435x_regs_t::CLOCK_DIV_MODE_FAST_LOCK; + _regs.r_counter_10_bit = R; + _regs.reference_divide_by_2 = T ? + adf435x_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + adf435x_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = D ? + adf435x_regs_t::REFERENCE_DOUBLER_ENABLED : + adf435x_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.band_select_clock_div = boost::uint8_t(BS); + _regs.rf_divider_select = static_cast<typename adf435x_regs_t::rf_divider_select_t>(_get_rfdiv_setting(RFdiv)); + _regs.ldf = int_n_mode ? + adf435x_regs_t::LDF_INT_N : + adf435x_regs_t::LDF_FRAC_N; + + std::string tuning_str = (int_n_mode) ? "Integer-N" : "Fractional"; + UHD_LOGV(often) + << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f" + ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl + << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" + ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (_reference_freq/1e6) << std::endl + << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl + << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl; + + UHD_ASSERT_THROW((_regs.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); + + UHD_ASSERT_THROW(vco_freq >= VCO_FREQ_MIN and vco_freq <= VCO_FREQ_MAX); + UHD_ASSERT_THROW(RFdiv >= static_cast<boost::uint16_t>(rf_divider_range.start())); + UHD_ASSERT_THROW(RFdiv <= static_cast<boost::uint16_t>(rf_divider_range.stop())); + UHD_ASSERT_THROW(_regs.int_16_bit >= static_cast<boost::uint16_t>(int_range.start())); + UHD_ASSERT_THROW(_regs.int_16_bit <= static_cast<boost::uint16_t>(int_range.stop())); + + if (flush) commit(); + return actual_freq; + } + + void commit() + { + //reset counters + _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_ENABLED; + std::vector<boost::uint32_t> regs; + regs.push_back(_regs.get_reg(boost::uint32_t(2))); + _write_fn(regs); + _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_DISABLED; + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + regs.clear(); + for (int addr = 5; addr >= 0; addr--) { + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + _write_fn(regs); + } + +protected: + uhd::range_t _get_rfdiv_range(); + int _get_rfdiv_setting(boost::uint16_t div); + + write_fn_t _write_fn; + adf435x_regs_t _regs; + double _fb_after_divider; + double _reference_freq; + int _N_min; +}; + +template <> +inline uhd::range_t adf435x_impl<adf4350_regs_t>::_get_rfdiv_range() +{ + return uhd::range_t(1, 16); +} + +template <> +inline uhd::range_t adf435x_impl<adf4351_regs_t>::_get_rfdiv_range() +{ + return uhd::range_t(1, 64); +} + +template <> +inline int adf435x_impl<adf4350_regs_t>::_get_rfdiv_setting(boost::uint16_t div) +{ + switch (div) { + case 1: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV1); + case 2: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV2); + case 4: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV4); + case 8: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV8); + case 16: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV16); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <> +inline int adf435x_impl<adf4351_regs_t>::_get_rfdiv_setting(boost::uint16_t div) +{ + switch (div) { + case 1: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV1); + case 2: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV2); + case 4: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV4); + case 8: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV8); + case 16: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV16); + case 32: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV32); + case 64: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV64); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +#endif // INCLUDED_ADF435X_HPP diff --git a/host/lib/usrp/common/adf435x_common.cpp b/host/lib/usrp/common/adf435x_common.cpp deleted file mode 100644 index 474a1c932..000000000 --- a/host/lib/usrp/common/adf435x_common.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// -// Copyright 2013-2014 Ettus Research LLC -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -// - -#include "adf435x_common.hpp" - -#include <boost/math/special_functions/round.hpp> -#include <uhd/types/tune_request.hpp> -#include <uhd/utils/log.hpp> -#include <cmath> - - -using namespace uhd; - -/*********************************************************************** - * ADF 4350/4351 Tuning Utility - **********************************************************************/ -adf435x_tuning_settings tune_adf435x_synth( - const double target_freq, - const double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq) -{ - //Default invalid value for actual_freq - actual_freq = 0; - - double pfd_freq = 0; - boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0; - boost::uint16_t RFdiv = static_cast<boost::uint16_t>(constraints.rf_divider_range.start()); - bool D = false, T = false; - - //Reference doubler for 50% duty cycle - //If ref_freq < 12.5MHz enable the reference doubler - D = (ref_freq <= constraints.ref_doubler_threshold); - - static const double MIN_VCO_FREQ = 2.2e9; - static const double MAX_VCO_FREQ = 4.4e9; - - //increase RF divider until acceptable VCO frequency - double vco_freq = target_freq; - while (vco_freq < MIN_VCO_FREQ && RFdiv < static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())) { - vco_freq *= 2; - RFdiv *= 2; - } - - /* - * The goal here is to loop though possible R dividers, - * band select clock dividers, N (int) dividers, and FRAC - * (frac) dividers. - * - * Calculate the N and F dividers for each set of values. - * The loop exits when it meets all of the constraints. - * The resulting loop values are loaded into the registers. - * - * from pg.21 - * - * f_pfd = f_ref*(1+D)/(R*(1+T)) - * f_vco = (N + (FRAC/MOD))*f_pfd - * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD - * f_actual = f_vco/RFdiv) - */ - double feedback_freq = constraints.feedback_after_divider ? target_freq : vco_freq; - - for(R = 1; R <= 1023; R+=1){ - //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) - pfd_freq = ref_freq*(D?2:1)/(R*(T?2:1)); - - //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) - if (pfd_freq > constraints.pfd_freq_max) continue; - - //First, ignore fractional part of tuning - N = boost::uint16_t(std::floor(feedback_freq/pfd_freq)); - - //keep N > minimum int divider requirement - if (N < static_cast<boost::uint16_t>(constraints.int_range.start())) continue; - - for(BS=1; BS <= 255; BS+=1){ - //keep the band select frequency at or below band_sel_freq_max - //constraint on band select clock - if (pfd_freq/BS > constraints.band_sel_freq_max) continue; - goto done_loop; - } - } done_loop: - - //Fractional-N calculation - MOD = 4095; //max fractional accuracy - FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD)); - if (constraints.force_frac0) { - if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target - N++; - } - FRAC = 0; - } - - //Reference divide-by-2 for 50% duty cycle - // if R even, move one divide by 2 to to regs.reference_divide_by_2 - if(R % 2 == 0) { - T = true; - R /= 2; - } - - //Typical phase resync time documented in data sheet pg.24 - static const double PHASE_RESYNC_TIME = 400e-6; - - //If feedback after divider, then compensation for the divider is pulled into the INT value - int rf_div_compensation = constraints.feedback_after_divider ? 1 : RFdiv; - - //Compute the actual frequency in terms of ref_freq, N, FRAC, MOD, D, R and T. - actual_freq = ( - double((N + (double(FRAC)/double(MOD))) * - (ref_freq*(D?2:1)/(R*(T?2:1)))) - ) / rf_div_compensation; - - //load the settings - adf435x_tuning_settings settings; - settings.frac_12_bit = FRAC; - settings.int_16_bit = N; - settings.mod_12_bit = MOD; - settings.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD))); - settings.r_counter_10_bit = R; - settings.r_divide_by_2_en = T; - settings.r_doubler_en = D; - settings.band_select_clock_div = boost::uint8_t(BS); - settings.rf_divider = RFdiv; - - std::string tuning_str = (constraints.force_frac0) ? "Integer-N" : "Fractional"; - UHD_LOGV(often) - << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f" - ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl - << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" - ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (ref_freq/1e6) << std::endl - << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl - << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d" - ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl; - - UHD_ASSERT_THROW((settings.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); - - UHD_ASSERT_THROW(vco_freq >= MIN_VCO_FREQ and vco_freq <= MAX_VCO_FREQ); - UHD_ASSERT_THROW(settings.rf_divider >= static_cast<boost::uint16_t>(constraints.rf_divider_range.start())); - UHD_ASSERT_THROW(settings.rf_divider <= static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())); - UHD_ASSERT_THROW(settings.int_16_bit >= static_cast<boost::uint16_t>(constraints.int_range.start())); - UHD_ASSERT_THROW(settings.int_16_bit <= static_cast<boost::uint16_t>(constraints.int_range.stop())); - - return settings; -} diff --git a/host/lib/usrp/common/adf435x_common.hpp b/host/lib/usrp/common/adf435x_common.hpp deleted file mode 100644 index 617b9d97f..000000000 --- a/host/lib/usrp/common/adf435x_common.hpp +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright 2014 Ettus Research LLC -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -// - -#ifndef INCLUDED_ADF435X_COMMON_HPP -#define INCLUDED_ADF435X_COMMON_HPP - -#include <boost/cstdint.hpp> -#include <uhd/property_tree.hpp> -#include <uhd/types/ranges.hpp> - -//Common IO Pins -#define ADF435X_CE (1 << 3) -#define ADF435X_PDBRF (1 << 2) -#define ADF435X_MUXOUT (1 << 1) // INPUT!!! -#define LOCKDET_MASK (1 << 0) // INPUT!!! - -#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control -#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control - -struct adf435x_tuning_constraints { - bool force_frac0; - bool feedback_after_divider; - double ref_doubler_threshold; - double pfd_freq_max; - double band_sel_freq_max; - uhd::range_t rf_divider_range; - uhd::range_t int_range; -}; - -struct adf435x_tuning_settings { - boost::uint16_t frac_12_bit; - boost::uint16_t int_16_bit; - boost::uint16_t mod_12_bit; - boost::uint16_t r_counter_10_bit; - bool r_doubler_en; - bool r_divide_by_2_en; - boost::uint16_t clock_divider_12_bit; - boost::uint8_t band_select_clock_div; - boost::uint16_t rf_divider; -}; - -adf435x_tuning_settings tune_adf435x_synth( - const double target_freq, - const double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq -); - -#endif /* INCLUDED_ADF435X_COMMON_HPP */ diff --git a/host/lib/usrp/common/adf5355.cpp b/host/lib/usrp/common/adf5355.cpp new file mode 100644 index 000000000..bb0906724 --- /dev/null +++ b/host/lib/usrp/common/adf5355.cpp @@ -0,0 +1,376 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "adf5355.hpp" +#include "adf5355_regs.hpp" +#include <uhd/utils/math.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <boost/thread.hpp> + +using namespace uhd; + +template<typename data_t> +data_t clamp(data_t val, data_t min, data_t max) { + return (val < min) ? min : ((val > max) ? max : val); +} + +template<typename data_t> +double todbl(data_t val) { + return static_cast<double>(val); +} + +static const double ADF5355_DOUBLER_MAX_REF_FREQ = 60e6; +static const double ADF5355_MAX_FREQ_PFD = 125e6; +static const double ADF5355_PRESCALER_THRESH = 7e9; + +static const double ADF5355_MIN_VCO_FREQ = 3.4e9; +static const double ADF5355_MAX_VCO_FREQ = 6.8e9; +static const double ADF5355_MAX_OUT_FREQ = 6.8e9; +static const double ADF5355_MIN_OUT_FREQ = (3.4e9 / 64); +static const double ADF5355_MAX_OUTB_FREQ = (6.8e9 * 2); +static const double ADF5355_MIN_OUTB_FREQ = (3.4e9 * 2); + +static const double ADF5355_PHASE_RESYNC_TIME = 400e-6; + +static const boost::uint32_t ADF5355_MOD1 = 16777216; +static const boost::uint32_t ADF5355_MAX_MOD2 = 16384; +static const boost::uint16_t ADF5355_MIN_INT_PRESCALER_89 = 75; + +class adf5355_impl : public adf5355_iface +{ +public: + adf5355_impl(write_fn_t write_fn) : + _write_fn(write_fn), + _regs(), + _rewrite_regs(true), + _wait_time_us(0), + _ref_freq(0.0), + _pfd_freq(0.0), + _fb_after_divider(false) + { + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED; + _regs.cp_three_state = adf5355_regs_t::CP_THREE_STATE_DISABLED; + _regs.power_down = adf5355_regs_t::POWER_DOWN_DISABLED; + _regs.pd_polarity = adf5355_regs_t::PD_POLARITY_POSITIVE; + _regs.mux_logic = adf5355_regs_t::MUX_LOGIC_3_3V; + _regs.ref_mode = adf5355_regs_t::REF_MODE_SINGLE; + _regs.muxout = adf5355_regs_t::MUXOUT_DLD; + _regs.double_buff_div = adf5355_regs_t::DOUBLE_BUFF_DIV_DISABLED; + + _regs.rf_out_a_enabled = adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED; + _regs.rf_out_b_enabled = adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED; + _regs.mute_till_lock_detect = adf5355_regs_t::MUTE_TILL_LOCK_DETECT_MUTE_DISABLED; + _regs.ld_mode = adf5355_regs_t::LD_MODE_FRAC_N; + _regs.frac_n_ld_precision = adf5355_regs_t::FRAC_N_LD_PRECISION_5NS; + _regs.ld_cyc_count = adf5355_regs_t::LD_CYC_COUNT_1024; + _regs.le_sync = adf5355_regs_t::LE_SYNC_LE_SYNCED_TO_REFIN; + _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; + _regs.reference_divide_by_2 = adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = adf5355_regs_t::REFERENCE_DOUBLER_DISABLED; + + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED; + _regs.prescaler = adf5355_regs_t::PRESCALER_4_5; + _regs.charge_pump_current = adf5355_regs_t::CHARGE_PUMP_CURRENT_0_94MA; + + _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED; + _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED; + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + _regs.output_power = adf5355_regs_t::OUTPUT_POWER_5DBM; + _regs.cp_bleed_current = 2; + _regs.r_counter_10_bit = 8; + + + _regs.ld_cyc_count = adf5355_regs_t::LD_CYC_COUNT_1024; + _regs.loss_of_lock_mode = adf5355_regs_t::LOSS_OF_LOCK_MODE_DISABLED; + _regs.frac_n_ld_precision = adf5355_regs_t::FRAC_N_LD_PRECISION_5NS; + _regs.ld_mode = adf5355_regs_t::LD_MODE_FRAC_N; + + _regs.vco_band_div = 3; + _regs.timeout = 11; + _regs.auto_level_timeout = 30; + _regs.synth_lock_timeout = 12; + + _regs.adc_clock_divider = 16; + _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED; + _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED; + + _regs.phase_resync_clk_div = 0; + } + + ~adf5355_impl() + { + _regs.power_down = adf5355_regs_t::POWER_DOWN_ENABLED; + commit(); + } + + void set_feedback_select(feedback_sel_t fb_sel) + { + _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); + + if (_fb_after_divider) { + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_DIVIDED; + } else { + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + } + } + + void set_reference_freq(double fref, bool force = false) + { + //Skip the body if the reference frequency does not change + if (uhd::math::frequencies_are_equal(fref, _ref_freq) and (not force)) + return; + + _ref_freq = fref; + + //----------------------------------------------------------- + //Set reference settings + + //Reference doubler for 50% duty cycle + bool doubler_en = (_ref_freq <= ADF5355_DOUBLER_MAX_REF_FREQ); + + /* Calculate and maximize PFD frequency */ + // TODO Target PFD should be configurable + /* TwinRX requires PFD of 6.25 MHz or less */ + const double TWINRX_PFD_FREQ = 6.25e6; + _pfd_freq = TWINRX_PFD_FREQ; + + int ref_div_factor = 16; + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + bool div2_en = (ref_div_factor % 2 == 0); + if (div2_en) { + ref_div_factor /= 2; + } + + _regs.reference_divide_by_2 = div2_en ? + adf5355_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = doubler_en ? + adf5355_regs_t::REFERENCE_DOUBLER_ENABLED : + adf5355_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.r_counter_10_bit = ref_div_factor; + UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); + + //----------------------------------------------------------- + //Set timeouts (code from ADI driver) + _regs.timeout = clamp<boost::uint16_t>( + static_cast<boost::uint16_t>(ceil(_pfd_freq / (20e3 * 30))), 1, 1023); + UHD_ASSERT_THROW((_regs.timeout & ((boost::uint16_t)~0x3FF)) == 0); + _regs.synth_lock_timeout = + static_cast<boost::uint8_t>(ceil((_pfd_freq * 2) / (100e3 * _regs.timeout))); + UHD_ASSERT_THROW((_regs.synth_lock_timeout & ((boost::uint16_t)~0x1F)) == 0); + _regs.auto_level_timeout = + static_cast<boost::uint8_t>(ceil((_pfd_freq * 5) / (100e3 * _regs.timeout))); + + //----------------------------------------------------------- + //Set VCO band divider + _regs.vco_band_div = + static_cast<boost::uint8_t>(ceil(_pfd_freq / 2.4e6)); + + //----------------------------------------------------------- + //Set ADC delay (code from ADI driver) + _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED; + _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED; + _regs.adc_clock_divider = clamp<boost::uint8_t>( + static_cast<boost::uint8_t>(ceil(((_pfd_freq / 100e3) - 2) / 4)), 1, 255); + _wait_time_us = static_cast<boost::uint32_t>( + ceil(16e6 / (_pfd_freq / ((4 * _regs.adc_clock_divider) + 2)))); + + //----------------------------------------------------------- + //Phase resync + _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; // Disabled during development + _regs.phase_adjust = adf5355_regs_t::PHASE_ADJUST_DISABLED; + _regs.sd_load_reset = adf5355_regs_t::SD_LOAD_RESET_ON_REG0_UPDATE; + _regs.phase_resync_clk_div = static_cast<boost::uint16_t>( + floor(ADF5355_PHASE_RESYNC_TIME * _pfd_freq)); + + _rewrite_regs = true; + } + + void set_output_power(output_power_t power) + { + adf5355_regs_t::output_power_t setting; + switch (power) { + case OUTPUT_POWER_M4DBM: setting = adf5355_regs_t::OUTPUT_POWER_M4DBM; break; + case OUTPUT_POWER_M1DBM: setting = adf5355_regs_t::OUTPUT_POWER_M1DBM; break; + case OUTPUT_POWER_2DBM: setting = adf5355_regs_t::OUTPUT_POWER_2DBM; break; + case OUTPUT_POWER_5DBM: setting = adf5355_regs_t::OUTPUT_POWER_5DBM; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + if (_regs.output_power != setting) _rewrite_regs = true; + _regs.output_power = setting; + } + + void set_output_enable(output_t output, bool enable) { + + switch (output) { + case RF_OUTPUT_A: _regs.rf_out_a_enabled = enable ? adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED : + adf5355_regs_t::RF_OUT_A_ENABLED_DISABLED; + break; + case RF_OUTPUT_B: _regs.rf_out_b_enabled = enable ? adf5355_regs_t::RF_OUT_B_ENABLED_ENABLED : + adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED; + break; + } + } + + void set_muxout_mode(muxout_t mode) + { + switch (mode) { + case MUXOUT_3STATE: _regs.muxout = adf5355_regs_t::MUXOUT_3STATE; break; + case MUXOUT_DVDD: _regs.muxout = adf5355_regs_t::MUXOUT_DVDD; break; + case MUXOUT_DGND: _regs.muxout = adf5355_regs_t::MUXOUT_DGND; break; + case MUXOUT_RDIV: _regs.muxout = adf5355_regs_t::MUXOUT_RDIV; break; + case MUXOUT_NDIV: _regs.muxout = adf5355_regs_t::MUXOUT_NDIV; break; + case MUXOUT_ALD: _regs.muxout = adf5355_regs_t::MUXOUT_ANALOG_LD; break; + case MUXOUT_DLD: _regs.muxout = adf5355_regs_t::MUXOUT_DLD; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + double set_frequency(double target_freq, double freq_resolution, bool flush = false) + { + if (target_freq > ADF5355_MAX_OUT_FREQ or target_freq < ADF5355_MIN_OUT_FREQ) { + throw uhd::runtime_error("requested frequency out of range."); + } + if ((boost::uint32_t) freq_resolution == 0) { + throw uhd::runtime_error("requested resolution cannot be less than 1."); + } + + /* Calculate target VCOout frequency */ + //Increase RF divider until acceptable VCO frequency + double target_vco_freq = target_freq; + boost::uint32_t rf_divider = 1; + while (target_vco_freq < ADF5355_MIN_VCO_FREQ && rf_divider < 64) { + target_vco_freq *= 2; + rf_divider *= 2; + } + + switch (rf_divider) { + case 1: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV1; break; + case 2: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV2; break; + case 4: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV4; break; + case 8: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV8; break; + case 16: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV16; break; + case 32: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV32; break; + case 64: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV64; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + + //Compute fractional PLL params + double prescaler_input_freq = target_vco_freq; + if (_fb_after_divider) { + prescaler_input_freq /= rf_divider; + } + + double N = prescaler_input_freq / _pfd_freq; + boost::uint16_t INT = static_cast<boost::uint16_t>(floor(N)); + boost::uint32_t FRAC1 = static_cast<boost::uint32_t>(floor((N - INT) * ADF5355_MOD1)); + double residue = ADF5355_MOD1 * (N - (INT + FRAC1 / ADF5355_MOD1)); + + double gcd = boost::math::gcd(static_cast<int>(_pfd_freq), static_cast<int>(freq_resolution)); + boost::uint16_t MOD2 = static_cast<boost::uint16_t>(floor(_pfd_freq / gcd)); + + if (MOD2 > ADF5355_MAX_MOD2) { + MOD2 = ADF5355_MAX_MOD2; + } + boost::uint16_t FRAC2 = ceil(residue * MOD2); + + double coerced_vco_freq = _pfd_freq * ( + todbl(INT) + ( + (todbl(FRAC1) + + (todbl(FRAC2) / todbl(MOD2))) + / todbl(ADF5355_MOD1) + ) + ); + + double coerced_out_freq = coerced_vco_freq / rf_divider; + + /* Update registers */ + _regs.int_16_bit = INT; + _regs.frac1_24_bit = FRAC1; + _regs.frac2_14_bit = FRAC2; + _regs.mod2_14_bit = MOD2; + _regs.phase_24_bit = 0; + +/* + if (_regs.int_16_bit >= ADF5355_MIN_INT_PRESCALER_89) { + _regs.prescaler = adf5355_regs_t::PRESCALER_8_9; + } else { + _regs.prescaler = adf5355_regs_t::PRESCALER_4_5; + } + + // ADI: Tests have shown that the optimal bleed set is the following: + // 4/N < IBLEED/ICP < 10/N */ +/* + boost::uint32_t cp_curr_ua = + (static_cast<boost::uint32_t>(_regs.charge_pump_current) + 1) * 315; + _regs.cp_bleed_current = clamp<boost::uint8_t>( + ceil((todbl(400)*cp_curr_ua) / (_regs.int_16_bit*375)), 1, 255); + _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED; + _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED; +*/ + + if (flush) commit(); + return coerced_out_freq; + } + + void commit() + { + if (_rewrite_regs) { + //For a full state sync write registers in reverse order 12 - 0 + addr_vtr_t regs; + for (int addr = 12; addr >= 0; addr--) { + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + _write_fn(regs); + _rewrite_regs = false; + + } else { + //Frequency update sequence from data sheet + static const size_t ONE_REG = 1; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(6))); + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_ENABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(2))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(1))); + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_DISABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); + boost::this_thread::sleep(boost::posix_time::microsec(_wait_time_us)); + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + } + } + +private: //Members + typedef std::vector<boost::uint32_t> addr_vtr_t; + + write_fn_t _write_fn; + adf5355_regs_t _regs; + bool _rewrite_regs; + boost::uint32_t _wait_time_us; + double _ref_freq; + double _pfd_freq; + double _fb_after_divider; +}; + +adf5355_iface::sptr adf5355_iface::make(write_fn_t write) +{ + return sptr(new adf5355_impl(write)); +} diff --git a/host/lib/usrp/common/adf5355.hpp b/host/lib/usrp/common/adf5355.hpp new file mode 100644 index 000000000..fb262cc9f --- /dev/null +++ b/host/lib/usrp/common/adf5355.hpp @@ -0,0 +1,57 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_ADF5355_HPP +#define INCLUDED_ADF5355_HPP + +#include <boost/function.hpp> +#include <vector> + +class adf5355_iface +{ +public: + typedef boost::shared_ptr<adf5355_iface> sptr; + typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t; + + static sptr make(write_fn_t write); + + virtual ~adf5355_iface() {} + + enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; + + enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; + + enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; + + enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD }; + + virtual void set_reference_freq(double fref, bool force = false) = 0; + + virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; + + virtual void set_output_power(output_power_t power) = 0; + + virtual void set_output_enable(output_t output, bool enable) = 0; + + virtual void set_muxout_mode(muxout_t mode) = 0; + + virtual double set_frequency(double target_freq, double freq_resolution, bool flush = false) = 0; + + virtual void commit(void) = 0; +}; + +#endif // INCLUDED_ADF5355_HPP diff --git a/host/lib/usrp/common/apply_corrections.cpp b/host/lib/usrp/common/apply_corrections.cpp index 3d33e7d11..272e0e093 100644 --- a/host/lib/usrp/common/apply_corrections.cpp +++ b/host/lib/usrp/common/apply_corrections.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -144,6 +144,34 @@ static void apply_fe_corrections( /*********************************************************************** * Wrapper routines with nice try/catch + print **********************************************************************/ +void uhd::usrp::apply_tx_fe_corrections( //overloading to work according to rfnoc tree struct + property_tree::sptr sub_tree, //starts at mboards/x + const uhd::fs_path db_path, + const uhd::fs_path tx_fe_corr_path, + const double lo_freq //actual lo freq +){ + boost::mutex::scoped_lock l(corrections_mutex); + try{ + apply_fe_corrections( + sub_tree, + db_path + "/tx_eeprom", + tx_fe_corr_path + "/iq_balance/value", + "tx_iq_cal_v0.2_", + lo_freq + ); + apply_fe_corrections( + sub_tree, + db_path + "/tx_eeprom", + tx_fe_corr_path + "/dc_offset/value", + "tx_dc_cal_v0.2_", + lo_freq + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl; + } +} + void uhd::usrp::apply_tx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot @@ -171,6 +199,27 @@ void uhd::usrp::apply_tx_fe_corrections( } } +void uhd::usrp::apply_rx_fe_corrections( //overloading to work according to rfnoc tree struct + property_tree::sptr sub_tree, //starts at mboards/x + const uhd::fs_path db_path, + const uhd::fs_path rx_fe_corr_path, + const double lo_freq //actual lo freq +){ + boost::mutex::scoped_lock l(corrections_mutex); + try{ + apply_fe_corrections( + sub_tree, + db_path + "/rx_eeprom", + rx_fe_corr_path + "/iq_balance/value", + "rx_iq_cal_v0.2_", + lo_freq + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl; + } +} + void uhd::usrp::apply_rx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot diff --git a/host/lib/usrp/common/apply_corrections.hpp b/host/lib/usrp/common/apply_corrections.hpp index c516862d1..0ab5377f3 100644 --- a/host/lib/usrp/common/apply_corrections.hpp +++ b/host/lib/usrp/common/apply_corrections.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -26,16 +26,28 @@ namespace uhd{ namespace usrp{ void apply_tx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x - const std::string &slot, //name of dboard slot + const fs_path db_path, + const fs_path tx_fe_corr_path, const double tx_lo_freq //actual lo freq ); + void apply_tx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const std::string &slot, //name of dboard slot + const double tx_lo_freq //actual lo freq + ); void apply_rx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot const double rx_lo_freq //actual lo freq ); + void apply_rx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const fs_path db_path, + const fs_path rx_fe_corr_path, + const double rx_lo_freq //actual lo freq + ); }} //namespace uhd::usrp #endif /* INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP */ diff --git a/host/lib/usrp/common/constrained_device_args.hpp b/host/lib/usrp/common/constrained_device_args.hpp new file mode 100644 index 000000000..1bfd1df00 --- /dev/null +++ b/host/lib/usrp/common/constrained_device_args.hpp @@ -0,0 +1,283 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/exception.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <vector> +#include <string> + +namespace uhd { +namespace usrp { + + /*! + * constrained_device_args_t provides a base and utilities to + * map key=value pairs passed in through the device creation + * args interface (device_addr_t). + * + * Inherit from this class to create typed device specific + * arguments and use the base class methods to handle parsing + * the device_addr or any key=value string to populate the args + * + * This file contains a library of different types of args the + * the user can pass in. The library can be extended to support + * non-intrinsic types by the client. + * + */ + class constrained_device_args_t { + public: //Types + + /*! + * Base argument type. All other arguments inherit from this. + */ + class generic_arg { + public: + generic_arg(const std::string& key): _key(key) {} + inline const std::string& key() const { return _key; } + inline virtual std::string to_string() const = 0; + private: + std::string _key; + }; + + /*! + * String argument type. Can be case sensitive or insensitive + */ + template<bool case_sensitive> + class str_arg : public generic_arg { + public: + str_arg(const std::string& name, const std::string& default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const std::string& value) { + _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value); + } + inline const std::string& get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + set(str_rep); + } + inline virtual std::string to_string() const { + return key() + "=" + get(); + } + inline bool operator==(const std::string& rhs) const { + return get() == boost::algorithm::to_lower_copy(rhs); + } + private: + std::string _value; + }; + typedef str_arg<false> str_ci_arg; + typedef str_arg<true> str_cs_arg; + + /*! + * Numeric argument type. The template type data_t allows the + * client to constrain the type of the number. + */ + template<typename data_t> + class num_arg : public generic_arg { + public: + num_arg(const std::string& name, const data_t default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const data_t value) { + _value = value; + } + inline const data_t get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = boost::lexical_cast<data_t>(str_rep); + } catch (std::exception& ex) { + throw uhd::value_error(str(boost::format( + "Error parsing numeric parameter %s: %s.") % + key() % ex.what() + )); + } + } + inline virtual std::string to_string() const { + return key() + "=" + boost::lexical_cast<std::string>(get()); + } + private: + data_t _value; + }; + + /*! + * Enumeration argument type. The template type enum_t allows the + * client to use their own enum and specify a string mapping for + * the values of the enum + * + * NOTE: The constraint on enum_t is that the values must start with + * 0 and be sequential + */ + template<typename enum_t> + class enum_arg : public generic_arg { + public: + enum_arg( + const std::string& name, + const enum_t default_value, + const std::vector<std::string>& values) : + generic_arg(name), _str_values(values) + { set(default_value); } + + inline void set(const enum_t value) { + _value = value; + } + inline const enum_t get() const { + return _value; + } + inline void parse(const std::string& str_rep, bool assert_invalid = true) { + std::string valid_values_str; + for (size_t i = 0; i < _str_values.size(); i++) { + if (boost::algorithm::to_lower_copy(str_rep) == + boost::algorithm::to_lower_copy(_str_values[i])) + { + valid_values_str += ((i==0)?"":", ") + _str_values[i]; + set(static_cast<enum_t>(static_cast<int>(i))); + return; + } + } + //If we reach here then, the string enum value was invalid + if (assert_invalid) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s=%s (Valid: {%s})") % + key() % str_rep % valid_values_str + )); + } + } + inline virtual std::string to_string() const { + size_t index = static_cast<size_t>(static_cast<int>(_value)); + UHD_ASSERT_THROW(index < _str_values.size()); + return key() + "=" + _str_values[index]; + } + + private: + enum_t _value; + std::vector<std::string> _str_values; + }; + + /*! + * Boolean argument type. + */ + class bool_arg : public generic_arg { + public: + bool_arg(const std::string& name, const bool default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const bool value) { + _value = value; + } + inline bool get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = (boost::lexical_cast<int>(str_rep) != 0); + } catch (std::exception& ex) { + if (str_rep.empty()) { + //If str_rep is empty then the device_addr was set + //without a value which means that the user "set" the flag + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "true" || + boost::algorithm::to_lower_copy(str_rep) == "yes" || + boost::algorithm::to_lower_copy(str_rep) == "y") { + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "false" || + boost::algorithm::to_lower_copy(str_rep) == "no" || + boost::algorithm::to_lower_copy(str_rep) == "n") { + _value = false; + } else { + throw uhd::value_error(str(boost::format( + "Error parsing boolean parameter %s: %s.") % + key() % ex.what() + )); + } + } + } + inline virtual std::string to_string() const { + return key() + "=" + (get() ? "true" : "false"); + } + private: + bool _value; + }; + + public: //Methods + constrained_device_args_t() {} + virtual ~constrained_device_args_t() {} + + void parse(const std::string& str_args) { + device_addr_t dev_args(str_args); + _parse(dev_args); + } + + void parse(const device_addr_t& dev_args) { + _parse(dev_args); + } + + inline virtual std::string to_string() const = 0; + + protected: //Methods + //Override _parse to provide an implementation to parse all + //client specific device args + virtual void _parse(const device_addr_t& dev_args) = 0; + + /*! + * Utility: Ensure that the value of the device arg is between min and max + */ + template<typename num_data_t> + static inline void _enforce_range(const num_arg<num_data_t>& arg, const num_data_t& min, const num_data_t& max) { + if (arg.get() > max || arg.get() < min) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Minimum: %s, Maximum: %s)") % + arg.to_string() % + boost::lexical_cast<std::string>(min) % boost::lexical_cast<std::string>(max))); + } + } + + /*! + * Utility: Ensure that the value of the device arg is is contained in valid_values + */ + template<typename arg_t, typename data_t> + static inline void _enforce_discrete(const arg_t& arg, const std::vector<data_t>& valid_values) { + bool match = false; + BOOST_FOREACH(const data_t& val, valid_values) { + if (val == arg.get()) { + match = true; + break; + } + } + if (!match) { + std::string valid_values_str; + for (size_t i = 0; i < valid_values.size(); i++) { + valid_values_str += ((i==0)?"":", ") + boost::lexical_cast<std::string>(valid_values[i]); + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Valid: {%s})") % + arg.to_string() % valid_values_str + )); + } + } + } + }; +}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP */ diff --git a/host/lib/usrp/common/fw_comm_protocol.h b/host/lib/usrp/common/fw_comm_protocol.h new file mode 100644 index 000000000..14adb33a9 --- /dev/null +++ b/host/lib/usrp/common/fw_comm_protocol.h @@ -0,0 +1,102 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_FW_COMM_PROTOCOL +#define INCLUDED_FW_COMM_PROTOCOL + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +/*! + * Structs and constants for communication between firmware and host. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +#define FW_COMM_PROTOCOL_SIGNATURE 0xACE3 +#define FW_COMM_PROTOCOL_VERSION 0 +#define FW_COMM_MAX_DATA_WORDS 16 +#define FW_COMM_PROTOCOL_MTU 256 + +#define FW_COMM_FLAGS_ACK 0x00000001 +#define FW_COMM_FLAGS_CMD_MASK 0x00000FF0 +#define FW_COMM_FLAGS_ERROR_MASK 0xFF000000 + +#define FW_COMM_CMD_ECHO 0x00000000 +#define FW_COMM_CMD_POKE32 0x00000010 +#define FW_COMM_CMD_PEEK32 0x00000020 +#define FW_COMM_CMD_BLOCK_POKE32 0x00000030 +#define FW_COMM_CMD_BLOCK_PEEK32 0x00000040 + +#define FW_COMM_ERR_PKT_ERROR 0x80000000 +#define FW_COMM_ERR_CMD_ERROR 0x40000000 +#define FW_COMM_ERR_SIZE_ERROR 0x20000000 + +#define FW_COMM_GENERATE_ID(prod) ((((uint32_t) FW_COMM_PROTOCOL_SIGNATURE) << 0) | \ + (((uint32_t) prod) << 16) | \ + (((uint32_t) FW_COMM_PROTOCOL_VERSION) << 24)) + +#define FW_COMM_GET_PROTOCOL_SIG(id) ((uint16_t)(id & 0xFFFF)) +#define FW_COMM_GET_PRODUCT_ID(id) ((uint8_t)(id >> 16)) +#define FW_COMM_GET_PROTOCOL_VER(id) ((uint8_t)(id >> 24)) + +typedef struct +{ + uint32_t id; //Protocol and device identifier + uint32_t flags; //Holds commands and ack messages + uint32_t sequence; //Sequence number (specific to FW communication transactions) + uint32_t data_words; //Number of data words in payload + uint32_t addr; //Address field for the command in flags + uint32_t data[FW_COMM_MAX_DATA_WORDS]; //Data field for the command in flags +} fw_comm_pkt_t; + +#ifdef __cplusplus +} //extern "C" +#endif + +// The following definitions are only useful in firmware. Exclude in host code. +#ifndef __cplusplus + +typedef void (*poke32_func)(const uint32_t addr, const uint32_t data); +typedef uint32_t (*peek32_func)(const uint32_t addr); + +/*! + * Process a firmware communication packet and compute a response. + * Args: + * - (in) request: Pointer to the request struct + * - (out) response: Pointer to the response struct + * - (in) product_id: The 8-bit usrp3 specific product ID (for request filtering) + * - (func) poke_callback, peek_callback: Callback functions for a single peek/poke + * - return value: Send a response packet + */ +bool process_fw_comm_protocol_pkt( + const fw_comm_pkt_t* request, + fw_comm_pkt_t* response, + uint8_t product_id, + uint32_t iface_id, + poke32_func poke_callback, + peek32_func peek_callback +); + +#endif //ifdef __cplusplus + +#endif /* INCLUDED_FW_COMM_PROTOCOL */ diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp new file mode 100644 index 000000000..ef541e37f --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp @@ -0,0 +1,246 @@ +// +// Copyright 2013 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp3_fw_ctrl_iface.hpp" + +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/foreach.hpp> +#include "fw_comm_protocol.h" + +namespace uhd { namespace usrp { namespace usrp3 { + +//---------------------------------------------------------- +// Factory method +//---------------------------------------------------------- +uhd::wb_iface::sptr usrp3_fw_ctrl_iface::make( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose) +{ + return wb_iface::sptr(new usrp3_fw_ctrl_iface(udp_xport, product_id, verbose)); +} + +//---------------------------------------------------------- +// udp_fw_ctrl_iface +//---------------------------------------------------------- + +usrp3_fw_ctrl_iface::usrp3_fw_ctrl_iface( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose) : + _product_id(product_id), _verbose(verbose), _udp_xport(udp_xport), + _seq_num(0) +{ + flush(); + peek32(0); +} + +usrp3_fw_ctrl_iface::~usrp3_fw_ctrl_iface() +{ + flush(); +} + +void usrp3_fw_ctrl_iface::flush() +{ + boost::mutex::scoped_lock lock(_mutex); + _flush(); +} + +void usrp3_fw_ctrl_iface::poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + boost::mutex::scoped_lock lock(_mutex); + + for (size_t i = 1; i <= NUM_RETRIES; i++) { + try { + _poke32(addr, data); + return; + } catch(const std::exception &ex) { + const std::string error_msg = str(boost::format( + "udp fw poke32 failure #%u\n%s") % i % ex.what()); + if (_verbose) UHD_MSG(warning) << error_msg << std::endl; + if (i == NUM_RETRIES) throw uhd::io_error(error_msg); + } + } +} + +boost::uint32_t usrp3_fw_ctrl_iface::peek32(const wb_addr_type addr) +{ + boost::mutex::scoped_lock lock(_mutex); + + for (size_t i = 1; i <= NUM_RETRIES; i++) { + try { + return _peek32(addr); + } catch(const std::exception &ex) { + const std::string error_msg = str(boost::format( + "udp fw peek32 failure #%u\n%s") % i % ex.what()); + if (_verbose) UHD_MSG(warning) << error_msg << std::endl; + if (i == NUM_RETRIES) throw uhd::io_error(error_msg); + } + } + return 0; +} + +void usrp3_fw_ctrl_iface::_poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + //Load request struct + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_POKE32); + request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); + request.addr = uhd::htonx(addr); + request.data_words = 1; + request.data[0] = uhd::htonx(data); + + //Send request + _flush(); + _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //Recv reply + fw_comm_pkt_t reply; + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("udp fw poke32 - reply timed out"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); + UHD_ASSERT_THROW(nbytes == sizeof(reply)); + UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); + UHD_ASSERT_THROW(flags & FW_COMM_CMD_POKE32); + UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + UHD_ASSERT_THROW(reply.data[0] == request.data[0]); +} + +boost::uint32_t usrp3_fw_ctrl_iface::_peek32(const wb_addr_type addr) +{ + //Load request struct + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_PEEK32); + request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); + request.addr = uhd::htonx(addr); + request.data_words = 1; + request.data[0] = 0; + + //Send request + _flush(); + _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //Recv reply + fw_comm_pkt_t reply; + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("udp fw peek32 - reply timed out"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); + UHD_ASSERT_THROW(nbytes == sizeof(reply)); + UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); + UHD_ASSERT_THROW(flags & FW_COMM_CMD_PEEK32); + UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + + //return result! + return uhd::ntohx<boost::uint32_t>(reply.data[0]); +} + +void usrp3_fw_ctrl_iface::_flush(void) +{ + char buff[FW_COMM_PROTOCOL_MTU] = {}; + while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { + /*NOP*/ + } +} + +std::vector<std::string> usrp3_fw_ctrl_iface::discover_devices( + const std::string& addr_hint, const std::string& port, + boost::uint16_t product_id) +{ + std::vector<std::string> addrs; + + //Create a UDP transport to communicate: + //Some devices will cause a throw when opened for a broadcast address. + //We print and recover so the caller can loop through all bcast addrs. + uhd::transport::udp_simple::sptr udp_bcast_xport; + try { + udp_bcast_xport = uhd::transport::udp_simple::make_broadcast(addr_hint, port); + } catch(const std::exception &e) { + UHD_MSG(error) << boost::format("Cannot open UDP transport on %s for discovery\n%s") + % addr_hint % e.what() << std::endl; + return addrs; + } + + //Send dummy request + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + udp_bcast_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + while (true) { + char buff[FW_COMM_PROTOCOL_MTU] = {}; + const size_t nbytes = udp_bcast_xport->recv(boost::asio::buffer(buff), 0.050); + if (nbytes != sizeof(fw_comm_pkt_t)) break; //No more responses or responses are invalid + + const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; + if (request.id == reply->id && + request.flags == reply->flags && + request.sequence == reply->sequence) + { + addrs.push_back(udp_bcast_xport->get_recv_addr()); + } + } + + return addrs; +} + +boost::uint32_t usrp3_fw_ctrl_iface::get_iface_id( + const std::string& addr, const std::string& port, + boost::uint16_t product_id) +{ + uhd::transport::udp_simple::sptr udp_xport = + uhd::transport::udp_simple::make_connected(addr, port); + + //Send dummy request + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + char buff[FW_COMM_PROTOCOL_MTU] = {}; + const size_t nbytes = udp_xport->recv(boost::asio::buffer(buff), 1.0); + + const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; + if (nbytes > 0 && + request.id == reply->id && + request.flags == reply->flags && + request.sequence == reply->sequence) + { + return uhd::ntohx<boost::uint32_t>(reply->data[0]); + } else { + throw uhd::io_error("udp get_iface_id - bad response"); + } +} + +}}} //namespace diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp new file mode 100644 index 000000000..33286861b --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp @@ -0,0 +1,72 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP +#define INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include <vector> + +namespace uhd { namespace usrp { namespace usrp3 { + +class usrp3_fw_ctrl_iface : public uhd::wb_iface +{ +public: + usrp3_fw_ctrl_iface( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose); + virtual ~usrp3_fw_ctrl_iface(); + + // -- uhd::wb_iface -- + void poke32(const wb_addr_type addr, const boost::uint32_t data); + boost::uint32_t peek32(const wb_addr_type addr); + void flush(); + + static uhd::wb_iface::sptr make( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose = true); + // -- uhd::wb_iface -- + + static std::vector<std::string> discover_devices( + const std::string& addr_hint, const std::string& port, + boost::uint16_t product_id); + + static boost::uint32_t get_iface_id( + const std::string& addr, const std::string& port, + boost::uint16_t product_id); + +private: + void _poke32(const wb_addr_type addr, const boost::uint32_t data); + boost::uint32_t _peek32(const wb_addr_type addr); + void _flush(void); + + const boost::uint16_t _product_id; + const bool _verbose; + uhd::transport::udp_simple::sptr _udp_xport; + boost::uint32_t _seq_num; + boost::mutex _mutex; + + static const size_t NUM_RETRIES = 3; +}; + +}}} //namespace + +#endif //INCLUDED_LIBUHD_USRP_USRP3_USRP3_UDP_FW_CTRL_HPP diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt index f28ae040f..1e16dd39e 100644 --- a/host/lib/usrp/cores/CMakeLists.txt +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -30,6 +30,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_frontend_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_vita_core_3000.cpp @@ -37,7 +38,11 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/time_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/spi_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/i2c_core_100_wb32.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dsp_core_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_core_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/gpio_atr_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_core_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_3000.cpp ) diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.cpp b/host/lib/usrp/cores/dma_fifo_core_3000.cpp new file mode 100644 index 000000000..5df28f7c2 --- /dev/null +++ b/host/lib/usrp/cores/dma_fifo_core_3000.cpp @@ -0,0 +1,401 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dma_fifo_core_3000.hpp" +#include <uhd/exception.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <uhd/utils/soft_register.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; + +#define SR_DRAM_BIST_BASE 16 + +dma_fifo_core_3000::~dma_fifo_core_3000(void) { + /* NOP */ +} + +class dma_fifo_core_3000_impl : public dma_fifo_core_3000 +{ +protected: + class rb_addr_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADDR, /*width*/ 3, /*shift*/ 0); //[2:0] + + static const boost::uint32_t RB_FIFO_STATUS = 0; + static const boost::uint32_t RB_BIST_STATUS = 1; + static const boost::uint32_t RB_BIST_XFER_CNT = 2; + static const boost::uint32_t RB_BIST_CYC_CNT = 3; + + rb_addr_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 0) + { + //Initial values + set(ADDR, RB_FIFO_STATUS); + } + }; + + class fifo_ctrl_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(CLEAR_FIFO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(RD_SUPPRESS_EN, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(BURST_TIMEOUT, /*width*/ 12, /*shift*/ 4); //[15:4] + UHD_DEFINE_SOFT_REG_FIELD(RD_SUPPRESS_THRESH, /*width*/ 16, /*shift*/ 16); //[31:16] + + fifo_ctrl_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 4) + { + //Initial values + set(CLEAR_FIFO, 1); + set(RD_SUPPRESS_EN, 0); + set(BURST_TIMEOUT, 256); + set(RD_SUPPRESS_THRESH, 0); + } + }; + + class base_addr_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(BASE_ADDR, /*width*/ 30, /*shift*/ 0); //[29:0] + + base_addr_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 8) + { + //Initial values + set(BASE_ADDR, 0x00000000); + } + }; + + class addr_mask_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADDR_MASK, /*width*/ 30, /*shift*/ 0); //[29:0] + + addr_mask_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 12) + { + //Initial values + set(ADDR_MASK, 0xFF000000); + } + }; + + class bist_ctrl_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(GO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(CONTINUOUS_MODE, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(TEST_PATT, /*width*/ 2, /*shift*/ 4); //[5:4] + + static const boost::uint32_t TEST_PATT_ZERO_ONE = 0; + static const boost::uint32_t TEST_PATT_CHECKERBOARD = 1; + static const boost::uint32_t TEST_PATT_COUNT = 2; + static const boost::uint32_t TEST_PATT_COUNT_INV = 3; + + bist_ctrl_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 16) + { + //Initial values + set(GO, 0); + set(CONTINUOUS_MODE, 0); + set(TEST_PATT, TEST_PATT_ZERO_ONE); + } + }; + + class bist_cfg_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(MAX_PKTS, /*width*/ 18, /*shift*/ 0); //[17:0] + UHD_DEFINE_SOFT_REG_FIELD(MAX_PKT_SIZE, /*width*/ 13, /*shift*/ 18); //[30:18] + UHD_DEFINE_SOFT_REG_FIELD(PKT_SIZE_RAMP, /*width*/ 1, /*shift*/ 31); //[31] + + bist_cfg_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 20) + { + //Initial values + set(MAX_PKTS, 0); + set(MAX_PKT_SIZE, 0); + set(PKT_SIZE_RAMP, 0); + } + }; + + class bist_delay_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(TX_PKT_DELAY, /*width*/ 16, /*shift*/ 0); //[15:0] + UHD_DEFINE_SOFT_REG_FIELD(RX_SAMP_DELAY, /*width*/ 8, /*shift*/ 16); //[23:16] + + bist_delay_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 24) + { + //Initial values + set(TX_PKT_DELAY, 0); + set(RX_SAMP_DELAY, 0); + } + }; + + class bist_sid_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SID, /*width*/ 32, /*shift*/ 0); //[31:0] + + bist_sid_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 28) + { + //Initial values + set(SID, 0); + } + }; + +public: + class fifo_readback { + public: + fifo_readback(wb_iface::sptr iface, const size_t base, const size_t rb_addr) : + _iface(iface), _addr_reg(base), _rb_addr(rb_addr) + { + _addr_reg.initialize(*iface, true); + } + + bool is_fifo_instantiated() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x80000000; + } + + boost::uint32_t get_occupied_cnt() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x7FFFFFF; + } + + boost::uint32_t is_fifo_busy() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x40000000; + } + + struct bist_status_t { + bool running; + bool finished; + boost::uint8_t error; + }; + + bist_status_t get_bist_status() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_STATUS); + boost::uint32_t st32 = _iface->peek32(_rb_addr) & 0xF; + bist_status_t status; + status.running = st32 & 0x1; + status.finished = st32 & 0x2; + status.error = static_cast<boost::uint8_t>((st32>>2) & 0x3); + return status; + } + + bool is_ext_bist_supported() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_STATUS); + return _iface->peek32(_rb_addr) & 0x80000000; + } + + double get_xfer_ratio() { + boost::lock_guard<boost::mutex> lock(_mutex); + boost::uint32_t xfer_cnt = 0, cyc_cnt = 0; + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_XFER_CNT); + xfer_cnt = _iface->peek32(_rb_addr); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_CYC_CNT); + cyc_cnt = _iface->peek32(_rb_addr); + return (static_cast<double>(xfer_cnt)/cyc_cnt); + } + + private: + wb_iface::sptr _iface; + rb_addr_reg_t _addr_reg; + const size_t _rb_addr; + boost::mutex _mutex; + }; + +public: + dma_fifo_core_3000_impl(wb_iface::sptr iface, const size_t base, const size_t readback): + _iface(iface), _base(base), _fifo_readback(iface, base, readback), + _fifo_ctrl_reg(base), _base_addr_reg(base), _addr_mask_reg(base), + _bist_ctrl_reg(base), _bist_cfg_reg(base), _bist_delay_reg(base), _bist_sid_reg(base) + { + _fifo_ctrl_reg.initialize(*iface, true); + _base_addr_reg.initialize(*iface, true); + _addr_mask_reg.initialize(*iface, true); + _bist_ctrl_reg.initialize(*iface, true); + _bist_cfg_reg.initialize(*iface, true); + _has_ext_bist = _fifo_readback.is_ext_bist_supported(); + if (_has_ext_bist) { + _bist_delay_reg.initialize(*iface, true); + _bist_sid_reg.initialize(*iface, true); + } + flush(); + } + + virtual ~dma_fifo_core_3000_impl() + { + } + + virtual void flush() { + //Clear the FIFO and hold it in that state + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1); + //Re-arm the FIFO + _wait_for_fifo_empty(); + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 0); + } + + virtual void resize(const boost::uint32_t base_addr, const boost::uint32_t size) { + //Validate parameters + if (size < 8192) throw uhd::runtime_error("DMA FIFO must be larger than 8KiB"); + boost::uint32_t size_mask = size - 1; + if (size & size_mask) throw uhd::runtime_error("DMA FIFO size must be a power of 2"); + + //Clear the FIFO and hold it in that state + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1); + //Write base address and mask + _base_addr_reg.write(base_addr_reg_t::BASE_ADDR, base_addr); + _addr_mask_reg.write(addr_mask_reg_t::ADDR_MASK, ~size_mask); + + //Re-arm the FIFO + flush(); + } + + virtual boost::uint32_t get_bytes_occupied() { + return _fifo_readback.get_occupied_cnt() * 8; + } + + virtual bool ext_bist_supported() { + return _fifo_readback.is_ext_bist_supported(); + } + + virtual boost::uint8_t run_bist(bool finite = true, boost::uint32_t timeout_ms = 500) { + return run_ext_bist(finite, 0, 0, 0, timeout_ms); + } + + virtual boost::uint8_t run_ext_bist( + bool finite, + boost::uint32_t rx_samp_delay, + boost::uint32_t tx_pkt_delay, + boost::uint32_t sid, + boost::uint32_t timeout_ms = 500 + ) { + boost::lock_guard<boost::mutex> lock(_mutex); + + _wait_for_bist_done(timeout_ms, true); //Stop previous BIST and wait (if running) + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 0); //Reset + + _bist_cfg_reg.set(bist_cfg_reg_t::MAX_PKTS, (2^18)-1); + _bist_cfg_reg.set(bist_cfg_reg_t::MAX_PKT_SIZE, 8000); + _bist_cfg_reg.set(bist_cfg_reg_t::PKT_SIZE_RAMP, 0); + _bist_cfg_reg.flush(); + + if (_has_ext_bist) { + _bist_delay_reg.set(bist_delay_reg_t::RX_SAMP_DELAY, rx_samp_delay); + _bist_delay_reg.set(bist_delay_reg_t::TX_PKT_DELAY, tx_pkt_delay); + _bist_delay_reg.flush(); + + _bist_sid_reg.write(bist_sid_reg_t::SID, sid); + } else { + if (rx_samp_delay != 0 || tx_pkt_delay != 0 || sid != 0) { + throw uhd::not_implemented_error( + "dma_fifo_core_3000: Runtime delay and SID support only available on FPGA images with extended BIST enabled"); + } + } + + _bist_ctrl_reg.set(bist_ctrl_reg_t::TEST_PATT, bist_ctrl_reg_t::TEST_PATT_COUNT); + _bist_ctrl_reg.set(bist_ctrl_reg_t::CONTINUOUS_MODE, finite ? 0 : 1); + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 1); + + if (!finite) { + boost::this_thread::sleep(boost::posix_time::milliseconds(timeout_ms)); + } + + _wait_for_bist_done(timeout_ms, !finite); + if (!_fifo_readback.get_bist_status().finished) { + throw uhd::runtime_error("dma_fifo_core_3000: DRAM BIST state machine is in a bad state."); + } + + return _fifo_readback.get_bist_status().error; + } + + virtual double get_bist_throughput(double fifo_clock_rate) { + if (_has_ext_bist) { + _wait_for_bist_done(1000); + static const double BYTES_PER_CYC = 8; + return _fifo_readback.get_xfer_ratio() * fifo_clock_rate * BYTES_PER_CYC; + } else { + throw uhd::not_implemented_error( + "dma_fifo_core_3000: Throughput counter only available on FPGA images with extended BIST enabled"); + } + } + +private: + void _wait_for_fifo_empty() + { + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + while (_fifo_readback.is_fifo_busy()) { + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + if (elapsed.total_milliseconds() > 100) break; + } + } + + void _wait_for_bist_done(boost::uint32_t timeout_ms, bool force_stop = false) + { + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + while (_fifo_readback.get_bist_status().running) { + if (force_stop) { + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 0); + force_stop = false; + } + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + if (elapsed.total_milliseconds() > timeout_ms) break; + } + } + +private: + wb_iface::sptr _iface; + const size_t _base; + boost::mutex _mutex; + bool _has_ext_bist; + + fifo_readback _fifo_readback; + fifo_ctrl_reg_t _fifo_ctrl_reg; + base_addr_reg_t _base_addr_reg; + addr_mask_reg_t _addr_mask_reg; + bist_ctrl_reg_t _bist_ctrl_reg; + bist_cfg_reg_t _bist_cfg_reg; + bist_delay_reg_t _bist_delay_reg; + bist_sid_reg_t _bist_sid_reg; +}; + +// +// Static make function +// +dma_fifo_core_3000::sptr dma_fifo_core_3000::make(wb_iface::sptr iface, const size_t set_base, const size_t rb_addr) +{ + if (check(iface, set_base, rb_addr)) { + return sptr(new dma_fifo_core_3000_impl(iface, set_base, rb_addr)); + } else { + throw uhd::runtime_error(""); + } +} + +bool dma_fifo_core_3000::check(wb_iface::sptr iface, const size_t set_base, const size_t rb_addr) +{ + dma_fifo_core_3000_impl::fifo_readback fifo_rb(iface, set_base, rb_addr); + return fifo_rb.is_fifo_instantiated(); +} diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.hpp b/host/lib/usrp/cores/dma_fifo_core_3000.hpp new file mode 100644 index 000000000..41430e5c3 --- /dev/null +++ b/host/lib/usrp/cores/dma_fifo_core_3000.hpp @@ -0,0 +1,86 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <uhd/types/wb_iface.hpp> + + +class dma_fifo_core_3000 : boost::noncopyable +{ +public: + typedef boost::shared_ptr<dma_fifo_core_3000> sptr; + virtual ~dma_fifo_core_3000(void) = 0; + + /*! + * Create a DMA FIFO controller using the given bus, settings and readback base + * Throws uhd::runtime_error if a DMA FIFO is not instantiated in the FPGA + */ + static sptr make(uhd::wb_iface::sptr iface, const size_t set_base, const size_t rb_addr); + + /*! + * Check if a DMA FIFO is instantiated in the FPGA + */ + static bool check(uhd::wb_iface::sptr iface, const size_t set_base, const size_t rb_addr); + + /*! + * Flush the DMA FIFO. Will clear all contents. + */ + virtual void flush() = 0; + + /*! + * Resize and rebase the DMA FIFO. Will clear all contents. + */ + virtual void resize(const boost::uint32_t base_addr, const boost::uint32_t size) = 0; + + /*! + * Get the (approx) number of bytes currently in the DMA FIFO + */ + virtual boost::uint32_t get_bytes_occupied() = 0; + + /*! + * Run the built-in-self-test routine for the DMA FIFO + */ + virtual boost::uint8_t run_bist(bool finite = true, boost::uint32_t timeout_ms = 500) = 0; + + /*! + * Is extended BIST supported + */ + virtual bool ext_bist_supported() = 0; + + /*! + * Run the built-in-self-test routine for the DMA FIFO (extended BIST only) + */ + virtual boost::uint8_t run_ext_bist( + bool finite, + boost::uint32_t rx_samp_delay, + boost::uint32_t tx_pkt_delay, + boost::uint32_t sid, + boost::uint32_t timeout_ms = 500) = 0; + + /*! + * Get the throughput measured from the last invocation of the BIST (extended BIST only) + */ + virtual double get_bist_throughput(double fifo_clock_rate) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/dsp_core_utils.cpp b/host/lib/usrp/cores/dsp_core_utils.cpp new file mode 100644 index 000000000..aea809ae8 --- /dev/null +++ b/host/lib/usrp/cores/dsp_core_utils.cpp @@ -0,0 +1,66 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dsp_core_utils.hpp" +#include <uhd/utils/math.hpp> +#include <uhd/exception.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> + +static const int32_t MAX_FREQ_WORD = boost::numeric::bounds<boost::int32_t>::highest(); +static const int32_t MIN_FREQ_WORD = boost::numeric::bounds<boost::int32_t>::lowest(); + +void get_freq_and_freq_word( + const double requested_freq, + const double tick_rate, + double &actual_freq, + int32_t &freq_word +) { + //correct for outside of rate (wrap around) + double freq = std::fmod(requested_freq, tick_rate); + if (std::abs(freq) > tick_rate/2.0) + freq -= boost::math::sign(freq) * tick_rate; + + //confirm that the target frequency is within range of the CORDIC + UHD_ASSERT_THROW(std::abs(freq) <= tick_rate/2.0); + + /* Now calculate the frequency word. It is possible for this calculation + * to cause an overflow. As the requested DSP frequency approaches the + * master clock rate, that ratio multiplied by the scaling factor (2^32) + * will generally overflow within the last few kHz of tunable range. + * Thus, we check to see if the operation will overflow before doing it, + * and if it will, we set it to the integer min or max of this system. + */ + freq_word = 0; + + static const double scale_factor = std::pow(2.0, 32); + if ((freq / tick_rate) >= (MAX_FREQ_WORD / scale_factor)) { + /* Operation would have caused a positive overflow of int32. */ + freq_word = MAX_FREQ_WORD; + + } else if ((freq / tick_rate) <= (MIN_FREQ_WORD / scale_factor)) { + /* Operation would have caused a negative overflow of int32. */ + freq_word = MIN_FREQ_WORD; + + } else { + /* The operation is safe. Perform normally. */ + freq_word = int32_t(boost::math::round((freq / tick_rate) * scale_factor)); + } + + actual_freq = (double(freq_word) / scale_factor) * tick_rate; +} + diff --git a/host/lib/usrp/cores/dsp_core_utils.hpp b/host/lib/usrp/cores/dsp_core_utils.hpp new file mode 100644 index 000000000..d5d43f236 --- /dev/null +++ b/host/lib/usrp/cores/dsp_core_utils.hpp @@ -0,0 +1,33 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP +#define INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP + +#include <stdint.h> + +/*! For a requested frequency and sampling rate, return the + * correct frequency word (to set the CORDIC) and the actual frequency. + */ +void get_freq_and_freq_word( + const double requested_freq, + const double tick_rate, + double &actual_freq, + int32_t &freq_word +); + +#endif /* INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP */ diff --git a/host/lib/usrp/cores/gpio_atr_3000.cpp b/host/lib/usrp/cores/gpio_atr_3000.cpp new file mode 100644 index 000000000..5844af601 --- /dev/null +++ b/host/lib/usrp/cores/gpio_atr_3000.cpp @@ -0,0 +1,341 @@ +// +// Copyright 2011,2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "gpio_atr_3000.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/utils/soft_register.hpp> + +using namespace uhd; +using namespace usrp; + +//------------------------------------------------------------- +// gpio_atr_3000 +//------------------------------------------------------------- + +#define REG_ATR_IDLE_OFFSET (base + 0) +#define REG_ATR_RX_OFFSET (base + 4) +#define REG_ATR_TX_OFFSET (base + 8) +#define REG_ATR_FDX_OFFSET (base + 12) +#define REG_DDR_OFFSET (base + 16) +#define REG_ATR_DISABLE_OFFSET (base + 20) + +namespace uhd { namespace usrp { namespace gpio_atr { + +class gpio_atr_3000_impl : public gpio_atr_3000{ +public: + gpio_atr_3000_impl( + wb_iface::sptr iface, + const wb_iface::wb_addr_type base, + const wb_iface::wb_addr_type rb_addr = READBACK_DISABLED + ): + _iface(iface), _rb_addr(rb_addr), + _atr_idle_reg(REG_ATR_IDLE_OFFSET, _atr_disable_reg), + _atr_rx_reg(REG_ATR_RX_OFFSET), + _atr_tx_reg(REG_ATR_TX_OFFSET), + _atr_fdx_reg(REG_ATR_FDX_OFFSET), + _ddr_reg(REG_DDR_OFFSET), + _atr_disable_reg(REG_ATR_DISABLE_OFFSET) + { + _atr_idle_reg.initialize(*_iface, true); + _atr_rx_reg.initialize(*_iface, true); + _atr_tx_reg.initialize(*_iface, true); + _atr_fdx_reg.initialize(*_iface, true); + _ddr_reg.initialize(*_iface, true); + _atr_disable_reg.initialize(*_iface, true); + } + + virtual void set_atr_mode(const gpio_atr_mode_t mode, const boost::uint32_t mask) + { + //Each bit in the "ATR Disable" register determines whether the respective bit in the GPIO + //output bus is driven by the ATR engine or a static register. + //For each bit position, a 1 means that the bit is static and 0 means that the bit + //is driven by the ATR state machine. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + _atr_disable_reg.set_with_mask((mode==MODE_ATR) ? ~MASK_SET_ALL : MASK_SET_ALL, mask); + _atr_disable_reg.flush(); + } + + virtual void set_gpio_ddr(const gpio_ddr_t dir, const boost::uint32_t mask) + { + //Each bit in the "DDR" register determines whether the respective bit in the GPIO + //bus is an input or an output. + //For each bit position, a 1 means that the bit is an output and 0 means that the bit + //is an input. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + _ddr_reg.set_with_mask((dir==DDR_INPUT) ? ~MASK_SET_ALL : MASK_SET_ALL, mask); + _ddr_reg.flush(); + } + + virtual void set_atr_reg(const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) + { + //Set the value of the specified ATR register. For bits with ATR Disable set to 1, + //the IDLE register will hold the output state + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + masked_reg_t* reg = NULL; + switch (atr) { + case ATR_REG_IDLE: reg = &_atr_idle_reg; break; + case ATR_REG_RX_ONLY: reg = &_atr_rx_reg; break; + case ATR_REG_TX_ONLY: reg = &_atr_tx_reg; break; + case ATR_REG_FULL_DUPLEX: reg = &_atr_fdx_reg; break; + default: reg = &_atr_idle_reg; break; + } + //For protection we only write to bits that have the mode ATR by masking the user + //specified "mask" with ~atr_disable. + reg->set_with_mask(value, mask); + reg->flush(); + } + + virtual void set_gpio_out(const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) { + //Set the value of the specified GPIO output register. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + + //For protection we only write to bits that have the mode GPIO by masking the user + //specified "mask" with atr_disable. + _atr_idle_reg.set_gpio_out_with_mask(value, mask); + _atr_idle_reg.flush(); + } + + virtual boost::uint32_t read_gpio() + { + //Read the state of the GPIO pins + //If a pin is configured as an input, reads the actual value of the pin + //If a pin is configured as an output, reads the last value written to the pin + if (_rb_addr != READBACK_DISABLED) { + return _iface->peek32(_rb_addr); + } else { + throw uhd::runtime_error("read_gpio not supported for write-only interface."); + } + } + + inline virtual void set_gpio_attr(const gpio_attr_t attr, const boost::uint32_t value) + { + //An attribute based API to configure all settings for the GPIO bus in one function + //call. This API does not have a mask so it configures all bits at the same time. + switch (attr) + { + case GPIO_CTRL: + set_atr_mode(MODE_ATR, value); //Configure mode=ATR for all bits that are set + set_atr_mode(MODE_GPIO, ~value); //Configure mode=GPIO for all bits that are unset + break; + case GPIO_DDR: + set_gpio_ddr(DDR_OUTPUT, value); //Configure as output for all bits that are set + set_gpio_ddr(DDR_INPUT, ~value); //Configure as input for all bits that are unset + break; + case GPIO_OUT: + //Only set bits that are driven statically + set_gpio_out(value); + break; + case GPIO_ATR_0X: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_IDLE, value); + break; + case GPIO_ATR_RX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_RX_ONLY, value); + break; + case GPIO_ATR_TX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_TX_ONLY, value); + break; + case GPIO_ATR_XX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_FULL_DUPLEX, value); + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + } + +protected: + //Special RB addr value to indicate no readback + //This value is invalid as a real address because it is not a multiple of 4 + static const wb_iface::wb_addr_type READBACK_DISABLED = 0xFFFFFFFF; + + class masked_reg_t : public uhd::soft_reg32_wo_t { + public: + masked_reg_t(const wb_iface::wb_addr_type offset): uhd::soft_reg32_wo_t(offset) { + uhd::soft_reg32_wo_t::set(REGISTER, 0); + } + + virtual void set_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + uhd::soft_reg32_wo_t::set(REGISTER, + (value&mask)|(uhd::soft_reg32_wo_t::get(REGISTER)&(~mask))); + } + + virtual boost::uint32_t get() { + return uhd::soft_reg32_wo_t::get(uhd::soft_reg32_wo_t::REGISTER); + } + + virtual void flush() { + uhd::soft_reg32_wo_t::flush(); + } + }; + + class atr_idle_reg_t : public masked_reg_t { + public: + atr_idle_reg_t(const wb_iface::wb_addr_type offset, masked_reg_t& atr_disable_reg): + masked_reg_t(offset), + _atr_idle_cache(0), _gpio_out_cache(0), + _atr_disable_reg(atr_disable_reg) + { } + + virtual void set_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + _atr_idle_cache = (value&mask)|(_atr_idle_cache&(~mask)); + } + + virtual boost::uint32_t get() { + return _atr_idle_cache; + } + + void set_gpio_out_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + _gpio_out_cache = (value&mask)|(_gpio_out_cache&(~mask)); + } + + virtual boost::uint32_t get_gpio_out() { + return _gpio_out_cache; + } + + virtual void flush() { + set(REGISTER, + (_atr_idle_cache & (~_atr_disable_reg.get())) | + (_gpio_out_cache & _atr_disable_reg.get()) + ); + masked_reg_t::flush(); + } + + private: + boost::uint32_t _atr_idle_cache; + boost::uint32_t _gpio_out_cache; + masked_reg_t& _atr_disable_reg; + }; + + wb_iface::sptr _iface; + wb_iface::wb_addr_type _rb_addr; + atr_idle_reg_t _atr_idle_reg; + masked_reg_t _atr_rx_reg; + masked_reg_t _atr_tx_reg; + masked_reg_t _atr_fdx_reg; + masked_reg_t _ddr_reg; + masked_reg_t _atr_disable_reg; +}; + +gpio_atr_3000::sptr gpio_atr_3000::make( + wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr +) { + return sptr(new gpio_atr_3000_impl(iface, base, rb_addr)); +} + +gpio_atr_3000::sptr gpio_atr_3000::make_write_only( + wb_iface::sptr iface, const wb_iface::wb_addr_type base +) { + gpio_atr_3000::sptr gpio_iface(new gpio_atr_3000_impl(iface, base)); + gpio_iface->set_gpio_ddr(DDR_OUTPUT, MASK_SET_ALL); + return gpio_iface; +} + +//------------------------------------------------------------- +// db_gpio_atr_3000 +//------------------------------------------------------------- + +class db_gpio_atr_3000_impl : public gpio_atr_3000_impl, public db_gpio_atr_3000 { +public: + db_gpio_atr_3000_impl(wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr): + gpio_atr_3000_impl(iface, base, rb_addr) { /* NOP */ } + + inline void set_pin_ctrl(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_atr_mode(MODE_ATR, compute_mask(unit, value&mask)); + gpio_atr_3000_impl::set_atr_mode(MODE_GPIO, compute_mask(unit, (~value)&mask)); + } + + inline boost::uint32_t get_pin_ctrl(const db_unit_t unit) + { + return (~_atr_disable_reg.get()) >> compute_shift(unit); + } + + inline void set_gpio_ddr(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_gpio_ddr(DDR_OUTPUT, compute_mask(unit, value&mask)); + gpio_atr_3000_impl::set_gpio_ddr(DDR_INPUT, compute_mask(unit, (~value)&mask)); + } + + inline boost::uint32_t get_gpio_ddr(const db_unit_t unit) + { + return _ddr_reg.get() >> compute_shift(unit); + } + + inline void set_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_atr_reg(atr, value << compute_shift(unit), compute_mask(unit, mask)); + } + + inline boost::uint32_t get_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr) + { + masked_reg_t* reg = NULL; + switch (atr) { + case ATR_REG_IDLE: reg = &_atr_idle_reg; break; + case ATR_REG_RX_ONLY: reg = &_atr_rx_reg; break; + case ATR_REG_TX_ONLY: reg = &_atr_tx_reg; break; + case ATR_REG_FULL_DUPLEX: reg = &_atr_fdx_reg; break; + default: reg = &_atr_idle_reg; break; + } + return (reg->get() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + + inline void set_gpio_out(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_gpio_out( + static_cast<boost::uint32_t>(value) << compute_shift(unit), + compute_mask(unit, mask)); + } + + inline boost::uint32_t get_gpio_out(const db_unit_t unit) + { + return (_atr_idle_reg.get_gpio_out() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + + inline boost::uint32_t read_gpio(const db_unit_t unit) + { + return (gpio_atr_3000_impl::read_gpio() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + +private: + inline boost::uint32_t compute_shift(const db_unit_t unit) { + switch (unit) { + case dboard_iface::UNIT_RX: return 0; + case dboard_iface::UNIT_TX: return 16; + default: return 0; + } + } + + inline boost::uint32_t compute_mask(const db_unit_t unit, const boost::uint32_t mask) { + boost::uint32_t tmp_mask = (unit == dboard_iface::UNIT_BOTH) ? mask : (mask & 0xFFFF); + return tmp_mask << (compute_shift(unit)); + } +}; + +db_gpio_atr_3000::sptr db_gpio_atr_3000::make( + wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr +) { + return sptr(new db_gpio_atr_3000_impl(iface, base, rb_addr)); +} + +}}} diff --git a/host/lib/usrp/cores/gpio_atr_3000.hpp b/host/lib/usrp/cores/gpio_atr_3000.hpp new file mode 100644 index 000000000..7b90429fe --- /dev/null +++ b/host/lib/usrp/cores/gpio_atr_3000.hpp @@ -0,0 +1,183 @@ +// +// Copyright 2011,2014,2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/wb_iface.hpp> + +namespace uhd { namespace usrp { namespace gpio_atr { + +class gpio_atr_3000 : boost::noncopyable { +public: + typedef boost::shared_ptr<gpio_atr_3000> sptr; + + static const boost::uint32_t MASK_SET_ALL = 0xFFFFFFFF; + + virtual ~gpio_atr_3000(void) {}; + + /*! + * Create a read-write GPIO ATR interface object + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + * \param base readback offset for GPIO ATR registers + */ + static sptr make( + uhd::wb_iface::sptr iface, + const uhd::wb_iface::wb_addr_type base, + const uhd::wb_iface::wb_addr_type rb_addr); + + /*! + * Create a write-only GPIO ATR interface object + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + */ + static sptr make_write_only( + uhd::wb_iface::sptr iface, const uhd::wb_iface::wb_addr_type base); + + /*! + * Select the ATR mode for all bits in the mask + * + * \param mode the mode to apply {ATR = outputs driven by ATR state machine, GPIO = outputs static} + * \param mask apply the mode to all non-zero bits in the mask + */ + virtual void set_atr_mode(const gpio_atr_mode_t mode, const boost::uint32_t mask) = 0; + + /*! + * Select the data direction for all bits in the mask + * + * \param dir the direction {OUTPUT, INPUT} + * \param mask apply the mode to all non-zero bits in the mask + */ + virtual void set_gpio_ddr(const gpio_ddr_t dir, const boost::uint32_t mask) = 0; + + /*! + * Write the specified (masked) value to the ATR register + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param value the value to write + * \param mask only writes to the bits where mask is non-zero + */ + virtual void set_atr_reg(const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) = 0; + + /*! + * Write to a static GPIO output + * + * \param value the value to write + * \param mask only writes to the bits where mask is non-zero + */ + virtual void set_gpio_out(const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) = 0; + + /*! + * Read the state of the GPIO pins + * If a pin is configured as an input, reads the actual value of the pin + * If a pin is configured as an output, reads the last value written to the pin + * + * \return the value read back + */ + virtual boost::uint32_t read_gpio() = 0; + + /*! + * Set a GPIO attribute + * + * \param attr the attribute to set + * \param value the value to write to the attribute + */ + virtual void set_gpio_attr(const gpio_attr_t attr, const boost::uint32_t value) = 0; +}; + +class db_gpio_atr_3000 { +public: + typedef boost::shared_ptr<db_gpio_atr_3000> sptr; + + typedef uhd::usrp::dboard_iface::unit_t db_unit_t; + + virtual ~db_gpio_atr_3000(void) {}; + + /*! + * Create a read-write GPIO ATR interface object for a daughterboard connector + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + * \param base readback offset for GPIO ATR registers + */ + static sptr make( + uhd::wb_iface::sptr iface, + const uhd::wb_iface::wb_addr_type base, + const uhd::wb_iface::wb_addr_type rb_addr); + + /*! + * Configure the GPIO mode for all pins in the daughterboard connector + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value if value[i] is 1, the i'th bit is in ATR mode otherwise it is in GPIO mode + */ + virtual void set_pin_ctrl(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_pin_ctrl(const db_unit_t unit) = 0; + + /*! + * Configure the direction for all pins in the daughterboard connector + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value if value[i] is 1, the i'th bit is an output otherwise it is an input + */ + virtual void set_gpio_ddr(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_gpio_ddr(const db_unit_t unit) = 0; + + /*! + * Write the specified value to the ATR register (all bits) + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value the value to write + */ + virtual void set_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr) = 0; + + /*! + * Write the specified value to the GPIO register (all bits) + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param value the value to write + */ + virtual void set_gpio_out(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_gpio_out(const db_unit_t unit) = 0; + + /*! + * Read the state of the GPIO pins + * If a pin is configured as an input, reads the actual value of the pin + * If a pin is configured as an output, reads the last value written to the pin + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \return the value read back + */ + virtual boost::uint32_t read_gpio(const db_unit_t unit) = 0; +}; + +}}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/gpio_core_200.cpp b/host/lib/usrp/cores/gpio_core_200.cpp index 704a71d5f..8ada95b1f 100644 --- a/host/lib/usrp/cores/gpio_core_200.cpp +++ b/host/lib/usrp/cores/gpio_core_200.cpp @@ -27,6 +27,11 @@ using namespace uhd; using namespace usrp; +template <typename T> +static void shadow_it(T &shadow, const T &value, const T &mask){ + shadow = (shadow & ~mask) | (value & mask); +} + gpio_core_200::~gpio_core_200(void){ /* NOP */ } @@ -36,13 +41,20 @@ public: gpio_core_200_impl(wb_iface::sptr iface, const size_t base, const size_t rb_addr): _iface(iface), _base(base), _rb_addr(rb_addr), _first_atr(true) { /* NOP */ } - void set_pin_ctrl(const unit_t unit, const boost::uint16_t value){ - _pin_ctrl[unit] = value; //shadow + void set_pin_ctrl(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_pin_ctrl[unit], value, mask); update(); //full update } - void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value){ - _atr_regs[unit][atr] = value; //shadow + boost::uint16_t get_pin_ctrl(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _pin_ctrl[unit]; + } + + void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_atr_regs[unit][atr], value, mask); if (_first_atr) { // To preserve legacy behavior, update all registers the first time @@ -53,20 +65,38 @@ public: update(atr); } - void set_gpio_ddr(const unit_t unit, const boost::uint16_t value){ - _gpio_ddr[unit] = value; //shadow + boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _atr_regs[unit][reg]; + } + + void set_gpio_ddr(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_gpio_ddr[unit], value, mask); _iface->poke32(REG_GPIO_DDR, //update the 32 bit register (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_RX]) << shift_by_unit(dboard_iface::UNIT_RX)) | (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_TX]) << shift_by_unit(dboard_iface::UNIT_TX)) ); } - void set_gpio_out(const unit_t unit, const boost::uint16_t value){ - _gpio_out[unit] = value; //shadow + boost::uint16_t get_gpio_ddr(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _gpio_ddr[unit]; + } + + void set_gpio_out(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_gpio_out[unit], value, mask); this->update(); //full update } + boost::uint16_t get_gpio_out(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _gpio_out[unit]; + } + boost::uint16_t read_gpio(const unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); return boost::uint16_t(_iface->peek32(_rb_addr) >> shift_by_unit(unit)); } @@ -85,26 +115,26 @@ private: } void update(void){ - update(dboard_iface::ATR_REG_IDLE); - update(dboard_iface::ATR_REG_TX_ONLY); - update(dboard_iface::ATR_REG_RX_ONLY); - update(dboard_iface::ATR_REG_FULL_DUPLEX); + update(gpio_atr::ATR_REG_IDLE); + update(gpio_atr::ATR_REG_TX_ONLY); + update(gpio_atr::ATR_REG_RX_ONLY); + update(gpio_atr::ATR_REG_FULL_DUPLEX); } void update(const atr_reg_t atr){ size_t addr; switch (atr) { - case dboard_iface::ATR_REG_IDLE: + case gpio_atr::ATR_REG_IDLE: addr = REG_GPIO_IDLE; break; - case dboard_iface::ATR_REG_TX_ONLY: + case gpio_atr::ATR_REG_TX_ONLY: addr = REG_GPIO_TX_ONLY; break; - case dboard_iface::ATR_REG_RX_ONLY: + case gpio_atr::ATR_REG_RX_ONLY: addr = REG_GPIO_RX_ONLY; break; - case dboard_iface::ATR_REG_FULL_DUPLEX: + case gpio_atr::ATR_REG_FULL_DUPLEX: addr = REG_GPIO_BOTH; break; default: @@ -144,27 +174,32 @@ public: gpio_core_200_32wo_impl(wb_iface::sptr iface, const size_t base): _iface(iface), _base(base) { + set_ddr_reg(); + } + + void set_ddr_reg(){ _iface->poke32(REG_GPIO_DDR, 0xffffffff); } + void set_atr_reg(const atr_reg_t atr, const boost::uint32_t value){ - if (atr == dboard_iface::ATR_REG_IDLE) + if (atr == gpio_atr::ATR_REG_IDLE) _iface->poke32(REG_GPIO_IDLE, value); - else if (atr == dboard_iface::ATR_REG_TX_ONLY) + else if (atr == gpio_atr::ATR_REG_TX_ONLY) _iface->poke32(REG_GPIO_TX_ONLY, value); - else if (atr == dboard_iface::ATR_REG_RX_ONLY) + else if (atr == gpio_atr::ATR_REG_RX_ONLY) _iface->poke32(REG_GPIO_RX_ONLY, value); - else if (atr == dboard_iface::ATR_REG_FULL_DUPLEX) + else if (atr == gpio_atr::ATR_REG_FULL_DUPLEX) _iface->poke32(REG_GPIO_BOTH, value); else UHD_THROW_INVALID_CODE_PATH(); } void set_all_regs(const boost::uint32_t value){ - set_atr_reg(dboard_iface::ATR_REG_IDLE, value); - set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, value); - set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, value); - set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, value); + set_atr_reg(gpio_atr::ATR_REG_IDLE, value); + set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, value); + set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, value); + set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, value); } private: diff --git a/host/lib/usrp/cores/gpio_core_200.hpp b/host/lib/usrp/cores/gpio_core_200.hpp index e22834fd9..c697f0e77 100644 --- a/host/lib/usrp/cores/gpio_core_200.hpp +++ b/host/lib/usrp/cores/gpio_core_200.hpp @@ -20,6 +20,7 @@ #include <uhd/config.hpp> #include <uhd/usrp/dboard_iface.hpp> +#include <uhd/usrp/gpio_defs.hpp> #include <boost/assign.hpp> #include <boost/cstdint.hpp> #include <boost/utility.hpp> @@ -27,28 +28,6 @@ #include <uhd/types/wb_iface.hpp> #include <map> -typedef enum { - GPIO_CTRL, - GPIO_DDR, - GPIO_OUT, - GPIO_ATR_0X, - GPIO_ATR_RX, - GPIO_ATR_TX, - GPIO_ATR_XX -} gpio_attr_t; - -typedef std::map<gpio_attr_t,std::string> gpio_attr_map_t; -static const gpio_attr_map_t gpio_attr_map = - boost::assign::map_list_of - (GPIO_CTRL, "CTRL") - (GPIO_DDR, "DDR") - (GPIO_OUT, "OUT") - (GPIO_ATR_0X, "ATR_0X") - (GPIO_ATR_RX, "ATR_RX") - (GPIO_ATR_TX, "ATR_TX") - (GPIO_ATR_XX, "ATR_XX") -; - class gpio_core_200 : boost::noncopyable{ public: typedef boost::shared_ptr<gpio_core_200> sptr; @@ -59,20 +38,32 @@ public: virtual ~gpio_core_200(void) = 0; //! makes a new GPIO core from iface and slave base - static sptr make(uhd::wb_iface::sptr iface, const size_t base, const size_t rb_addr); + static sptr make( + uhd::wb_iface::sptr iface, const size_t base, const size_t rb_addr); //! 1 = ATR - virtual void set_pin_ctrl(const unit_t unit, const boost::uint16_t value) = 0; + virtual void set_pin_ctrl( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; + + virtual boost::uint16_t get_pin_ctrl(unit_t unit) = 0; - virtual void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value) = 0; + virtual void set_atr_reg( + const unit_t unit, const atr_reg_t atr, const boost::uint16_t value, const boost::uint16_t mask) = 0; + + virtual boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg) = 0; //! 1 = OUTPUT - virtual void set_gpio_ddr(const unit_t unit, const boost::uint16_t value) = 0; + virtual void set_gpio_ddr( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; - virtual void set_gpio_out(const unit_t unit, const boost::uint16_t value) = 0; + virtual boost::uint16_t get_gpio_ddr(unit_t unit) = 0; - virtual boost::uint16_t read_gpio(const unit_t unit) = 0; + virtual void set_gpio_out( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; + virtual boost::uint16_t get_gpio_out(unit_t unit) = 0; + + virtual boost::uint16_t read_gpio(const unit_t unit) = 0; }; //! Simple wrapper for 32 bit write only @@ -86,6 +77,8 @@ public: static sptr make(uhd::wb_iface::sptr iface, const size_t); + virtual void set_ddr_reg() = 0; + virtual void set_atr_reg(const atr_reg_t atr, const boost::uint32_t value) = 0; virtual void set_all_regs(const boost::uint32_t value) = 0; diff --git a/host/lib/usrp/cores/rx_dsp_core_200.cpp b/host/lib/usrp/cores/rx_dsp_core_200.cpp index b899085c0..e51862d3b 100644 --- a/host/lib/usrp/cores/rx_dsp_core_200.cpp +++ b/host/lib/usrp/cores/rx_dsp_core_200.cpp @@ -16,6 +16,7 @@ // #include "rx_dsp_core_200.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> @@ -24,7 +25,6 @@ #include <boost/assign/list_of.hpp> #include <boost/thread/thread.hpp> //thread sleep #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/numeric/conversion/bounds.hpp> #include <algorithm> #include <cmath> @@ -223,42 +223,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling/32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.cpp b/host/lib/usrp/cores/rx_dsp_core_3000.cpp index 035bc6a3f..eedbbef95 100644 --- a/host/lib/usrp/cores/rx_dsp_core_3000.cpp +++ b/host/lib/usrp/cores/rx_dsp_core_3000.cpp @@ -16,6 +16,7 @@ // #include "rx_dsp_core_3000.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> @@ -24,7 +25,6 @@ #include <boost/assign/list_of.hpp> #include <boost/thread/thread.hpp> //thread sleep #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <algorithm> #include <cmath> @@ -69,6 +69,7 @@ public: _scaling_adjustment = 1.0; _dsp_extra_scaling = 1.0; _tick_rate = 1.0; + _dsp_freq_offset = 0.0; } ~rx_dsp_core_3000_impl(void) @@ -79,17 +80,41 @@ public: ) } - void set_mux(const std::string &mode, const bool fe_swapped, const bool invert_i, const bool invert_q){ - static const uhd::dict<std::string, boost::uint32_t> mode_to_mux = boost::assign::map_list_of - ("IQ", 0) - ("QI", FLAG_DSP_RX_MUX_SWAP_IQ) - ("I", FLAG_DSP_RX_MUX_REAL_MODE) - ("Q", FLAG_DSP_RX_MUX_SWAP_IQ | FLAG_DSP_RX_MUX_REAL_MODE) - ; - _iface->poke32(REG_DSP_RX_MUX, mode_to_mux[mode] - | (fe_swapped ? FLAG_DSP_RX_MUX_SWAP_IQ : 0) - | (invert_i ? FLAG_DSP_RX_MUX_INVERT_I : 0) - | (invert_q ? FLAG_DSP_RX_MUX_INVERT_Q : 0)); + void set_mux(const uhd::usrp::fe_connection_t& fe_conn){ + boost::uint32_t reg_val = 0; + switch (fe_conn.get_sampling_mode()) { + case uhd::usrp::fe_connection_t::REAL: + case uhd::usrp::fe_connection_t::HETERODYNE: + reg_val = FLAG_DSP_RX_MUX_REAL_MODE; + break; + default: + reg_val = 0; + break; + } + + if (fe_conn.is_iq_swapped()) reg_val |= FLAG_DSP_RX_MUX_SWAP_IQ; + if (fe_conn.is_i_inverted()) reg_val |= FLAG_DSP_RX_MUX_INVERT_I; + if (fe_conn.is_q_inverted()) reg_val |= FLAG_DSP_RX_MUX_INVERT_Q; + + _iface->poke32(REG_DSP_RX_MUX, reg_val); + + if (fe_conn.get_sampling_mode() == uhd::usrp::fe_connection_t::HETERODYNE) { + //1. Remember the sign of the IF frequency. + // It will be discarded in the next step + int if_freq_sign = boost::math::sign(fe_conn.get_if_freq()); + //2. Map IF frequency to the range [0, _tick_rate) + double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _tick_rate)); + //3. Map IF frequency to the range [-_tick_rate/2, _tick_rate/2) + // This is the aliased frequency + if (if_freq > (_tick_rate / 2.0)) { + if_freq -= _tick_rate; + } + //4. Set DSP offset to spin the signal in the opposite + // direction as the aliased frequency + _dsp_freq_offset = if_freq * (-if_freq_sign); + } else { + _dsp_freq_offset = 0.0; + } } void set_tick_rate(const double rate){ @@ -209,47 +234,18 @@ public: return _fxpt_scalar_correction*_host_extra_scaling/32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq + _dsp_freq_offset, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } uhd::meta_range_t get_freq_range(void){ - return uhd::meta_range_t(-_tick_rate/2, +_tick_rate/2, _tick_rate/std::pow(2.0, 32)); + //Too keep the DSP range symmetric about 0, we use abs(_dsp_freq_offset) + const double offset = std::abs<double>(_dsp_freq_offset); + return uhd::meta_range_t(-(_tick_rate-offset)/2, +(_tick_rate-offset)/2, _tick_rate/std::pow(2.0, 32)); } void setup(const uhd::stream_args_t &stream_args){ @@ -284,18 +280,18 @@ public: void populate_subtree(property_tree::sptr subtree) { subtree->create<meta_range_t>("rate/range") - .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, this)) + .set_publisher(boost::bind(&rx_dsp_core_3000::get_host_rates, this)) ; subtree->create<double>("rate/value") .set(DEFAULT_RATE) - .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, this, _1)) + .set_coercer(boost::bind(&rx_dsp_core_3000::set_host_rate, this, _1)) ; subtree->create<double>("freq/value") .set(DEFAULT_CORDIC_FREQ) - .coerce(boost::bind(&rx_dsp_core_3000::set_freq, this, _1)) + .set_coercer(boost::bind(&rx_dsp_core_3000::set_freq, this, _1)) ; subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, this)) + .set_publisher(boost::bind(&rx_dsp_core_3000::get_freq_range, this)) ; } @@ -305,6 +301,7 @@ private: const bool _is_b200; //TODO: Obsolete this when we switch to the new DDC on the B200 double _tick_rate, _link_rate; double _scaling_adjustment, _dsp_extra_scaling, _host_extra_scaling, _fxpt_scalar_correction; + double _dsp_freq_offset; }; rx_dsp_core_3000::sptr rx_dsp_core_3000::make(wb_iface::sptr iface, const size_t dsp_base, const bool is_b200 /* = false */) diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.hpp b/host/lib/usrp/cores/rx_dsp_core_3000.hpp index 65801de1d..41b328357 100644 --- a/host/lib/usrp/cores/rx_dsp_core_3000.hpp +++ b/host/lib/usrp/cores/rx_dsp_core_3000.hpp @@ -24,6 +24,7 @@ #include <uhd/types/stream_cmd.hpp> #include <uhd/types/wb_iface.hpp> #include <uhd/property_tree.hpp> +#include <uhd/usrp/fe_connection.hpp> #include <boost/utility.hpp> #include <boost/shared_ptr.hpp> #include <string> @@ -43,7 +44,7 @@ public: const bool is_b200 = false //TODO: Obsolete this when we switch to the new DDC on the B200 ); - virtual void set_mux(const std::string &mode, const bool fe_swapped = false, const bool invert_i = false, const bool invert_q = false) = 0; + virtual void set_mux(const uhd::usrp::fe_connection_t& fe_conn) = 0; virtual void set_tick_rate(const double rate) = 0; diff --git a/host/lib/usrp/cores/rx_frontend_core_200.cpp b/host/lib/usrp/cores/rx_frontend_core_200.cpp index 7ac920553..0a60bf87c 100644 --- a/host/lib/usrp/cores/rx_frontend_core_200.cpp +++ b/host/lib/usrp/cores/rx_frontend_core_200.cpp @@ -83,15 +83,15 @@ public: { subtree->create<std::complex<double> >("dc_offset/value") .set(DEFAULT_DC_OFFSET_VALUE) - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, this, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, this, _1)) ; subtree->create<bool>("dc_offset/enable") .set(DEFAULT_DC_OFFSET_ENABLE) - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, this, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, this, _1)) ; subtree->create<std::complex<double> >("iq_balance/value") .set(DEFAULT_IQ_BALANCE_VALUE) - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, this, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, this, _1)) ; } diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.cpp b/host/lib/usrp/cores/rx_frontend_core_3000.cpp new file mode 100644 index 000000000..23197cf5a --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_3000.cpp @@ -0,0 +1,186 @@ +// +// Copyright 2011-2012,2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "rx_frontend_core_3000.hpp" +#include "dsp_core_utils.hpp" +#include <boost/math/special_functions/round.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/bind.hpp> +#include <uhd/types/dict.hpp> + +using namespace uhd; + +#define REG_RX_FE_MAG_CORRECTION (_base + 0 ) //18 bits +#define REG_RX_FE_PHASE_CORRECTION (_base + 4 ) //18 bits +#define REG_RX_FE_OFFSET_I (_base + 8 ) //18 bits +#define REG_RX_FE_OFFSET_Q (_base + 12) //18 bits +#define REG_RX_FE_MAPPING (_base + 16) +#define REG_RX_FE_HET_CORDIC_PHASE (_base + 20) + +#define FLAG_DSP_RX_MAPPING_SWAP_IQ (1 << 0) +#define FLAG_DSP_RX_MAPPING_REAL_MODE (1 << 1) +#define FLAG_DSP_RX_MAPPING_INVERT_Q (1 << 2) +#define FLAG_DSP_RX_MAPPING_INVERT_I (1 << 3) +#define FLAG_DSP_RX_MAPPING_REAL_DECIM (1 << 4) +//#define FLAG_DSP_RX_MAPPING_RESERVED (1 << 5) +//#define FLAG_DSP_RX_MAPPING_RESERVED (1 << 6) +#define FLAG_DSP_RX_MAPPING_BYPASS_ALL (1 << 7) + +#define OFFSET_FIXED (1ul << 31) +#define OFFSET_SET (1ul << 30) +#define FLAG_MASK (OFFSET_FIXED | OFFSET_SET) + +using namespace uhd::usrp; + +static boost::uint32_t fs_to_bits(const double num, const size_t bits){ + return boost::int32_t(boost::math::round(num * (1 << (bits-1)))); +} + +rx_frontend_core_3000::~rx_frontend_core_3000(void){ + /* NOP */ +} + +const std::complex<double> rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE = std::complex<double>(0.0, 0.0); +const bool rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE = true; +const std::complex<double> rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE = std::complex<double>(0.0, 0.0); + +class rx_frontend_core_3000_impl : public rx_frontend_core_3000{ +public: + rx_frontend_core_3000_impl(wb_iface::sptr iface, const size_t base): + _i_dc_off(0), _q_dc_off(0), + _adc_rate(0.0), + _fe_conn(fe_connection_t("IQ")), + _iface(iface), _base(base) + { + //NOP + } + + void set_adc_rate(const double rate) { + _adc_rate = rate; + } + + void bypass_all(bool bypass_en) { + if (bypass_en) { + _iface->poke32(REG_RX_FE_MAPPING, FLAG_DSP_RX_MAPPING_BYPASS_ALL); + } else { + set_fe_connection(_fe_conn); + } + } + + void set_fe_connection(const fe_connection_t& fe_conn) { + boost::uint32_t mapping_reg_val = 0; + switch (fe_conn.get_sampling_mode()) { + case fe_connection_t::REAL: + case fe_connection_t::HETERODYNE: + mapping_reg_val = FLAG_DSP_RX_MAPPING_REAL_MODE|FLAG_DSP_RX_MAPPING_REAL_DECIM; + break; + default: + mapping_reg_val = 0; + break; + } + + if (fe_conn.is_iq_swapped()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_SWAP_IQ; + if (fe_conn.is_i_inverted()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_INVERT_I; + if (fe_conn.is_q_inverted()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_INVERT_Q; + + _iface->poke32(REG_RX_FE_MAPPING, mapping_reg_val); + + UHD_ASSERT_THROW(_adc_rate!=0.0) + double cordic_freq = 0.0, actual_cordic_freq = 0.0; + if (fe_conn.get_sampling_mode() == fe_connection_t::HETERODYNE) { + //1. Remember the sign of the IF frequency. + // It will be discarded in the next step + int if_freq_sign = boost::math::sign(fe_conn.get_if_freq()); + //2. Map IF frequency to the range [0, _adc_rate) + double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _adc_rate)); + //3. Map IF frequency to the range [-_adc_rate/2, _adc_rate/2) + // This is the aliased frequency + if (if_freq > (_adc_rate / 2.0)) { + if_freq -= _adc_rate; + } + //4. Set DSP offset to spin the signal in the opposite + // direction as the aliased frequency + cordic_freq = if_freq * (-if_freq_sign); + } + int32_t freq_word; + get_freq_and_freq_word(cordic_freq, _adc_rate, actual_cordic_freq, freq_word); + _iface->poke32(REG_RX_FE_HET_CORDIC_PHASE, boost::uint32_t(freq_word)); + + _fe_conn = fe_conn; + } + + void set_dc_offset_auto(const bool enb) { + _set_dc_offset(enb ? 0 : OFFSET_FIXED); + } + + std::complex<double> set_dc_offset(const std::complex<double> &off) { + static const double scaler = double(1ul << 29); + _i_dc_off = boost::math::iround(off.real()*scaler); + _q_dc_off = boost::math::iround(off.imag()*scaler); + + _set_dc_offset(OFFSET_SET | OFFSET_FIXED); + + return std::complex<double>(_i_dc_off/scaler, _q_dc_off/scaler); + } + + void _set_dc_offset(const boost::uint32_t flags) { + _iface->poke32(REG_RX_FE_OFFSET_I, flags | (_i_dc_off & ~FLAG_MASK)); + _iface->poke32(REG_RX_FE_OFFSET_Q, flags | (_q_dc_off & ~FLAG_MASK)); + } + + void set_iq_balance(const std::complex<double> &cor) { + _iface->poke32(REG_RX_FE_MAG_CORRECTION, fs_to_bits(cor.real(), 18)); + _iface->poke32(REG_RX_FE_PHASE_CORRECTION, fs_to_bits(cor.imag(), 18)); + } + + void populate_subtree(uhd::property_tree::sptr subtree) { + subtree->create<std::complex<double> >("dc_offset/value") + .set(DEFAULT_DC_OFFSET_VALUE) + .set_coercer(boost::bind(&rx_frontend_core_3000::set_dc_offset, this, _1)) + ; + subtree->create<bool>("dc_offset/enable") + .set(DEFAULT_DC_OFFSET_ENABLE) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_3000::set_dc_offset_auto, this, _1)) + ; + subtree->create<std::complex<double> >("iq_balance/value") + .set(DEFAULT_IQ_BALANCE_VALUE) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_3000::set_iq_balance, this, _1)) + ; + } + + double get_output_rate() { + switch (_fe_conn.get_sampling_mode()) { + case fe_connection_t::REAL: + case fe_connection_t::HETERODYNE: + return _adc_rate / 2; + default: + return _adc_rate; + } + return _adc_rate; + } + +private: + boost::int32_t _i_dc_off, _q_dc_off; + double _adc_rate; + fe_connection_t _fe_conn; + wb_iface::sptr _iface; + const size_t _base; +}; + +rx_frontend_core_3000::sptr rx_frontend_core_3000::make(wb_iface::sptr iface, const size_t base){ + return sptr(new rx_frontend_core_3000_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.hpp b/host/lib/usrp/cores/rx_frontend_core_3000.hpp new file mode 100644 index 000000000..baa58331e --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_3000.hpp @@ -0,0 +1,69 @@ +// +// Copyright 2011,2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/fe_connection.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <complex> +#include <string> + +class rx_frontend_core_3000 : boost::noncopyable{ +public: + static const std::complex<double> DEFAULT_DC_OFFSET_VALUE; + static const bool DEFAULT_DC_OFFSET_ENABLE; + static const std::complex<double> DEFAULT_IQ_BALANCE_VALUE; + + typedef boost::shared_ptr<rx_frontend_core_3000> sptr; + + virtual ~rx_frontend_core_3000(void) = 0; + + static sptr make(uhd::wb_iface::sptr iface, const size_t base); + + /*! Set the input sampling rate (i.e. ADC rate) + */ + virtual void set_adc_rate(const double rate) = 0; + + virtual void bypass_all(bool bypass_en) = 0; + + virtual void set_fe_connection(const uhd::usrp::fe_connection_t& fe_conn) = 0; + + virtual void set_dc_offset_auto(const bool enb) = 0; + + virtual std::complex<double> set_dc_offset(const std::complex<double> &off) = 0; + + virtual void set_iq_balance(const std::complex<double> &cor) = 0; + + virtual void populate_subtree(uhd::property_tree::sptr subtree) = 0; + + /*! Return the sampling rate at the output + * + * In real mode, the frontend core will decimate the sampling rate by a + * factor of 2. + * + * \returns RX sampling rate + */ + virtual double get_output_rate(void) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/rx_vita_core_3000.cpp b/host/lib/usrp/cores/rx_vita_core_3000.cpp index f61da7cc3..54c57c2d5 100644 --- a/host/lib/usrp/cores/rx_vita_core_3000.cpp +++ b/host/lib/usrp/cores/rx_vita_core_3000.cpp @@ -20,6 +20,8 @@ #include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/tuple/tuple.hpp> +#include <boost/date_time.hpp> +#include <boost/thread.hpp> #define REG_FRAMER_MAXLEN _base + 4*4 + 0 #define REG_FRAMER_SID _base + 4*4 + 4 @@ -63,13 +65,26 @@ struct rx_vita_core_3000_impl : rx_vita_core_3000 void configure_flow_control(const size_t window_size) { + // The window needs to be disabled in the case where this object is + // uncleanly destroyed and the FC window is left enabled + _iface->poke32(REG_FC_ENABLE, 0); + + // Sleep for a large amount of time to allow the source flow control + // module in the FPGA to flush all the packets buffered upstream. + // At 1 ms * 200 MHz = 200k cycles, 8 bytes * 200k cycles = 1.6 MB + // of flushed data, when the typical amount of data buffered + // is on the order of kilobytes + boost::this_thread::sleep(boost::posix_time::milliseconds(1.0)); + _iface->poke32(REG_FC_WINDOW, window_size-1); _iface->poke32(REG_FC_ENABLE, window_size?1:0); } void clear(void) { - this->configure_flow_control(0); //disable fc + // FC should never be disabled, this will actually become + // impossible in the future + //this->configure_flow_control(0); //disable fc } void set_nsamps_per_packet(const size_t nsamps) diff --git a/host/lib/usrp/cores/spi_core_3000.cpp b/host/lib/usrp/cores/spi_core_3000.cpp index 0656d910a..01df71cec 100644 --- a/host/lib/usrp/cores/spi_core_3000.cpp +++ b/host/lib/usrp/cores/spi_core_3000.cpp @@ -20,9 +20,10 @@ #include <uhd/utils/msg.hpp> #include <boost/thread/thread.hpp> //sleep -#define SPI_DIV _base + 0 -#define SPI_CTRL _base + 4 -#define SPI_DATA _base + 8 +#define SPI_DIV _base + 0 +#define SPI_CTRL _base + 4 +#define SPI_DATA _base + 8 +#define SPI_SHUTDOWN _base + 12 using namespace uhd; @@ -34,7 +35,7 @@ class spi_core_3000_impl : public spi_core_3000 { public: spi_core_3000_impl(wb_iface::sptr iface, const size_t base, const size_t readback): - _iface(iface), _base(base), _readback(readback), _ctrl_word_cache(0) + _iface(iface), _base(base), _readback(readback), _ctrl_word_cache(0), _divider_cache(0) { this->set_divider(30); } @@ -46,7 +47,21 @@ public: size_t num_bits, bool readback ){ - boost::mutex::scoped_lock lock(_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + + //load SPI divider + size_t spi_divider = _div; + if (config.use_custom_divider) { + //The resulting SPI frequency will be f_system/(2*(divider+1)) + //This math ensures the frequency will be equal to or less than the target + spi_divider = (config.divider-1)/2; + } + + //conditionally send SPI divider + if (spi_divider != _divider_cache) { + _iface->poke32(SPI_DIV, spi_divider); + _divider_cache = spi_divider; + } //load control word boost::uint32_t ctrl_word = 0; @@ -55,17 +70,16 @@ public: if (config.mosi_edge == spi_config_t::EDGE_FALL) ctrl_word |= (1 << 31); if (config.miso_edge == spi_config_t::EDGE_RISE) ctrl_word |= (1 << 30); - //load data word (must be in upper bits) - const boost::uint32_t data_out = data << (32 - num_bits); - //conditionally send control word if (_ctrl_word_cache != ctrl_word) { - _iface->poke32(SPI_DIV, _div); _iface->poke32(SPI_CTRL, ctrl_word); _ctrl_word_cache = ctrl_word; } + //load data word (must be in upper bits) + const boost::uint32_t data_out = data << (32 - num_bits); + //send data word _iface->poke32(SPI_DATA, data_out); @@ -78,6 +92,17 @@ public: return 0; } + void set_shutdown(const bool shutdown) + { + _shutdown_cache = shutdown; + _iface->poke32(SPI_SHUTDOWN, _shutdown_cache); + } + + bool get_shutdown() + { + return(_shutdown_cache); + } + void set_divider(const double div) { _div = size_t((div/2) - 0.5); @@ -89,8 +114,10 @@ private: const size_t _base; const size_t _readback; boost::uint32_t _ctrl_word_cache; + bool _shutdown_cache; boost::mutex _mutex; size_t _div; + size_t _divider_cache; }; spi_core_3000::sptr spi_core_3000::make(wb_iface::sptr iface, const size_t base, const size_t readback) diff --git a/host/lib/usrp/cores/spi_core_3000.hpp b/host/lib/usrp/cores/spi_core_3000.hpp index 6a439772e..8d5177196 100644 --- a/host/lib/usrp/cores/spi_core_3000.hpp +++ b/host/lib/usrp/cores/spi_core_3000.hpp @@ -36,6 +36,13 @@ public: //! Set the spi clock divider to something usable virtual void set_divider(const double div) = 0; + + //! Place SPI core in shutdown mode. All attempted SPI transactions are dropped by + // the core. + virtual void set_shutdown(const bool shutdown) = 0; + + //! Get state of shutdown register + virtual bool get_shutdown() = 0; }; #endif /* INCLUDED_LIBUHD_USRP_SPI_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/tx_dsp_core_200.cpp b/host/lib/usrp/cores/tx_dsp_core_200.cpp index 2ef9f4406..4c456a10d 100644 --- a/host/lib/usrp/cores/tx_dsp_core_200.cpp +++ b/host/lib/usrp/cores/tx_dsp_core_200.cpp @@ -16,13 +16,13 @@ // #include "tx_dsp_core_200.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> #include <uhd/utils/msg.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/thread/thread.hpp> //sleep #include <algorithm> #include <cmath> @@ -163,42 +163,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling*32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } diff --git a/host/lib/usrp/cores/tx_dsp_core_3000.cpp b/host/lib/usrp/cores/tx_dsp_core_3000.cpp index 7e447ae7d..3889bbdc4 100644 --- a/host/lib/usrp/cores/tx_dsp_core_3000.cpp +++ b/host/lib/usrp/cores/tx_dsp_core_3000.cpp @@ -16,13 +16,13 @@ // #include "tx_dsp_core_3000.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> #include <uhd/utils/msg.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/thread/thread.hpp> //sleep #include <algorithm> #include <cmath> @@ -136,42 +136,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling*32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq) { + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } @@ -211,18 +180,18 @@ public: void populate_subtree(property_tree::sptr subtree) { subtree->create<meta_range_t>("rate/range") - .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, this)) + .set_publisher(boost::bind(&tx_dsp_core_3000::get_host_rates, this)) ; subtree->create<double>("rate/value") .set(DEFAULT_RATE) - .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, this, _1)) + .set_coercer(boost::bind(&tx_dsp_core_3000::set_host_rate, this, _1)) ; subtree->create<double>("freq/value") .set(DEFAULT_CORDIC_FREQ) - .coerce(boost::bind(&tx_dsp_core_3000::set_freq, this, _1)) + .set_coercer(boost::bind(&tx_dsp_core_3000::set_freq, this, _1)) ; subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, this)) + .set_publisher(boost::bind(&tx_dsp_core_3000::get_freq_range, this)) ; } diff --git a/host/lib/usrp/cores/tx_frontend_core_200.cpp b/host/lib/usrp/cores/tx_frontend_core_200.cpp index 0fa028571..be4f77f39 100644 --- a/host/lib/usrp/cores/tx_frontend_core_200.cpp +++ b/host/lib/usrp/cores/tx_frontend_core_200.cpp @@ -79,11 +79,11 @@ public: { subtree->create< std::complex<double> >("dc_offset/value") .set(DEFAULT_DC_OFFSET_VALUE) - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, this, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, this, _1)) ; subtree->create< std::complex<double> >("iq_balance/value") .set(DEFAULT_IQ_BALANCE_VALUE) - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, this, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, this, _1)) ; } diff --git a/host/lib/usrp/cores/tx_vita_core_3000.cpp b/host/lib/usrp/cores/tx_vita_core_3000.cpp index 71a2b7e21..c76b384d9 100644 --- a/host/lib/usrp/cores/tx_vita_core_3000.cpp +++ b/host/lib/usrp/cores/tx_vita_core_3000.cpp @@ -18,9 +18,11 @@ #include "tx_vita_core_3000.hpp" #include <uhd/utils/safe_call.hpp> -#define REG_CTRL_ERROR_POLICY _base + 0 -#define REG_DEFRAMER_CYCLE_FC_UPS _base + 2*4 + 0 -#define REG_DEFRAMER_PACKET_FC_UPS _base + 2*4 + 4 +#define REG_CTRL_ERROR_POLICY (_base + 0) +#define REG_FC_PRE_RADIO_RESP_BASE (_base + 2*4) +#define REG_FC_PRE_FIFO_RESP_BASE (_base + 4*4) +#define REG_CTRL_FC_CYCLE_OFFSET (0*4) +#define REG_CTRL_FC_PACKET_OFFSET (1*4) using namespace uhd; @@ -32,12 +34,22 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 { tx_vita_core_3000_impl( wb_iface::sptr iface, - const size_t base + const size_t base, + fc_monitor_loc fc_location ): _iface(iface), - _base(base) + _base(base), + _fc_base((fc_location==FC_PRE_RADIO or fc_location==FC_DEFAULT) ? + REG_FC_PRE_RADIO_RESP_BASE : REG_FC_PRE_FIFO_RESP_BASE), + _fc_location(fc_location) { - this->set_tick_rate(1); //init to non zero + if (fc_location != FC_DEFAULT) { + //Turn off the other FC monitoring module + const size_t other_fc_base = (fc_location==FC_PRE_RADIO) ? + REG_FC_PRE_FIFO_RESP_BASE : REG_FC_PRE_RADIO_RESP_BASE; + _iface->poke32(other_fc_base + REG_CTRL_FC_CYCLE_OFFSET, 0); + _iface->poke32(other_fc_base + REG_CTRL_FC_PACKET_OFFSET, 0); + } this->set_underflow_policy("next_packet"); this->clear(); } @@ -56,11 +68,6 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 this->set_underflow_policy(_policy); //clears the seq } - void set_tick_rate(const double rate) - { - _tick_rate = rate; - } - void set_underflow_policy(const std::string &policy) { if (policy == "next_packet") @@ -89,23 +96,35 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 void configure_flow_control(const size_t cycs_per_up, const size_t pkts_per_up) { - if (cycs_per_up == 0) _iface->poke32(REG_DEFRAMER_CYCLE_FC_UPS, 0); - else _iface->poke32(REG_DEFRAMER_CYCLE_FC_UPS, (1 << 31) | ((cycs_per_up) & 0xffffff)); + if (cycs_per_up == 0) _iface->poke32(_fc_base + REG_CTRL_FC_CYCLE_OFFSET, 0); + else _iface->poke32(_fc_base + REG_CTRL_FC_CYCLE_OFFSET, (1 << 31) | ((cycs_per_up) & 0xffffff)); - if (pkts_per_up == 0) _iface->poke32(REG_DEFRAMER_PACKET_FC_UPS, 0); - else _iface->poke32(REG_DEFRAMER_PACKET_FC_UPS, (1 << 31) | ((pkts_per_up) & 0xffff)); + if (pkts_per_up == 0) _iface->poke32(_fc_base + REG_CTRL_FC_PACKET_OFFSET, 0); + else _iface->poke32(_fc_base + REG_CTRL_FC_PACKET_OFFSET, (1 << 31) | ((pkts_per_up) & 0xffff)); } - wb_iface::sptr _iface; - const size_t _base; - double _tick_rate; - std::string _policy; + wb_iface::sptr _iface; + const size_t _base; + const size_t _fc_base; + std::string _policy; + fc_monitor_loc _fc_location; + }; tx_vita_core_3000::sptr tx_vita_core_3000::make( wb_iface::sptr iface, + const size_t base, + fc_monitor_loc fc_location +) +{ + return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base, fc_location)); +} + +tx_vita_core_3000::sptr tx_vita_core_3000::make_no_radio_buff( + wb_iface::sptr iface, const size_t base ) { - return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base)); + //No internal radio buffer so only pre-radio monitoring is supported. + return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base, FC_DEFAULT)); } diff --git a/host/lib/usrp/cores/tx_vita_core_3000.hpp b/host/lib/usrp/cores/tx_vita_core_3000.hpp index 4c0052d4f..bd0f20ba4 100644 --- a/host/lib/usrp/cores/tx_vita_core_3000.hpp +++ b/host/lib/usrp/cores/tx_vita_core_3000.hpp @@ -32,17 +32,27 @@ class tx_vita_core_3000 : boost::noncopyable public: typedef boost::shared_ptr<tx_vita_core_3000> sptr; + enum fc_monitor_loc { + FC_DEFAULT, + FC_PRE_RADIO, + FC_PRE_FIFO + }; + virtual ~tx_vita_core_3000(void) = 0; static sptr make( uhd::wb_iface::sptr iface, + const size_t base, + fc_monitor_loc fc_location = FC_PRE_RADIO + ); + + static sptr make_no_radio_buff( + uhd::wb_iface::sptr iface, const size_t base ); virtual void clear(void) = 0; - virtual void set_tick_rate(const double rate) = 0; - virtual void setup(const uhd::stream_args_t &stream_args) = 0; virtual void configure_flow_control(const size_t cycs_per_up, const size_t pkts_per_up) = 0; diff --git a/host/lib/usrp/cores/user_settings_core_3000.cpp b/host/lib/usrp/cores/user_settings_core_3000.cpp new file mode 100644 index 000000000..549264f57 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_3000.cpp @@ -0,0 +1,85 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "user_settings_core_3000.hpp" +#include <uhd/exception.hpp> +#include <boost/thread/thread.hpp> + +using namespace uhd; + +#define REG_USER_SR_ADDR _sr_base_addr + 0 +#define REG_USER_SR_DATA _sr_base_addr + 4 +#define REG_USER_RB_ADDR _sr_base_addr + 8 + +class user_settings_core_3000_impl : public user_settings_core_3000 { +public: + user_settings_core_3000_impl( + wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr): + _iface(iface), _sr_base_addr(sr_base_addr), _rb_reg_addr(rb_reg_addr) + { + } + + void poke64(const wb_addr_type offset, const boost::uint64_t value) + { + if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("poke64: Incorrect address alignment"); + poke32(offset, static_cast<boost::uint32_t>(value)); + poke32(offset + 4, static_cast<boost::uint32_t>(value >> 32)); + } + + boost::uint64_t peek64(const wb_addr_type offset) + { + if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("peek64: Incorrect address alignment"); + + boost::unique_lock<boost::mutex> lock(_mutex); + _iface->poke32(REG_USER_RB_ADDR, offset >> 3); //Translate byte offset to 64-bit offset + return _iface->peek64(_rb_reg_addr); + } + + void poke32(const wb_addr_type offset, const boost::uint32_t value) + { + if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("poke32: Incorrect address alignment"); + + boost::unique_lock<boost::mutex> lock(_mutex); + _iface->poke32(REG_USER_SR_ADDR, offset >> 2); //Translate byte offset to 64-bit offset + _iface->poke32(REG_USER_SR_DATA, value); + } + + boost::uint32_t peek32(const wb_addr_type offset) + { + if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("peek32: Incorrect address alignment"); + + boost::uint64_t value = peek64((offset >> 3) << 3); + if ((offset & 0x7) == 0) { + return static_cast<boost::uint32_t>(value); + } else { + return static_cast<boost::uint32_t>(value >> 32); + } + } + +private: + wb_iface::sptr _iface; + const wb_addr_type _sr_base_addr; + const wb_addr_type _rb_reg_addr; + boost::mutex _mutex; +}; + +wb_iface::sptr user_settings_core_3000::make(wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr) +{ + return sptr(new user_settings_core_3000_impl(iface, sr_base_addr, rb_reg_addr)); +} diff --git a/host/lib/usrp/cores/user_settings_core_3000.hpp b/host/lib/usrp/cores/user_settings_core_3000.hpp new file mode 100644 index 000000000..6891b9e81 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_3000.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/wb_iface.hpp> + +class user_settings_core_3000 : public uhd::wb_iface { +public: + virtual ~user_settings_core_3000() {} + + static sptr make( + wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr); +}; + +#endif /* INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP */ diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 6cebecdbf..183496734 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -39,5 +39,9 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.cpp ${CMAKE_CURRENT_SOURCE_DIR}/db_e3x0.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_experts.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_gain_tables.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_twinrx.cpp ) diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp index 2b30dab52..941a80ea4 100644 --- a/host/lib/usrp/dboard/db_basic_and_lf.cpp +++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp @@ -50,7 +50,7 @@ static const uhd::dict<std::string, double> subdev_bandwidth_scalar = map_list_o class basic_rx : public rx_dboard_base{ public: basic_rx(ctor_args_t args, double max_freq); - ~basic_rx(void); + virtual ~basic_rx(void); private: double _max_freq; @@ -59,7 +59,7 @@ private: class basic_tx : public tx_dboard_base{ public: basic_tx(ctor_args_t args, double max_freq); - ~basic_tx(void); + virtual ~basic_tx(void); private: double _max_freq; @@ -121,7 +121,7 @@ basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){ this->get_rx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_rx_subtree()->create<double>("freq/value") - .publish(&always_zero_freq); + .set_publisher(&always_zero_freq); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(freq_range_t(-_max_freq, +_max_freq)); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -176,7 +176,7 @@ basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){ this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_tx_subtree()->create<double>("freq/value") - .publish(&always_zero_freq); + .set_publisher(&always_zero_freq); this->get_tx_subtree()->create<meta_range_t>("freq/range") .set(freq_range_t(-_max_freq, +_max_freq)); this->get_tx_subtree()->create<std::string>("antenna/value") diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp index 9d04d8e16..6e1846fb8 100644 --- a/host/lib/usrp/dboard/db_dbsrx.cpp +++ b/host/lib/usrp/dboard/db_dbsrx.cpp @@ -66,7 +66,7 @@ static const double usrp1_gpio_clock_rate_limit = 4e6; class dbsrx : public rx_dboard_base{ public: dbsrx(ctor_args_t args); - ~dbsrx(void); + virtual ~dbsrx(void); private: double _lo_freq; @@ -204,16 +204,16 @@ dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("DBSRX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&dbsrx::get_locked, this)); + .set_publisher(boost::bind(&dbsrx::get_locked, this)); BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&dbsrx::set_gain, this, _1, name)) + .set_coercer(boost::bind(&dbsrx::set_gain, this, _1, name)) .set(dbsrx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(dbsrx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&dbsrx::set_lo_freq, this, _1)); + .set_coercer(boost::bind(&dbsrx::set_lo_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(dbsrx_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -227,7 +227,7 @@ dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&dbsrx::set_bandwidth, this, _1)); + .set_coercer(boost::bind(&dbsrx::set_bandwidth, this, _1)); this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(dbsrx_bandwidth_range); diff --git a/host/lib/usrp/dboard/db_dbsrx2.cpp b/host/lib/usrp/dboard/db_dbsrx2.cpp index 1debe3c8f..11d706ed6 100644 --- a/host/lib/usrp/dboard/db_dbsrx2.cpp +++ b/host/lib/usrp/dboard/db_dbsrx2.cpp @@ -60,7 +60,7 @@ static const uhd::dict<std::string, gain_range_t> dbsrx2_gain_ranges = map_list_ class dbsrx2 : public rx_dboard_base{ public: dbsrx2(ctor_args_t args); - ~dbsrx2(void); + virtual ~dbsrx2(void); private: double _lo_freq; @@ -191,16 +191,16 @@ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("DBSRX2"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&dbsrx2::get_locked, this)); + .set_publisher(boost::bind(&dbsrx2::get_locked, this)); BOOST_FOREACH(const std::string &name, dbsrx2_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&dbsrx2::set_gain, this, _1, name)) + .set_coercer(boost::bind(&dbsrx2::set_gain, this, _1, name)) .set(dbsrx2_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(dbsrx2_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&dbsrx2::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&dbsrx2::set_lo_freq, this, _1)) .set(dbsrx2_freq_range.start()); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(dbsrx2_freq_range); @@ -218,7 +218,7 @@ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&dbsrx2::set_bandwidth, this, _1)) + .set_coercer(boost::bind(&dbsrx2::set_bandwidth, this, _1)) .set(2.0*(0.8*codec_rate/2.0)); //bandwidth in lowpass, convert to complex bandpass //default to anti-alias at different codec_rate this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") diff --git a/host/lib/usrp/dboard/db_e3x0.cpp b/host/lib/usrp/dboard/db_e3x0.cpp index 523927d49..c7cc52d73 100644 --- a/host/lib/usrp/dboard/db_e3x0.cpp +++ b/host/lib/usrp/dboard/db_e3x0.cpp @@ -29,7 +29,7 @@ class e310_dboard : public xcvr_dboard_base{ public: e310_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} - ~e310_dboard(void) {} + virtual ~e310_dboard(void) {} }; /*********************************************************************** @@ -40,7 +40,7 @@ class e300_dboard : public xcvr_dboard_base{ public: e300_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} - ~e300_dboard(void) {} + virtual ~e300_dboard(void) {} }; /*********************************************************************** diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp index 1342c913d..dbb1600ec 100644 --- a/host/lib/usrp/dboard/db_rfx.cpp +++ b/host/lib/usrp/dboard/db_rfx.cpp @@ -78,7 +78,7 @@ public: const freq_range_t &freq_range, bool rx_div2, bool tx_div2 ); - ~rfx_xcvr(void); + virtual ~rfx_xcvr(void); private: const freq_range_t _freq_range; @@ -183,20 +183,20 @@ rfx_xcvr::rfx_xcvr( else this->get_rx_subtree()->create<std::string>("name").set("RFX RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name)) .set(_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((_freq_range.start() + _freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&rfx_xcvr::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&rfx_xcvr::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(rfx_rx_antennas); @@ -219,14 +219,14 @@ rfx_xcvr::rfx_xcvr( else this->get_tx_subtree()->create<std::string>("name").set("RFX TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((_freq_range.start() + _freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(_freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0)); + .add_coerced_subscriber(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(rfx_tx_antennas); this->get_tx_subtree()->create<std::string>("connection").set("IQ"); @@ -248,15 +248,15 @@ rfx_xcvr::rfx_xcvr( this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, output_enables); //setup the tx atr (this does not change with antenna) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); //setup the rx atr (this does not change with antenna) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); } rfx_xcvr::~rfx_xcvr(void){ @@ -272,14 +272,14 @@ void rfx_xcvr::set_rx_ant(const std::string &ant){ //set the rx atr regs that change with antenna setting if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX ); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX ); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ((ant == "TX/RX")? ANT_TXRX : ANT_RX2)); } @@ -292,12 +292,12 @@ void rfx_xcvr::set_tx_ant(const std::string &ant){ //set the tx atr regs that change with antenna setting if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); } } diff --git a/host/lib/usrp/dboard/db_sbx_common.cpp b/host/lib/usrp/dboard/db_sbx_common.cpp index ce5166c4c..be02cf77a 100644 --- a/host/lib/usrp/dboard/db_sbx_common.cpp +++ b/host/lib/usrp/dboard/db_sbx_common.cpp @@ -159,20 +159,20 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ else this->get_rx_subtree()->create<std::string>("name").set("SBX/CBX RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, sbx_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name)) .set(sbx_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(sbx_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((freq_range.start() + freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&sbx_xcvr::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&sbx_xcvr::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(sbx_rx_antennas); @@ -200,20 +200,20 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ else this->get_tx_subtree()->create<std::string>("name").set("SBX/CBX TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); BOOST_FOREACH(const std::string &name, sbx_tx_gain_ranges.keys()){ this->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name)) .set(sbx_tx_gain_ranges[name].start()); this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(sbx_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((freq_range.start() + freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&sbx_xcvr::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&sbx_xcvr::set_tx_ant, this, _1)) .set(sbx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(sbx_tx_antennas); @@ -237,8 +237,8 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - //flash LEDs - flash_leds(); + //Initialize ATR registers after direction and pin ctrl configuration + update_atr(); UHD_LOGV(often) << boost::format( "SBX GPIO Direction: RX: 0x%08x, TX: 0x%08x" @@ -265,39 +265,39 @@ void sbx_xcvr::update_atr(void){ //setup the tx atr (this does not change with antenna) this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, 0 | tx_lo_lpf_en \ + gpio_atr::ATR_REG_IDLE, 0 | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_XX | TX_MIXER_DIS); //setup the rx atr (this does not change with antenna) this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_XX | RX_MIXER_DIS); //set the RX atr regs that change with antenna setting this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \ | ((_rx_ant != "RX2")? ANT_TXRX : ANT_RX2)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_DIS \ | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \ | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2)); //set the TX atr regs that change with antenna setting this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \ + gpio_atr::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_DIS \ | ((_rx_ant != "RX2")? ANT_RX : ANT_TX)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \ + gpio_atr::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \ | ((_tx_ant == "CAL")? ANT_RX : ANT_TX)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \ + gpio_atr::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \ | ((_tx_ant == "CAL")? ANT_RX : ANT_TX)); } @@ -352,45 +352,3 @@ sensor_value_t sbx_xcvr::get_locked(dboard_iface::unit_t unit) { return sensor_value_t("LO", locked, "locked", "unlocked"); } - - -void sbx_xcvr::flash_leds(void) { - //Remove LED gpios from ATR control temporarily and set to outputs - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|RX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, \ - TX_LED_TXRX|TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, \ - RX_LED_RX1RX2|RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - //Put LED gpios back in ATR control and update atr - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); -} - diff --git a/host/lib/usrp/dboard/db_sbx_common.hpp b/host/lib/usrp/dboard/db_sbx_common.hpp index 4800bbd83..c0e29f263 100644 --- a/host/lib/usrp/dboard/db_sbx_common.hpp +++ b/host/lib/usrp/dboard/db_sbx_common.hpp @@ -16,10 +16,15 @@ // #include <uhd/types/device_addr.hpp> - -#include "adf435x_common.hpp" +#include "adf435x.hpp" #include "max287x.hpp" +// LO Related +#define ADF435X_CE (1 << 3) +#define ADF435X_PDBRF (1 << 2) +#define ADF435X_MUXOUT (1 << 1) // INPUT!!! +#define LOCKDET_MASK (1 << 0) // INPUT!!! + // Common IO Pins #define LO_LPF_EN (1 << 15) @@ -36,6 +41,8 @@ #define RX_LED_LD (1 << 6) // LED for RX Lock Detect #define DIS_POWER_RX (1 << 5) // on UNIT_RX, 0 powers up RX #define RX_DISABLE (1 << 4) // on UNIT_RX, 1 disables RX Mixer and Baseband +#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control +#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control // TX Attenuator Pins #define TX_ATTN_SHIFT 8 // lsb of TX Attenuator Control @@ -184,12 +191,16 @@ protected: class sbx_version3 : public sbx_versionx { public: sbx_version3(sbx_xcvr *_self_sbx_xcvr); - ~sbx_version3(void); + virtual ~sbx_version3(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); /*! This is the registered instance of the wrapper class, sbx_base. */ sbx_xcvr *self_base; + private: + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; /*! @@ -200,12 +211,16 @@ protected: class sbx_version4 : public sbx_versionx { public: sbx_version4(sbx_xcvr *_self_sbx_xcvr); - ~sbx_version4(void); + virtual ~sbx_version4(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); /*! This is the registered instance of the wrapper class, sbx_base. */ sbx_xcvr *self_base; + private: + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; /*! @@ -218,7 +233,7 @@ protected: class cbx : public sbx_versionx { public: cbx(sbx_xcvr *_self_sbx_xcvr); - ~cbx(void); + virtual ~cbx(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); diff --git a/host/lib/usrp/dboard/db_sbx_version3.cpp b/host/lib/usrp/dboard/db_sbx_version3.cpp index b848097d1..76ad7b04f 100644 --- a/host/lib/usrp/dboard/db_sbx_version3.cpp +++ b/host/lib/usrp/dboard/db_sbx_version3.cpp @@ -16,9 +16,7 @@ // -#include "adf4350_regs.hpp" #include "db_sbx_common.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <boost/algorithm/string.hpp> @@ -32,12 +30,21 @@ using namespace boost::assign; sbx_xcvr::sbx_version3::sbx_version3(sbx_xcvr *_self_sbx_xcvr) { //register the handle to our base SBX class self_base = _self_sbx_xcvr; + _txlo = adf435x_iface::make_adf4350(boost::bind(&sbx_xcvr::sbx_version3::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&sbx_xcvr::sbx_version3::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); } sbx_xcvr::sbx_version3::~sbx_version3(void){ /* NOP */ } +void sbx_xcvr::sbx_version3::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) +{ + BOOST_FOREACH(boost::uint32_t reg, regs) + { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} /*********************************************************************** * Tuning @@ -57,95 +64,27 @@ double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); - //clip the input - target_freq = sbx_freq_range.clip(target_freq); - - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4350_regs_t::prescaler_t prescaler = target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; - - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); //INT is a 12-bit field - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); - tuning_constraints.feedback_after_divider = true; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + lo_iface->set_reference_freq(self_base->get_iface()->get_clock_rate(unit)); - double actual_freq; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - target_freq, self_base->get_iface()->get_clock_rate(unit), - tuning_constraints, actual_freq); + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(target_freq > 3e9 ? adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - //load the register values - adf4350_regs_t regs; + //Configure the LO + double actual_freq = 0.0; + actual_freq = lo_iface->set_frequency(sbx_freq_range.clip(target_freq), is_int_n); - if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) - regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "SBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "SBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_sbx_version4.cpp b/host/lib/usrp/dboard/db_sbx_version4.cpp index 8f7e747bc..639bce250 100644 --- a/host/lib/usrp/dboard/db_sbx_version4.cpp +++ b/host/lib/usrp/dboard/db_sbx_version4.cpp @@ -16,9 +16,7 @@ // -#include "adf4351_regs.hpp" #include "db_sbx_common.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <boost/algorithm/string.hpp> @@ -32,6 +30,8 @@ using namespace boost::assign; sbx_xcvr::sbx_version4::sbx_version4(sbx_xcvr *_self_sbx_xcvr) { //register the handle to our base SBX class self_base = _self_sbx_xcvr; + _txlo = adf435x_iface::make_adf4351(boost::bind(&sbx_xcvr::sbx_version4::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4351(boost::bind(&sbx_xcvr::sbx_version4::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); } @@ -39,6 +39,14 @@ sbx_xcvr::sbx_version4::~sbx_version4(void){ /* NOP */ } +void sbx_xcvr::sbx_version4::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) +{ + BOOST_FOREACH(boost::uint32_t reg, regs) + { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} + /*********************************************************************** * Tuning @@ -58,99 +66,27 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); - //clip the input - target_freq = sbx_freq_range.clip(target_freq); - - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4351_regs_t::PRESCALER_4_5 - (1,75) //adf4351_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) - ; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4351_regs_t::prescaler_t prescaler = target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; - - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); //INT is a 12-bit field - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 64); - tuning_constraints.feedback_after_divider = true; - - double actual_freq; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - target_freq, self_base->get_iface()->get_clock_rate(unit), - tuning_constraints, actual_freq); - - //load the register values - adf4351_regs_t regs; - - if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) - regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4351_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4351_regs_t::LDF_INT_N : - adf4351_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - boost::uint16_t rx_id = self_base->get_rx_id().to_uint16(); - std::string board_name = (rx_id == 0x0083) ? "SBX-120" : "SBX"; - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "%s SPI Reg (0x%02x): 0x%08x" - ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + lo_iface->set_reference_freq(self_base->get_iface()->get_clock_rate(unit)); + + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(target_freq > 3.6e9 ? adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + + //Configure the LO + double actual_freq = 0.0; + actual_freq = lo_iface->set_frequency(sbx_freq_range.clip(target_freq), is_int_n); + + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "%s tune: actual frequency %f MHz" - ) % board_name.c_str() % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp index e9f60f765..0f84cd68a 100644 --- a/host/lib/usrp/dboard/db_tvrx.cpp +++ b/host/lib/usrp/dboard/db_tvrx.cpp @@ -134,7 +134,7 @@ static const double reference_freq = 4.0e6; class tvrx : public rx_dboard_base{ public: tvrx(ctor_args_t args); - ~tvrx(void); + virtual ~tvrx(void); private: uhd::dict<std::string, double> _gains; @@ -190,12 +190,12 @@ tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&tvrx::set_gain, this, _1, name)); + .set_coercer(boost::bind(&tvrx::set_gain, this, _1, name)); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(get_tvrx_gain_ranges()[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&tvrx::set_freq, this, _1)); + .set_coercer(boost::bind(&tvrx::set_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(tvrx_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") diff --git a/host/lib/usrp/dboard/db_tvrx2.cpp b/host/lib/usrp/dboard/db_tvrx2.cpp index ccbac0b5d..6f0604f72 100644 --- a/host/lib/usrp/dboard/db_tvrx2.cpp +++ b/host/lib/usrp/dboard/db_tvrx2.cpp @@ -751,7 +751,7 @@ static const uhd::dict<std::string, gain_range_t> tvrx2_gain_ranges = map_list_o class tvrx2 : public rx_dboard_base{ public: tvrx2(ctor_args_t args); - ~tvrx2(void); + virtual ~tvrx2(void); private: double _freq_scalar; @@ -957,19 +957,19 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("TVRX2"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&tvrx2::get_locked, this)); + .set_publisher(boost::bind(&tvrx2::get_locked, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&tvrx2::get_rssi, this)); + .set_publisher(boost::bind(&tvrx2::get_rssi, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/temperature") - .publish(boost::bind(&tvrx2::get_temp, this)); + .set_publisher(boost::bind(&tvrx2::get_temp, this)); BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&tvrx2::set_gain, this, _1, name)); + .set_coercer(boost::bind(&tvrx2::set_gain, this, _1, name)); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(tvrx2_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&tvrx2::set_lo_freq, this, _1)); + .set_coercer(boost::bind(&tvrx2::set_lo_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(tvrx2_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -979,12 +979,12 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("connection") .set(tvrx2_sd_name_to_conn[get_subdev_name()]); this->get_rx_subtree()->create<bool>("enabled") - .coerce(boost::bind(&tvrx2::set_enabled, this, _1)) + .set_coercer(boost::bind(&tvrx2::set_enabled, this, _1)) .set(_enabled); this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&tvrx2::set_bandwidth, this, _1)) + .set_coercer(boost::bind(&tvrx2::set_bandwidth, this, _1)) .set(_bandwidth); this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(tvrx2_bandwidth_range); diff --git a/host/lib/usrp/dboard/db_twinrx.cpp b/host/lib/usrp/dboard/db_twinrx.cpp new file mode 100644 index 000000000..e960f134f --- /dev/null +++ b/host/lib/usrp/dboard/db_twinrx.cpp @@ -0,0 +1,337 @@ +// +// Copyright 2014-15 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "twinrx/twinrx_experts.hpp" +#include "twinrx/twinrx_ctrl.hpp" +#include "twinrx/twinrx_io.hpp" +#include <expert_factory.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include "dboard_ctor_args.hpp" +#include <boost/assign/list_of.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread.hpp> +#include <boost/thread/mutex.hpp> +//#include <fstream> //Needed for _expert->to_dot() below + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::usrp::dboard::twinrx; +using namespace uhd::experts; + +static const dboard_id_t TWINRX_V100_000_ID(0x91); + +/*! + * twinrx_rcvr_fe is the dbaord class (dboard_base) that + * represents each front-end of a TwinRX board. UHD will + * create and hold two instances of this class per TwinRX + * dboard. + * + */ +class twinrx_rcvr_fe : public rx_dboard_base +{ +public: + twinrx_rcvr_fe( + ctor_args_t args, + expert_container::sptr expert, + twinrx_ctrl::sptr ctrl + ) : + rx_dboard_base(args), _expert(expert), _ctrl(ctrl), + _ch_name(dboard_ctor_args_t::cast(args).sd_name) + { + //--------------------------------------------------------- + // Add user-visible, channel specific properties to front-end tree + //--------------------------------------------------------- + + //Generic + get_rx_subtree()->create<std::string>("name") + .set("TwinRX RX" + _ch_name); + get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + get_rx_subtree()->create<std::string>("connection") + .set(_ch_name == "0" ? "II" : "QQ"); //Ch->ADC port mapping + static const double BW = 80e6; + get_rx_subtree()->create<double>("bandwidth/value") + .set(BW); + get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(BW, BW)); + + //Frequency Specific + get_rx_subtree()->create<meta_range_t>("freq/range") + .set(freq_range_t(10e6, 6.0e9)); + expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(), + "freq/value", prepend_ch("freq/desired", _ch_name), prepend_ch("freq/coerced", _ch_name), + 1.0e9, AUTO_RESOLVE_ON_READ_WRITE); + get_rx_subtree()->create<device_addr_t>("tune_args") + .set(device_addr_t()); + + static const double DEFAULT_IF_FREQ = 150e6; + meta_range_t if_freq_range; + if_freq_range.push_back(range_t(-DEFAULT_IF_FREQ-(BW/2), -DEFAULT_IF_FREQ+(BW/2))); + if_freq_range.push_back(range_t( DEFAULT_IF_FREQ-(BW/2), DEFAULT_IF_FREQ+(BW/2))); + get_rx_subtree()->create<meta_range_t>("if_freq/range") + .set(if_freq_range); + expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(), + "if_freq/value", prepend_ch("if_freq/desired", _ch_name), prepend_ch("if_freq/coerced", _ch_name), + DEFAULT_IF_FREQ, AUTO_RESOLVE_ON_WRITE); + + //LO Specific + get_rx_subtree()->create<meta_range_t>("los/LO1/freq/range") + .set(freq_range_t(2.0e9, 6.8e9)); + expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(), + "los/LO1/freq/value", prepend_ch("los/LO1/freq/desired", _ch_name), prepend_ch("los/LO1/freq/coerced", _ch_name), + 0.0, AUTO_RESOLVE_ON_READ_WRITE); + get_rx_subtree()->create<meta_range_t>("los/LO2/freq/range") + .set(freq_range_t(1.0e9, 3.0e9)); + expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(), + "los/LO2/freq/value", prepend_ch("los/LO2/freq/desired", _ch_name), prepend_ch("los/LO2/freq/coerced", _ch_name), + 0.0, AUTO_RESOLVE_ON_READ_WRITE); + get_rx_subtree()->create<std::vector<std::string> >("los/all/source/options") + .set(boost::assign::list_of("internal")("external")("companion")("disabled")); + expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(), + "los/all/source/value", prepend_ch("los/all/source", _ch_name), + "internal", AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<bool>(_expert, get_rx_subtree(), + "los/all/export", prepend_ch("los/all/export", _ch_name), + false, AUTO_RESOLVE_ON_WRITE); + + //Gain Specific + get_rx_subtree()->create<meta_range_t>("gains/all/range") + .set(gain_range_t(0, 95, double(1.0))); + expert_factory::add_prop_node<double>(_expert, get_rx_subtree(), + "gains/all/value", prepend_ch("gain", _ch_name), + 0.0, AUTO_RESOLVE_ON_WRITE); + get_rx_subtree()->create<std::vector<std::string> >("gains/all/profile/options") + .set(boost::assign::list_of("low-noise")("low-distortion")("default")); + expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(), + "gains/all/profile/value", prepend_ch("gain_profile", _ch_name), + "default", AUTO_RESOLVE_ON_WRITE); + + //Antenna Specific + get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(boost::assign::list_of("RX1")("RX2")); + expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(), + "antenna/value", prepend_ch("antenna", _ch_name), + (_ch_name == "0" ? "RX1" : "RX2"), AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<bool>(_expert, get_rx_subtree(), + "enabled", prepend_ch("enabled", _ch_name), + false, AUTO_RESOLVE_ON_WRITE); + + //Readback + get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .set_publisher(boost::bind(&twinrx_rcvr_fe::get_lo_locked, this)); + + //--------------------------------------------------------- + // Add internal channel-specific data nodes to expert + //--------------------------------------------------------- + expert_factory::add_data_node<lo_inj_side_t>(_expert, + prepend_ch("ch/LO1/inj_side", _ch_name), INJ_LOW_SIDE); + expert_factory::add_data_node<lo_inj_side_t>(_expert, + prepend_ch("ch/LO2/inj_side", _ch_name), INJ_LOW_SIDE); + expert_factory::add_data_node<twinrx_ctrl::signal_path_t>(_expert, + prepend_ch("ch/signal_path", _ch_name), twinrx_ctrl::PATH_LOWBAND); + expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(_expert, + prepend_ch("ch/lb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1); + expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(_expert, + prepend_ch("ch/hb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1); + expert_factory::add_data_node<bool>(_expert, + prepend_ch("ch/lb_preamp_presel", _ch_name), false); + expert_factory::add_data_node<bool>(_expert, + prepend_ch("ant/lb_preamp_presel", _ch_name), false); + expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(_expert, + prepend_ch("ch/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS); + expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(_expert, + prepend_ch("ant/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS); + expert_factory::add_data_node<bool>(_expert, + prepend_ch("ch/preamp2", _ch_name), false); + expert_factory::add_data_node<bool>(_expert, + prepend_ch("ant/preamp2", _ch_name), false); + expert_factory::add_data_node<boost::uint8_t>(_expert, + prepend_ch("ch/input_atten", _ch_name), 0); + expert_factory::add_data_node<boost::uint8_t>(_expert, + prepend_ch("ant/input_atten", _ch_name), 0); + expert_factory::add_data_node<boost::uint8_t>(_expert, + prepend_ch("ch/lb_atten", _ch_name), 0); + expert_factory::add_data_node<boost::uint8_t>(_expert, + prepend_ch("ch/hb_atten", _ch_name), 0); + expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(_expert, + prepend_ch("ch/LO1/source", _ch_name), twinrx_ctrl::LO_INTERNAL); + expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(_expert, + prepend_ch("ch/LO2/source", _ch_name), twinrx_ctrl::LO_INTERNAL); + expert_factory::add_data_node<lo_synth_mapping_t>(_expert, + prepend_ch("synth/LO1/mapping", _ch_name), MAPPING_NONE); + expert_factory::add_data_node<lo_synth_mapping_t>(_expert, + prepend_ch("synth/LO2/mapping", _ch_name), MAPPING_NONE); + + } + + virtual ~twinrx_rcvr_fe(void) + { + } + + sensor_value_t get_lo_locked() + { + bool locked = true; + twinrx_ctrl::channel_t ch = (_ch_name == "0") ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2; + locked &= _ctrl->read_lo1_locked(ch); + locked &= _ctrl->read_lo2_locked(ch); + return sensor_value_t("LO", locked, "locked", "unlocked"); + } + +private: + expert_container::sptr _expert; + twinrx_ctrl::sptr _ctrl; + const std::string _ch_name; +}; + +/*! + * twinrx_rcvr is the top-level container for each + * TwinRX board. UHD will hold one instance of this + * class per TwinRX dboard. This class is responsible + * for owning all the control classes for the board. + * + */ +class twinrx_rcvr : public rx_dboard_base +{ +public: + typedef boost::shared_ptr<twinrx_rcvr> sptr; + + twinrx_rcvr(ctor_args_t args) : rx_dboard_base(args) + { + _db_iface = get_iface(); + twinrx_gpio::sptr gpio_iface = boost::make_shared<twinrx_gpio>(_db_iface); + twinrx_cpld_regmap::sptr cpld_regs = boost::make_shared<twinrx_cpld_regmap>(); + cpld_regs->initialize(*gpio_iface, false); + _ctrl = twinrx_ctrl::make(_db_iface, gpio_iface, cpld_regs); + _expert = expert_factory::create_container("twinrx_expert"); + } + + virtual ~twinrx_rcvr(void) + { + } + + inline expert_container::sptr get_expert() { + return _expert; + } + + inline twinrx_ctrl::sptr get_ctrl() { + return _ctrl; + } + + virtual void initialize() + { + //--------------------------------------------------------- + // Add internal channel-agnostic data nodes to expert + //--------------------------------------------------------- + expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(_expert, + "com/LO1/export_source", twinrx_ctrl::LO_EXPORT_DISABLED); + expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(_expert, + "com/LO2/export_source", twinrx_ctrl::LO_EXPORT_DISABLED); + expert_factory::add_data_node<twinrx_ctrl::antenna_mapping_t>(_expert, + "com/ant_mapping", twinrx_ctrl::ANTX_NATIVE); + expert_factory::add_data_node<twinrx_ctrl::cal_mode_t>(_expert, + "com/cal_mode", twinrx_ctrl::CAL_DISABLED); + expert_factory::add_data_node<bool>(_expert, + "com/synth/LO1/hopping_enabled", false); + expert_factory::add_data_node<bool>(_expert, + "com/synth/LO2/hopping_enabled", false); + + //--------------------------------------------------------- + // Add workers to expert + //--------------------------------------------------------- + //Channel (front-end) specific + BOOST_FOREACH(const std::string& fe, _fe_names) { + expert_factory::add_worker_node<twinrx_freq_path_expert>(_expert, _expert->node_retriever(), fe); + expert_factory::add_worker_node<twinrx_freq_coercion_expert>(_expert, _expert->node_retriever(), fe); + expert_factory::add_worker_node<twinrx_chan_gain_expert>(_expert, _expert->node_retriever(), fe); + expert_factory::add_worker_node<twinrx_nyquist_expert>(_expert, _expert->node_retriever(), fe, _db_iface); + } + + //Channel (front-end) agnostic + expert_factory::add_worker_node<twinrx_lo_config_expert>(_expert, _expert->node_retriever()); + expert_factory::add_worker_node<twinrx_lo_mapping_expert>(_expert, _expert->node_retriever(), STAGE_LO1); + expert_factory::add_worker_node<twinrx_lo_mapping_expert>(_expert, _expert->node_retriever(), STAGE_LO2); + expert_factory::add_worker_node<twinrx_antenna_expert>(_expert, _expert->node_retriever()); + expert_factory::add_worker_node<twinrx_ant_gain_expert>(_expert, _expert->node_retriever()); + expert_factory::add_worker_node<twinrx_settings_expert>(_expert, _expert->node_retriever(), _ctrl); + + /*//Expert debug code + std::ofstream dot_file("/tmp/twinrx.dot", std::ios::out); + dot_file << _expert->to_dot(); + dot_file.close(); + */ + + _expert->debug_audit(); + _expert->resolve_all(true); + } + + static dboard_base::sptr make_twinrx_fe(dboard_base::ctor_args_t args) + { + const dboard_ctor_args_t& db_args = dboard_ctor_args_t::cast(args); + sptr container = boost::dynamic_pointer_cast<twinrx_rcvr>(db_args.rx_container); + if (container) { + dboard_base::sptr fe = dboard_base::sptr( + new twinrx_rcvr_fe(args, container->get_expert(), container->get_ctrl())); + container->add_twinrx_fe(db_args.sd_name); + return fe; + } else { + throw uhd::assertion_error("error creating twinrx frontend"); + } + } + +protected: + inline void add_twinrx_fe(const std::string& name) { + _fe_names.push_back(name); + } + +private: + typedef std::map<std::string, dboard_base::sptr> twinrx_fe_map_t; + + dboard_iface::sptr _db_iface; + twinrx_ctrl::sptr _ctrl; + std::vector<std::string> _fe_names; + expert_container::sptr _expert; +}; + +/*! + * Initialization Sequence for each TwinRX board: + * - make_twinrx_container is called which creates an instance of twinrx_rcvr + * - twinrx_rcvr::make_twinrx_fe is called with channel "0" which creates an instance of twinrx_rcvr_fe + * - twinrx_rcvr::make_twinrx_fe is called with channel "1" which creates an instance of twinrx_rcvr_fe + * - twinrx_rcvr::initialize is called with finishes the init sequence + * + */ +static dboard_base::sptr make_twinrx_container(dboard_base::ctor_args_t args) +{ + return dboard_base::sptr(new twinrx_rcvr(args)); +} + +UHD_STATIC_BLOCK(reg_twinrx_dboards) +{ + dboard_manager::register_dboard_restricted( + TWINRX_V100_000_ID, + &twinrx_rcvr::make_twinrx_fe, + "TwinRX v1.0", + boost::assign::list_of("0")("1"), + &make_twinrx_container + ); +} diff --git a/host/lib/usrp/dboard/db_ubx.cpp b/host/lib/usrp/dboard/db_ubx.cpp index 15a4d17a9..be3bdd17d 100644 --- a/host/lib/usrp/dboard/db_ubx.cpp +++ b/host/lib/usrp/dboard/db_ubx.cpp @@ -27,7 +27,9 @@ #include <uhd/usrp/dboard_manager.hpp> #include <uhd/utils/assert_has.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> #include <uhd/utils/static.hpp> +#include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/shared_ptr.hpp> #include <boost/math/special_functions/round.hpp> @@ -319,14 +321,14 @@ public: write_gpio(); // Configure ATR - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _tx_gpio_reg.atr_idle); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _rx_gpio_reg.atr_idle); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _tx_gpio_reg.atr_idle); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _rx_gpio_reg.atr_idle); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex); // Engage ATR control (1 is ATR control, 0 is manual control) _iface->set_pin_ctrl(dboard_iface::UNIT_TX, _tx_gpio_reg.atr_mask); @@ -385,23 +387,23 @@ public: get_rx_subtree()->create<std::vector<std::string> >("power_mode/options") .set(ubx_power_modes); get_rx_subtree()->create<std::string>("power_mode/value") - .subscribe(boost::bind(&ubx_xcvr::set_power_mode, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_power_mode, this, _1)) .set("performance"); get_rx_subtree()->create<std::vector<std::string> >("xcvr_mode/options") .set(ubx_xcvr_modes); get_rx_subtree()->create<std::string>("xcvr_mode/value") - .subscribe(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1)) .set("FDX"); get_tx_subtree()->create<std::vector<std::string> >("power_mode/options") .set(ubx_power_modes); get_tx_subtree()->create<std::string>("power_mode/value") - .subscribe(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1)) - .publish(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value"))); + .add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1)) + .set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value"))); get_tx_subtree()->create<std::vector<std::string> >("xcvr_mode/options") .set(ubx_xcvr_modes); get_tx_subtree()->create<std::string>("xcvr_mode/value") - .subscribe(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1)) - .publish(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value"))); + .add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1)) + .set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value"))); //////////////////////////////////////////////////////////////////// // Register TX properties @@ -410,20 +412,20 @@ public: get_tx_subtree()->create<device_addr_t>("tune_args") .set(device_addr_t()); get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&ubx_xcvr::get_locked, this, "TXLO")); + .set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "TXLO")); get_tx_subtree()->create<double>("gains/PGA0/value") - .coerce(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0); + .set_coercer(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0); get_tx_subtree()->create<meta_range_t>("gains/PGA0/range") .set(ubx_tx_gain_range); get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&ubx_xcvr::set_tx_freq, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_tx_freq, this, _1)) .set(ubx_freq_range.start()); get_tx_subtree()->create<meta_range_t>("freq/range") .set(ubx_freq_range); get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(ubx_tx_antennas); get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&ubx_xcvr::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_tx_ant, this, _1)) .set(ubx_tx_antennas.at(0)); get_tx_subtree()->create<std::string>("connection") .set("QI"); @@ -436,7 +438,7 @@ public: get_tx_subtree()->create<meta_range_t>("bandwidth/range") .set(freq_range_t(bw, bw)); get_tx_subtree()->create<int64_t>("sync_delay") - .subscribe(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1)) .set(-8); //////////////////////////////////////////////////////////////////// @@ -446,21 +448,21 @@ public: get_rx_subtree()->create<device_addr_t>("tune_args") .set(device_addr_t()); get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&ubx_xcvr::get_locked, this, "RXLO")); + .set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "RXLO")); get_rx_subtree()->create<double>("gains/PGA0/value") - .coerce(boost::bind(&ubx_xcvr::set_rx_gain, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_rx_gain, this, _1)) .set(0); get_rx_subtree()->create<meta_range_t>("gains/PGA0/range") .set(ubx_rx_gain_range); get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&ubx_xcvr::set_rx_freq, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_rx_freq, this, _1)) .set(ubx_freq_range.start()); get_rx_subtree()->create<meta_range_t>("freq/range") .set(ubx_freq_range); get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(ubx_rx_antennas); get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2"); + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2"); get_rx_subtree()->create<std::string>("connection") .set("IQ"); get_rx_subtree()->create<bool>("enabled") @@ -472,35 +474,38 @@ public: get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(freq_range_t(bw, bw)); get_rx_subtree()->create<int64_t>("sync_delay") - .subscribe(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1)) .set(-8); } - ~ubx_xcvr(void) + virtual ~ubx_xcvr(void) { - // Shutdown synthesizers - _txlo1->shutdown(); - _txlo2->shutdown(); - _rxlo1->shutdown(); - _rxlo2->shutdown(); + UHD_SAFE_CALL + ( + // Shutdown synthesizers + _txlo1->shutdown(); + _txlo2->shutdown(); + _rxlo1->shutdown(); + _rxlo2->shutdown(); - // Reset CPLD values - _cpld_reg.value = 0; - write_cpld_reg(); + // Reset CPLD values + _cpld_reg.value = 0; + write_cpld_reg(); - // Reset GPIO values - set_gpio_field(TX_GAIN, 0); - set_gpio_field(CPLD_RST_N, 0); - set_gpio_field(RX_ANT, 1); - set_gpio_field(TX_EN_N, 1); - set_gpio_field(RX_EN_N, 1); - set_gpio_field(SPI_ADDR, 0x7); - set_gpio_field(RX_GAIN, 0); - set_gpio_field(TXLO1_SYNC, 0); - set_gpio_field(TXLO2_SYNC, 0); - set_gpio_field(RXLO1_SYNC, 0); - set_gpio_field(RXLO1_SYNC, 0); - write_gpio(); + // Reset GPIO values + set_gpio_field(TX_GAIN, 0); + set_gpio_field(CPLD_RST_N, 0); + set_gpio_field(RX_ANT, 1); + set_gpio_field(TX_EN_N, 1); + set_gpio_field(RX_EN_N, 1); + set_gpio_field(SPI_ADDR, 0x7); + set_gpio_field(RX_GAIN, 0); + set_gpio_field(TXLO1_SYNC, 0); + set_gpio_field(TXLO2_SYNC, 0); + set_gpio_field(RXLO1_SYNC, 0); + set_gpio_field(RXLO1_SYNC, 0); + write_gpio(); + ) } private: @@ -638,23 +643,23 @@ private: uint16_t mask = lo1_field_info.mask | lo2_field_info.mask; dboard_iface::unit_t unit = lo1_field_info.unit; UHD_ASSERT_THROW(lo1_field_info.unit == lo2_field_info.unit); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, value, mask); // De-assert SYNC // Head of line blocking means the command time does not need to be set. - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, 0, mask); } } @@ -760,6 +765,20 @@ private: device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); UHD_LOGV(rarely) << boost::format("UBX TX: the requested frequency is %f MHz") % (freq/1e6) << std::endl; + double target_pfd_freq = _tx_target_pfd_freq; + if (is_int_n and tune_args.has_key("int_n_step")) + { + target_pfd_freq = tune_args.cast<double>("int_n_step", _tx_target_pfd_freq); + if (target_pfd_freq > _tx_target_pfd_freq) + { + UHD_MSG(warning) + << boost::format("Requested int_n_step of %f MHz too large, clipping to %f MHz") + % (target_pfd_freq/1e6) + % (_tx_target_pfd_freq/1e6) + << std::endl; + target_pfd_freq = _tx_target_pfd_freq; + } + } // Clip the frequency to the valid range freq = ubx_freq_range.clip(freq); @@ -796,10 +815,10 @@ private: set_cpld_field(TXLB_SEL, 1); set_cpld_field(TXHB_SEL, 0); // Set LO1 to IF of 2100 MHz (offset from RX IF to reduce leakage) - freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _txlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= (500*fMHz)) && (freq <= (800*fMHz))) @@ -809,7 +828,7 @@ private: set_cpld_field(TXLO1_FSEL1, 1); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (800*fMHz)) && (freq <= (1000*fMHz))) @@ -819,7 +838,7 @@ private: set_cpld_field(TXLO1_FSEL1, 1); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } else if ((freq > (1000*fMHz)) && (freq <= (2200*fMHz))) @@ -829,7 +848,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (2200*fMHz)) && (freq <= (2500*fMHz))) @@ -839,7 +858,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (2500*fMHz)) && (freq <= (6000*fMHz))) @@ -849,7 +868,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } @@ -902,6 +921,20 @@ private: property_tree::sptr subtree = this->get_rx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double target_pfd_freq = _rx_target_pfd_freq; + if (is_int_n and tune_args.has_key("int_n_step")) + { + target_pfd_freq = tune_args.cast<double>("int_n_step", _rx_target_pfd_freq); + if (target_pfd_freq > _rx_target_pfd_freq) + { + UHD_MSG(warning) + << boost::format("Requested int_n_step of %f Mhz too large, clipping to %f MHz") + % (target_pfd_freq/1e6) + % (_rx_target_pfd_freq/1e6) + << std::endl; + target_pfd_freq = _rx_target_pfd_freq; + } + } // Clip the frequency to the valid range freq = ubx_freq_range.clip(freq); @@ -940,10 +973,10 @@ private: set_cpld_field(RXLB_SEL, 1); set_cpld_field(RXHB_SEL, 0); // Set LO1 to IF of 2380 MHz (2440 MHz filter center minus 60 MHz offset to minimize LO leakage) - freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _rxlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 100*fMHz) && (freq < 500*fMHz)) @@ -956,10 +989,10 @@ private: set_cpld_field(RXLB_SEL, 1); set_cpld_field(RXHB_SEL, 0); // Set LO1 to IF of 2440 (center of filter) - freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 500*fMHz) && (freq < 800*fMHz)) @@ -971,7 +1004,7 @@ private: set_cpld_field(RXLO1_FSEL1, 1); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 800*fMHz) && (freq < 1000*fMHz)) @@ -983,7 +1016,7 @@ private: set_cpld_field(RXLO1_FSEL1, 1); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } else if ((freq >= 1000*fMHz) && (freq < 1500*fMHz)) @@ -995,7 +1028,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 1500*fMHz) && (freq < 2200*fMHz)) @@ -1007,7 +1040,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 2200*fMHz) && (freq < 2500*fMHz)) @@ -1019,7 +1052,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 2500*fMHz) && (freq <= 6000*fMHz)) @@ -1031,7 +1064,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp index 97357bc90..6539e798a 100644 --- a/host/lib/usrp/dboard/db_wbx_common.cpp +++ b/host/lib/usrp/dboard/db_wbx_common.cpp @@ -69,17 +69,17 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t()); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::set_rx_gain, this, _1, name)) .set(wbx_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_rx_gain_ranges[name]); } this->get_rx_subtree()->create<std::string>("connection").set("IQ"); this->get_rx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::set_rx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::set_rx_enabled, this, _1)) .set(true); //start enabled this->get_rx_subtree()->create<bool>("use_lo_offset").set(false); @@ -94,7 +94,7 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t()); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX)); this->get_tx_subtree()->create<std::string>("connection").set("IQ"); this->get_tx_subtree()->create<bool>("use_lo_offset").set(false); @@ -156,3 +156,9 @@ sensor_value_t wbx_base::get_locked(dboard_iface::unit_t unit){ const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; return sensor_value_t("LO", locked, "locked", "unlocked"); } + +void wbx_base::wbx_versionx::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) { + BOOST_FOREACH(boost::uint32_t reg, regs) { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp index 6a4224048..0e339e4a3 100644 --- a/host/lib/usrp/dboard/db_wbx_common.hpp +++ b/host/lib/usrp/dboard/db_wbx_common.hpp @@ -19,8 +19,13 @@ #define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP #include <uhd/types/device_addr.hpp> +#include "adf435x.hpp" -#include "../common/adf435x_common.hpp" +// LO Related +#define ADF435X_CE (1 << 3) +#define ADF435X_PDBRF (1 << 2) +#define ADF435X_MUXOUT (1 << 1) // INPUT!!! +#define LOCKDET_MASK (1 << 0) // INPUT!!! // TX IO Pins #define TX_PUP_5V (1 << 7) // enables 5.0V power supply @@ -40,6 +45,9 @@ #define TX_ATTN_1 (1 << 1) #define TX_ATTN_MASK (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1) // valid bits of TX Attenuator Control +#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control +#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control + // Mixer functions #define TX_MIXER_ENB (TXMOD_EN|ADF435X_PDBRF) // for v3, TXMOD_EN tied to ADF435X_PDBRF rather than separate #define TX_MIXER_DIS 0 @@ -142,6 +150,10 @@ protected: property_tree::sptr get_tx_subtree(void){ return self_base->get_tx_subtree(); } + + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; @@ -153,7 +165,7 @@ protected: class wbx_version2 : public wbx_versionx { public: wbx_version2(wbx_base *_self_wbx_base); - ~wbx_version2(void); + virtual ~wbx_version2(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); @@ -168,7 +180,7 @@ protected: class wbx_version3 : public wbx_versionx { public: wbx_version3(wbx_base *_self_wbx_base); - ~wbx_version3(void); + virtual ~wbx_version3(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); @@ -183,7 +195,7 @@ protected: class wbx_version4 : public wbx_versionx { public: wbx_version4(wbx_base *_self_wbx_base); - ~wbx_version4(void); + virtual ~wbx_version4(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp index c8f2be155..062e1294b 100644 --- a/host/lib/usrp/dboard/db_wbx_simple.cpp +++ b/host/lib/usrp/dboard/db_wbx_simple.cpp @@ -46,7 +46,7 @@ static const std::vector<std::string> wbx_rx_antennas = list_of("TX/RX")("RX2")( class wbx_simple : public wbx_base{ public: wbx_simple(ctor_args_t args); - ~wbx_simple(void); + virtual ~wbx_simple(void); private: void set_rx_ant(const std::string &ant); @@ -88,7 +88,7 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ std::string(str(boost::format("%s+GDB") % this->get_rx_subtree()->access<std::string>("name").get() ))); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&wbx_simple::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_simple::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(wbx_rx_antennas); @@ -100,7 +100,7 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ std::string(str(boost::format("%s+GDB") % this->get_tx_subtree()->access<std::string>("name").get() ))); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&wbx_simple::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_simple::set_tx_ant, this, _1)) .set(wbx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(wbx_tx_antennas); @@ -112,14 +112,14 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); //setup ATR for the antenna switches (constant) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); - - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); } wbx_simple::~wbx_simple(void){ @@ -138,14 +138,14 @@ void wbx_simple::set_rx_ant(const std::string &ant){ //write the new antenna setting to atr regs if (_rx_ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO); } } @@ -154,11 +154,11 @@ void wbx_simple::set_tx_ant(const std::string &ant){ //write the new antenna setting to atr regs if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); } } diff --git a/host/lib/usrp/dboard/db_wbx_version2.cpp b/host/lib/usrp/dboard/db_wbx_version2.cpp index 03c8f37e9..489291881 100644 --- a/host/lib/usrp/dboard/db_wbx_version2.cpp +++ b/host/lib/usrp/dboard/db_wbx_version2.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4350_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> @@ -77,13 +75,15 @@ static double tx_pga0_gain_to_dac_volts(double &gain){ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create<std::string>("name").set("WBXv2 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range); @@ -93,17 +93,17 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { this->get_tx_subtree()->create<std::string>("name").set("WBXv2 TX"); BOOST_FOREACH(const std::string &name, wbx_v2_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name)) .set(wbx_v2_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v2_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -117,15 +117,15 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF435X_CE|RXBB_PDB|ADF435X_PDBRF|RX_ATTN_MASK); //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } wbx_base::wbx_version2::~wbx_version2(void){ @@ -178,111 +178,45 @@ double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer - //frequency must 2x the target frequency. This introduces a 180 degree - //phase ambiguity + //frequency must 2x the target frequency double synth_target_freq = target_freq * 2; - adf4350_regs_t::prescaler_t prescaler = - synth_target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); //The feedback of the divided frequency must be disabled whenever the target frequency //divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value. //If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD //frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz) //will have too much ambiguity to synchronize. - tuning_constraints.feedback_after_divider = - (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]); + lo_iface->set_feedback_select( + (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL)); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4350_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4350_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "WBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "WBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_wbx_version3.cpp b/host/lib/usrp/dboard/db_wbx_version3.cpp index a559a7b14..4dcf3bb71 100644 --- a/host/lib/usrp/dboard/db_wbx_version3.cpp +++ b/host/lib/usrp/dboard/db_wbx_version3.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4350_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> #include <uhd/types/ranges.hpp> @@ -82,13 +80,15 @@ static int tx_pga0_gain_to_iobits(double &gain){ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create<std::string>("name").set("WBXv3 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range); @@ -98,17 +98,17 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { this->get_tx_subtree()->create<std::string>("name").set("WBXv3 TX"); BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name)) .set(wbx_v3_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v3_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -129,29 +129,29 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { //slip between bursts). set TX gain iobits to min gain (max attenuation) //when RX_ONLY or IDLE to suppress LO leakage self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, v3_tx_mod, \ + gpio_atr::ATR_REG_IDLE, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, v3_tx_mod, \ + gpio_atr::ATR_REG_RX_ONLY, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, v3_tx_mod, \ + gpio_atr::ATR_REG_TX_ONLY, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, \ + gpio_atr::ATR_REG_FULL_DUPLEX, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, \ + gpio_atr::ATR_REG_IDLE, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, \ + gpio_atr::ATR_REG_TX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, \ + gpio_atr::ATR_REG_RX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, \ + gpio_atr::ATR_REG_FULL_DUPLEX, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } @@ -181,8 +181,8 @@ double wbx_base::wbx_version3::set_tx_gain(double gain, const std::string &name) //write the new gain to tx gpio outputs //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); } else UHD_THROW_INVALID_CODE_PATH(); return self_base->_tx_gains[name]; //shadow @@ -209,111 +209,45 @@ double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer - //frequency must 2x the target frequency. This introduces a 180 degree - //phase ambiguity + //frequency must 2x the target frequency double synth_target_freq = target_freq * 2; - adf4350_regs_t::prescaler_t prescaler = - synth_target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); //The feedback of the divided frequency must be disabled whenever the target frequency //divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value. //If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD //frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz) //will have too much ambiguity to synchronize. - tuning_constraints.feedback_after_divider = - (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]); + lo_iface->set_feedback_select( + (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL)); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4350_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4350_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "WBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "WBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp index 81cdaefac..dc351af1d 100644 --- a/host/lib/usrp/dboard/db_wbx_version4.cpp +++ b/host/lib/usrp/dboard/db_wbx_version4.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4351_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> #include <uhd/types/ranges.hpp> @@ -83,6 +81,8 @@ static int tx_pga0_gain_to_iobits(double &gain){ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4351(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4351(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties @@ -92,7 +92,7 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { if(rx_id == 0x0063) this->get_rx_subtree()->create<std::string>("name").set("WBXv4 RX"); else if(rx_id == 0x0081) this->get_rx_subtree()->create<std::string>("name").set("WBX-120 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); @@ -105,17 +105,17 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { else if(rx_id == 0x0081) this->get_tx_subtree()->create<std::string>("name").set("WBX-120 TX"); BOOST_FOREACH(const std::string &name, wbx_v4_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name)) .set(wbx_v4_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v4_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -136,29 +136,29 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //between bursts) set TX gain iobits to min gain (max attenuation) when //RX_ONLY or IDLE to suppress LO leakage self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, v4_tx_mod, \ + gpio_atr::ATR_REG_IDLE, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, v4_tx_mod, \ + gpio_atr::ATR_REG_RX_ONLY, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, v4_tx_mod, \ + gpio_atr::ATR_REG_TX_ONLY, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, v4_tx_mod, \ + gpio_atr::ATR_REG_FULL_DUPLEX, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, \ + gpio_atr::ATR_REG_IDLE, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, \ + gpio_atr::ATR_REG_TX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, \ + gpio_atr::ATR_REG_RX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, \ + gpio_atr::ATR_REG_FULL_DUPLEX, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } @@ -188,8 +188,8 @@ double wbx_base::wbx_version4::set_tx_gain(double gain, const std::string &name) //write the new gain to tx gpio outputs //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); } else UHD_THROW_INVALID_CODE_PATH(); @@ -217,116 +217,46 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (adf4351_regs_t::PRESCALER_4_5, 23) - (adf4351_regs_t::PRESCALER_8_9, 75) - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //frequency must 2x the target frequency. This introduces a 180 degree phase //ambiguity when trying to synchronize the phase of multiple boards. double synth_target_freq = target_freq * 2; - adf4351_regs_t::prescaler_t prescaler = - synth_target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3.6e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 64); //The feedback of the divided frequency must be disabled whenever the target frequency //divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value. //If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD //frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz) //will have too much ambiguity to synchronize. - tuning_constraints.feedback_after_divider = - (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]); + lo_iface->set_feedback_select( + (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL)); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4351_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4351_regs_t::OUTPUT_POWER_5DBM - : adf4351_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4351_regs_t::OUTPUT_POWER_5DBM - : adf4351_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4351_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4351_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4351_regs_t::LDF_INT_N : - adf4351_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - boost::uint16_t rx_id = self_base->get_rx_id().to_uint16(); - std::string board_name = (rx_id == 0x0081) ? "WBX-120" : "WBX"; - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "%s SPI Reg (0x%02x): 0x%08x" - ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "%s tune: actual frequency %f MHz" - ) % board_name.c_str() % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); return actual_freq; } diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp index 50c67991a..4a3f69f69 100644 --- a/host/lib/usrp/dboard/db_xcvr2450.cpp +++ b/host/lib/usrp/dboard/db_xcvr2450.cpp @@ -112,7 +112,7 @@ static const uhd::dict<std::string, gain_range_t> xcvr_rx_gain_ranges = map_list class xcvr2450 : public xcvr_dboard_base{ public: xcvr2450(ctor_args_t args); - ~xcvr2450(void); + virtual ~xcvr2450(void); private: double _lo_freq; @@ -231,23 +231,23 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("XCVR2450 RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&xcvr2450::get_locked, this)); + .set_publisher(boost::bind(&xcvr2450::get_locked, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&xcvr2450::get_rssi, this)); + .set_publisher(boost::bind(&xcvr2450::get_rssi, this)); BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&xcvr2450::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&xcvr2450::set_rx_gain, this, _1, name)) .set(xcvr_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(xcvr_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&xcvr2450::set_lo_freq, this, _1)) .set(double(2.45e9)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(xcvr_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&xcvr2450::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&xcvr2450::set_rx_ant, this, _1)) .set(xcvr_antennas.at(0)); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(xcvr_antennas); @@ -258,7 +258,7 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth + .set_coercer(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth .set(2.0*_rx_bandwidth); //_rx_bandwidth in lowpass, convert to complex bandpass this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(xcvr_rx_bandwidth_range); @@ -269,21 +269,21 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<std::string>("name") .set("XCVR2450 TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&xcvr2450::get_locked, this)); + .set_publisher(boost::bind(&xcvr2450::get_locked, this)); BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){ this->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&xcvr2450::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&xcvr2450::set_tx_gain, this, _1, name)) .set(xcvr_tx_gain_ranges[name].start()); this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(xcvr_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&xcvr2450::set_lo_freq, this, _1)) .set(double(2.45e9)); this->get_tx_subtree()->create<meta_range_t>("freq/range") .set(xcvr_freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&xcvr2450::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&xcvr2450::set_tx_ant, this, _1)) .set(xcvr_antennas.at(1)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(xcvr_antennas); @@ -294,7 +294,7 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_tx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth + .set_coercer(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth .set(2.0*_tx_bandwidth); //_tx_bandwidth in lowpass, convert to complex bandpass this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") .set(xcvr_tx_bandwidth_range); @@ -315,12 +315,12 @@ xcvr2450::~xcvr2450(void){ void xcvr2450::spi_reset(void){ //spi reset mode: global enable = off, tx and rx enable = on - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_ENB_TXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, TX_ENB_TXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); //take it back out of spi reset mode and wait a bit - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } @@ -337,16 +337,16 @@ void xcvr2450::update_atr(void){ int ad9515div = (_ad9515div == 3)? AD9515DIV_3_TXIO : AD9515DIV_2_TXIO; //set the tx registers - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); //set the rx registers - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); } /*********************************************************************** diff --git a/host/lib/usrp/dboard/twinrx/table_to_cpp.py b/host/lib/usrp/dboard/twinrx/table_to_cpp.py new file mode 100644 index 000000000..ea0e3bf66 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/table_to_cpp.py @@ -0,0 +1,26 @@ +#!/usr/bin/python + +import sys + +if len(sys.argv) != 3: + print 'Usage: ' + sys.argv[0] + ' <table filename> <table name>' + sys.exit(1) + +with open(sys.argv[1], 'rb') as f: + print ('static const std::vector<twinrx_gain_config_t> ' + + sys.argv[2] + ' = boost::assign::list_of') + i = -1 + for line in f.readlines(): + if (i != -1): + row = line.strip().split(',') + print (' ( twinrx_gain_config_t( %s, %6.1f, %s, %s, %s, %s ) )' % ( + str(i).rjust(5), + float(row[0].rjust(6)), + row[1].rjust(6), row[2].rjust(6), + ('true' if int(row[3]) == 1 else 'false').rjust(5), + ('true' if int(row[4]) == 1 else 'false').rjust(5))) + else: + print (' // %s, %s, %s, %s, %s, %s' % ( + 'Index', ' Gain', 'Atten1', 'Atten2', ' Amp1', ' Amp2')) + i += 1 + print ';' diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp new file mode 100644 index 000000000..172992f0a --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp @@ -0,0 +1,643 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/thread/thread.hpp> +#include <uhd/utils/math.hpp> +#include <uhd/utils/safe_call.hpp> +#include "twinrx_ctrl.hpp" +#include "adf435x.hpp" +#include "adf5355.hpp" + +using namespace uhd; +using namespace usrp; +using namespace dboard::twinrx; +typedef twinrx_cpld_regmap rm; + +static boost::uint32_t bool2bin(bool x) { return x ? 1 : 0; } + +static const double TWINRX_DESIRED_REFERENCE_FREQ = 50e6; + +class twinrx_ctrl_impl : public twinrx_ctrl { +public: + twinrx_ctrl_impl( + dboard_iface::sptr db_iface, + twinrx_gpio::sptr gpio_iface, + twinrx_cpld_regmap::sptr cpld_regmap + ) : _db_iface(db_iface), _gpio_iface(gpio_iface), _cpld_regs(cpld_regmap) + { + + //Turn on switcher and wait for power good + _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 1); + size_t timeout_ms = 100; + while (_gpio_iface->get_field(twinrx_gpio::FIELD_SWPS_PWR_GOOD) == 0) { + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + if (--timeout_ms == 0) { + throw uhd::runtime_error("power supply failure"); + } + } + //Initialize synthesizer objects + _lo1_iface[size_t(CH1)] = adf5355_iface::make( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + _lo1_iface[size_t(CH2)] = adf5355_iface::make( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + + _lo2_iface[size_t(CH1)] = adf435x_iface::make_adf4351( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1)); + _lo2_iface[size_t(CH2)] = adf435x_iface::make_adf4351( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1)); + + // Assert synthesizer chip enables + _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH1, 1); + _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH2, 1); + _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH1, 1); + _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH2, 1); + + //Initialize default state + set_chan_enabled(BOTH, false, false); + set_preamp1(BOTH, PREAMP_BYPASS, false); + set_preamp2(BOTH, false, false); + set_lb_preamp_preselector(BOTH, false, false); + set_signal_path(BOTH, PATH_LOWBAND, false); + set_lb_preselector(BOTH, PRESEL_PATH3, false); + set_hb_preselector(BOTH, PRESEL_PATH1, false); + set_input_atten(BOTH, 31, false); + set_lb_atten(BOTH, 31, false); + set_hb_atten(BOTH, 31, false); + set_lo1_source(BOTH, LO_INTERNAL, false); + set_lo2_source(BOTH, LO_INTERNAL, false); + set_lo1_export_source(LO_EXPORT_DISABLED, false); + set_lo2_export_source(LO_EXPORT_DISABLED, false); + set_antenna_mapping(ANTX_NATIVE, false); + set_crossover_cal_mode(CAL_DISABLED, false); + commit(); + + //Initialize clocks and LO + bool found_rate = false; + BOOST_FOREACH(double rate, _db_iface->get_clock_rates(dboard_iface::UNIT_TX)) { + found_rate |= uhd::math::frequencies_are_equal(rate, TWINRX_DESIRED_REFERENCE_FREQ); + } + BOOST_FOREACH(double rate, _db_iface->get_clock_rates(dboard_iface::UNIT_RX)) { + found_rate |= uhd::math::frequencies_are_equal(rate, TWINRX_DESIRED_REFERENCE_FREQ); + } + if (not found_rate) { + throw uhd::runtime_error("TwinRX not supported on this motherboard"); + } + _db_iface->set_clock_rate(dboard_iface::UNIT_TX, TWINRX_DESIRED_REFERENCE_FREQ); + _db_iface->set_clock_rate(dboard_iface::UNIT_RX, TWINRX_DESIRED_REFERENCE_FREQ); + + _db_iface->set_clock_enabled(dboard_iface::UNIT_TX, true); + _db_iface->set_clock_enabled(dboard_iface::UNIT_RX, true); + for (size_t i = 0; i < NUM_CHANS; i++) { + _config_lo1_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2); + _config_lo2_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2); + _lo1_iface[i]->set_output_power(adf5355_iface::OUTPUT_POWER_5DBM); + _lo1_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ); + _lo1_iface[i]->set_muxout_mode(adf5355_iface::MUXOUT_DLD); + _lo1_iface[i]->set_frequency(3e9, 1.0e3); + _lo2_iface[i]->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + _lo2_iface[i]->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); + _lo2_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ); + _lo2_iface[i]->set_muxout_mode(adf435x_iface::MUXOUT_DLD); + _lo1_iface[i]->commit(); + _lo2_iface[i]->commit(); + } + _config_lo1_route(LO_CONFIG_NONE); + _config_lo2_route(LO_CONFIG_NONE); + } + + ~twinrx_ctrl_impl() + { + UHD_SAFE_CALL( + boost::lock_guard<boost::mutex> lock(_mutex); + _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 0); + ) + } + + void commit() + { + boost::lock_guard<boost::mutex> lock(_mutex); + _commit(); + } + + void set_chan_enabled(channel_t ch, bool enabled, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::AMP_LO1_EN_CH1, bool2bin(enabled)); + _cpld_regs->if0_reg3.set(rm::if0_reg3_t::IF1_IF2_EN_CH1, bool2bin(enabled)); + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH1, bool2bin(enabled)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::AMP_LO1_EN_CH2, bool2bin(enabled)); + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::IF1_IF2_EN_CH2, bool2bin(enabled)); + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH2, bool2bin(enabled)); + } + if (commit) _commit(); + } + + void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::SWPA1_CTL_CH1, bool2bin(value==PREAMP_HIGHBAND)); + _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::SWPA2_CTRL_CH1, bool2bin(value==PREAMP_BYPASS)); + _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::HB_PREAMP_EN_CH1, bool2bin(value==PREAMP_HIGHBAND)); + _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::LB_PREAMP_EN_CH1, bool2bin(value==PREAMP_LOWBAND)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SWPA1_CTRL_CH2, bool2bin(value==PREAMP_HIGHBAND)); + _cpld_regs->rf2_reg5.set(rm::rf2_reg5_t::SWPA2_CTRL_CH2, bool2bin(value==PREAMP_BYPASS)); + _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::HB_PREAMP_EN_CH2, bool2bin(value==PREAMP_HIGHBAND)); + _cpld_regs->rf2_reg6.set(rm::rf2_reg6_t::LB_PREAMP_EN_CH2, bool2bin(value==PREAMP_LOWBAND)); + } + if (commit) _commit(); + } + + void set_preamp2(channel_t ch, bool enabled, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SWPA4_CTRL_CH1, bool2bin(not enabled)); + _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::PREAMP2_EN_CH1, bool2bin(enabled)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SWPA4_CTRL_CH2, bool2bin(not enabled)); + _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::PREAMP2_EN_CH2, bool2bin(enabled)); + } + if (commit) _commit(); + } + + void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SWPA3_CTRL_CH1, bool2bin(not enabled)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::SWPA3_CTRL_CH2, bool2bin(not enabled)); + } + if (commit) _commit(); + } + + void set_signal_path(channel_t ch, signal_path_t path, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::SW11_CTRL_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::SW12_CTRL_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::HB_PRESEL_PGA_EN_CH1, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW6_CTRL_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->if0_reg3.set(rm::if0_reg3_t::SW13_CTRL_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->if0_reg2.set(rm::if0_reg2_t::AMP_LB_IF1_EN_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_HB_IF1_EN_CH1, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::AMP_HB_EN_CH1, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::AMP_LB_EN_CH1, bool2bin(path==PATH_LOWBAND)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW11_CTRL_CH2, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW12_CTRL_CH2, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::HB_PRESEL_PGA_EN_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW6_CTRL_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->if0_reg6.set(rm::if0_reg6_t::SW13_CTRL_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->if0_reg2.set(rm::if0_reg2_t::AMP_LB_IF1_EN_CH2, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->if0_reg6.set(rm::if0_reg6_t::AMP_HB_IF1_EN_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::AMP_HB_EN_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::AMP_LB_EN_CH2, bool2bin(path==PATH_LOWBAND)); + } + if (commit) _commit(); + } + + void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + boost::uint32_t sw7val = 0, sw8val = 0; + switch (path) { + case PRESEL_PATH1: sw7val = 3; sw8val = 1; break; + case PRESEL_PATH2: sw7val = 2; sw8val = 0; break; + case PRESEL_PATH3: sw7val = 0; sw8val = 2; break; + case PRESEL_PATH4: sw7val = 1; sw8val = 3; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW7_CTRL_CH1, sw7val); + _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW8_CTRL_CH1, sw8val); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW7_CTRL_CH2, sw7val); + _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW8_CTRL_CH2, sw8val); + } + if (commit) _commit(); + } + + void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + boost::uint32_t sw9ch1val = 0, sw10ch1val = 0, sw9ch2val = 0, sw10ch2val = 0; + switch (path) { + case PRESEL_PATH1: sw9ch1val = 3; sw10ch1val = 0; sw9ch2val = 0; sw10ch2val = 3; break; + case PRESEL_PATH2: sw9ch1val = 1; sw10ch1val = 2; sw9ch2val = 1; sw10ch2val = 1; break; + case PRESEL_PATH3: sw9ch1val = 2; sw10ch1val = 1; sw9ch2val = 2; sw10ch2val = 2; break; + case PRESEL_PATH4: sw9ch1val = 0; sw10ch1val = 3; sw9ch2val = 3; sw10ch2val = 0; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW9_CTRL_CH1, sw9ch1val); + _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW10_CTRL_CH1, sw10ch1val); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW9_CTRL_CH2, sw9ch2val); + _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW10_CTRL_CH2, sw10ch2val); + } + if (commit) _commit(); + + } + + void set_input_atten(channel_t ch, boost::uint8_t atten, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg0.set(rm::rf0_reg0_t::ATTEN_IN_CH1, atten&0x1F); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg4.set(rm::rf0_reg4_t::ATTEN_IN_CH2, atten&0x1F); + } + if (commit) _commit(); + } + + void set_lb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf2_reg0.set(rm::rf2_reg0_t::ATTEN_LB_CH1, atten&0x1F); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf2_reg4.set(rm::rf2_reg4_t::ATTEN_LB_CH2, atten&0x1F); + } + if (commit) _commit(); + } + + void set_hb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf1_reg0.set(rm::rf1_reg0_t::ATTEN_HB_CH1, atten&0x1F); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf1_reg4.set(rm::rf1_reg4_t::ATTEN_HB_CH2, atten&0x1F); + } + if (commit) _commit(); + } + + void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW14_CTRL_CH2, bool2bin(source!=LO_COMPANION)); + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW15_CTRL_CH1, bool2bin(source==LO_EXTERNAL)); + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW16_CTRL_CH1, bool2bin(source!=LO_INTERNAL)); + _lo1_src[size_t(CH1)] = source; + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW14_CTRL_CH1, bool2bin(source==LO_COMPANION)); + _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW15_CTRL_CH2, bool2bin(source!=LO_INTERNAL)); + _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::SW16_CTRL_CH2, bool2bin(source==LO_INTERNAL)); + _lo1_src[size_t(CH2)] = source; + } + if (commit) _commit(); + } + + void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW19_CTRL_CH2, bool2bin(source==LO_COMPANION)); + _cpld_regs->if0_reg1.set(rm::if0_reg1_t::SW20_CTRL_CH1, bool2bin(source==LO_COMPANION)); + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW21_CTRL_CH1, bool2bin(source==LO_INTERNAL)); + _lo2_src[size_t(CH1)] = source; + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW19_CTRL_CH1, bool2bin(source==LO_EXTERNAL)); + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW20_CTRL_CH2, bool2bin(source==LO_INTERNAL||source==LO_DISABLED)); + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW21_CTRL_CH2, bool2bin(source==LO_INTERNAL)); + _lo2_src[size_t(CH2)] = source; + } + if (commit) _commit(); + } + + void set_lo1_export_source(lo_export_source_t source, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + //SW22 may conflict with the cal switch but this attr takes priority and we assume + //that the cal switch is disabled (by disabling it!) + _set_cal_mode(CAL_DISABLED, source); + _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW23_CTRL, bool2bin(source!=LO_CH1_SYNTH)); + _lo1_export = source; + + if (commit) _commit(); + } + + void set_lo2_export_source(lo_export_source_t source, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + _cpld_regs->if0_reg7.set(rm::if0_reg7_t::SW24_CTRL_CH2, bool2bin(source==LO_CH2_SYNTH)); + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW25_CTRL, bool2bin(source!=LO_CH1_SYNTH)); + _cpld_regs->if0_reg3.set(rm::if0_reg3_t::SW24_CTRL_CH1, bool2bin(source!=LO_CH1_SYNTH)); + _lo2_export = source; + + if (commit) _commit(); + } + + void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + enum switch_path_t { CONNECT, TERM, EXPORT, IMPORT, SWAP }; + switch_path_t path1, path2; + + switch (mapping) { + case ANTX_NATIVE: + path1 = CONNECT; path2 = CONNECT; break; + case ANT1_SHARED: + path1 = EXPORT; path2 = IMPORT; break; + case ANT2_SHARED: + path1 = IMPORT; path2 = EXPORT; break; + case ANTX_SWAPPED: + path1 = SWAP; path2 = SWAP; break; + default: + path1 = TERM; path2 = TERM; break; + } + + _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW3_CTRL_CH1, bool2bin(path1==EXPORT||path1==SWAP)); + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW4_CTRL_CH1, bool2bin(!(path1==IMPORT||path1==SWAP))); + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW5_CTRL_CH1, bool2bin(path1==CONNECT)); + _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW3_CTRL_CH2, bool2bin(path2==EXPORT||path2==SWAP)); + _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW4_CTRL_CH2, bool2bin(path2==IMPORT||path2==SWAP)); + _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW5_CTRL_CH2, bool2bin(path2==CONNECT)); + + if (commit) _commit(); + } + + void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (_lo1_export == LO_CH1_SYNTH && cal_mode == CAL_CH2) { + throw uhd::runtime_error("cannot enable cal crossover on CH2 when LO1 in CH1 is exported"); + } + if (_lo1_export == LO_CH2_SYNTH && cal_mode == CAL_CH1) { + throw uhd::runtime_error("cannot enable cal crossover on CH1 when LO1 in CH2 is exported"); + } + _set_cal_mode(cal_mode, _lo1_export); + + if (commit) _commit(); + } + + double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + static const double RESOLUTION = 1e3; + + double coerced_freq = 0.0; + if (ch == CH1 or ch == BOTH) { + coerced_freq = _lo1_iface[size_t(CH1)]->set_frequency(freq, RESOLUTION, false); + _lo1_freq[size_t(CH1)] = tune_freq_t(freq); + } + if (ch == CH2 or ch == BOTH) { + coerced_freq = _lo1_iface[size_t(CH2)]->set_frequency(freq, RESOLUTION, false); + _lo1_freq[size_t(CH2)] = tune_freq_t(freq); + } + + if (commit) _commit(); + return coerced_freq; + } + + double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + static const double PRESCALER_THRESH = 3.6e9; + + double coerced_freq = 0.0; + if (ch == CH1 or ch == BOTH) { + _lo2_iface[size_t(CH1)]->set_prescaler(freq > PRESCALER_THRESH ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + coerced_freq = _lo2_iface[size_t(CH1)]->set_frequency(freq, false, false); + _lo2_freq[size_t(CH1)] = tune_freq_t(freq); + } + if (ch == CH2 or ch == BOTH) { + _lo2_iface[size_t(CH2)]->set_prescaler(freq > PRESCALER_THRESH ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + coerced_freq = _lo2_iface[size_t(CH2)]->set_frequency(freq, false, false); + _lo2_freq[size_t(CH2)] = tune_freq_t(freq); + } + + if (commit) _commit(); + return coerced_freq; + } + + bool read_lo1_locked(channel_t ch) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + bool locked = true; + if (ch == CH1 or ch == BOTH) { + locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH1) == 1); + } + if (ch == CH2 or ch == BOTH) { + locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH2) == 1); + } + return locked; + } + + bool read_lo2_locked(channel_t ch) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + bool locked = true; + if (ch == CH1 or ch == BOTH) { + locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH1) == 1); + } + if (ch == CH2 or ch == BOTH) { + locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH2) == 1); + } + return locked; + } + +private: //Functions + void _set_cal_mode(cal_mode_t cal_mode, lo_export_source_t lo1_export_src) + { + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW17_CTRL_CH1, bool2bin(cal_mode!=CAL_CH1)); + _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::SW17_CTRL_CH2, bool2bin(cal_mode!=CAL_CH2)); + _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW18_CTRL_CH1, bool2bin(cal_mode!=CAL_CH1)); + _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW18_CTRL_CH2, bool2bin(cal_mode!=CAL_CH2)); + _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW22_CTRL_CH1, bool2bin((lo1_export_src!=LO_CH1_SYNTH)||(cal_mode==CAL_CH1))); + _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW22_CTRL_CH2, bool2bin((lo1_export_src!=LO_CH2_SYNTH)||(cal_mode==CAL_CH2))); + } + + void _config_lo1_route(lo_config_route_t source) + { + //Route SPI LEs through CPLD (will not assert them) + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH1, bool2bin(source==LO_CONFIG_CH1||source==LO_CONFIG_BOTH)); + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH2, bool2bin(source==LO_CONFIG_CH2||source==LO_CONFIG_BOTH)); + _cpld_regs->rf0_reg2.flush(); + } + + void _config_lo2_route(lo_config_route_t source) + { + //Route SPI LEs through CPLD (will not assert them) + _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH1, bool2bin(source==LO_CONFIG_CH1||source==LO_CONFIG_BOTH)); + _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH2, bool2bin(source==LO_CONFIG_CH2||source==LO_CONFIG_BOTH)); + _cpld_regs->if0_reg2.flush(); + } + + void _write_lo_spi(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) + { + BOOST_FOREACH(boost::uint32_t reg, regs) { + spi_config_t spi_config = spi_config_t(spi_config_t::EDGE_RISE); + spi_config.use_custom_divider = true; + spi_config.divider = 67; + _db_iface->write_spi(unit, spi_config, reg, 32); + } + } + + void _commit() + { + //Commit everything except the LO synthesizers + _cpld_regs->flush(); + + // Disable unused LO synthesizers + _lo1_enable[size_t(CH1)] = _lo1_src[size_t(CH1)] == LO_INTERNAL || + _lo1_src[size_t(CH2)] == LO_COMPANION || + _lo1_export == LO_CH1_SYNTH; + + _lo1_enable[size_t(CH2)] = _lo1_src[size_t(CH2)] == LO_INTERNAL || + _lo1_src[size_t(CH1)] == LO_COMPANION || + _lo1_export == LO_CH2_SYNTH; + _lo2_enable[size_t(CH1)] = _lo2_src[size_t(CH1)] == LO_INTERNAL || + _lo2_src[size_t(CH2)] == LO_COMPANION || + _lo2_export == LO_CH1_SYNTH; + + _lo2_enable[size_t(CH2)] = _lo2_src[size_t(CH2)] == LO_INTERNAL || + _lo2_src[size_t(CH1)] == LO_COMPANION || + _lo2_export == LO_CH2_SYNTH; + + _lo1_iface[size_t(CH1)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH1)].get()); + _lo1_iface[size_t(CH2)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH2)].get()); + + _lo2_iface[size_t(CH1)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH1)].get()); + _lo2_iface[size_t(CH2)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH2)].get()); + + //Commit LO1 frequency + // Commit Channel 1's settings to both channels simultaneously if the frequency is the same. + bool simultaneous_commit_lo1 = _lo1_freq[size_t(CH1)].is_dirty() and + _lo1_freq[size_t(CH2)].is_dirty() and + _lo1_freq[size_t(CH1)].get() == _lo1_freq[size_t(CH2)].get() and + _lo1_enable[size_t(CH1)].get() == _lo1_enable[size_t(CH2)].get(); + + if (simultaneous_commit_lo1) { + _config_lo1_route(LO_CONFIG_BOTH); + //Only commit one of the channels. The route LO_CONFIG_BOTH + //will ensure that the LEs for both channels are enabled + _lo1_iface[size_t(CH1)]->commit(); + _lo1_freq[size_t(CH1)].mark_clean(); + _lo1_freq[size_t(CH2)].mark_clean(); + _lo1_enable[size_t(CH1)].mark_clean(); + _lo1_enable[size_t(CH2)].mark_clean(); + _config_lo1_route(LO_CONFIG_NONE); + } else { + if (_lo1_freq[size_t(CH1)].is_dirty() || _lo1_enable[size_t(CH1)].is_dirty()) { + _config_lo1_route(LO_CONFIG_CH1); + _lo1_iface[size_t(CH1)]->commit(); + _lo1_freq[size_t(CH1)].mark_clean(); + _lo1_enable[size_t(CH1)].mark_clean(); + _config_lo1_route(LO_CONFIG_NONE); + } + if (_lo1_freq[size_t(CH2)].is_dirty() || _lo1_enable[size_t(CH2)].is_dirty()) { + _config_lo1_route(LO_CONFIG_CH2); + _lo1_iface[size_t(CH2)]->commit(); + _lo1_freq[size_t(CH2)].mark_clean(); + _lo1_enable[size_t(CH2)].mark_clean(); + _config_lo1_route(LO_CONFIG_NONE); + } + } + + //Commit LO2 frequency + bool simultaneous_commit_lo2 = _lo2_freq[size_t(CH1)].is_dirty() and + _lo2_freq[size_t(CH2)].is_dirty() and + _lo2_freq[size_t(CH1)].get() == _lo2_freq[size_t(CH2)].get() and + _lo2_enable[size_t(CH1)].get() == _lo2_enable[size_t(CH2)].get(); + + if (simultaneous_commit_lo2) { + _config_lo2_route(LO_CONFIG_BOTH); + //Only commit one of the channels. The route LO_CONFIG_BOTH + //will ensure that the LEs for both channels are enabled + _lo2_iface[size_t(CH1)]->commit(); + _lo2_freq[size_t(CH1)].mark_clean(); + _lo2_freq[size_t(CH2)].mark_clean(); + _lo2_enable[size_t(CH1)].mark_clean(); + _lo2_enable[size_t(CH2)].mark_clean(); + _config_lo2_route(LO_CONFIG_NONE); + } else { + if (_lo2_freq[size_t(CH1)].is_dirty() || _lo2_enable[size_t(CH1)].is_dirty()) { + _config_lo2_route(LO_CONFIG_CH1); + _lo2_iface[size_t(CH1)]->commit(); + _lo2_freq[size_t(CH1)].mark_clean(); + _lo2_enable[size_t(CH1)].mark_clean(); + _config_lo2_route(LO_CONFIG_NONE); + } + if (_lo2_freq[size_t(CH2)].is_dirty() || _lo2_enable[size_t(CH2)].is_dirty()) { + _config_lo2_route(LO_CONFIG_CH2); + _lo2_iface[size_t(CH2)]->commit(); + _lo2_freq[size_t(CH2)].mark_clean(); + _lo2_enable[size_t(CH2)].mark_clean(); + _config_lo2_route(LO_CONFIG_NONE); + } + } + } + +private: //Members + static const size_t NUM_CHANS = 2; + + struct tune_freq_t : public uhd::math::fp_compare::fp_compare_delta<double> { + tune_freq_t() : uhd::math::fp_compare::fp_compare_delta<double>( + 0.0, uhd::math::FREQ_COMPARISON_DELTA_HZ) {} + + tune_freq_t(double freq) : uhd::math::fp_compare::fp_compare_delta<double>( + freq, uhd::math::FREQ_COMPARISON_DELTA_HZ) {} + }; + + boost::mutex _mutex; + dboard_iface::sptr _db_iface; + twinrx_gpio::sptr _gpio_iface; + twinrx_cpld_regmap::sptr _cpld_regs; + adf5355_iface::sptr _lo1_iface[NUM_CHANS]; + adf435x_iface::sptr _lo2_iface[NUM_CHANS]; + lo_source_t _lo1_src[NUM_CHANS]; + lo_source_t _lo2_src[NUM_CHANS]; + dirty_tracked<tune_freq_t> _lo1_freq[NUM_CHANS]; + dirty_tracked<tune_freq_t> _lo2_freq[NUM_CHANS]; + dirty_tracked<bool> _lo1_enable[NUM_CHANS]; + dirty_tracked<bool> _lo2_enable[NUM_CHANS]; + lo_export_source_t _lo1_export; + lo_export_source_t _lo2_export; +}; + +twinrx_ctrl::sptr twinrx_ctrl::make( + dboard_iface::sptr db_iface, + twinrx_gpio::sptr gpio_iface, + twinrx_cpld_regmap::sptr cpld_regmap +) { + return sptr(new twinrx_ctrl_impl(db_iface, gpio_iface, cpld_regmap)); +} diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp new file mode 100644 index 000000000..521e27ae9 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp @@ -0,0 +1,101 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_DBOARD_TWINRX_CTRL_HPP +#define INCLUDED_DBOARD_TWINRX_CTRL_HPP + +#include <boost/noncopyable.hpp> +#include <uhd/types/wb_iface.hpp> +#include "twinrx_io.hpp" + +namespace uhd { namespace usrp { namespace dboard { namespace twinrx { + +class twinrx_ctrl : public boost::noncopyable { +public: + typedef boost::shared_ptr<twinrx_ctrl> sptr; + + static sptr make( + dboard_iface::sptr db_iface, + twinrx_gpio::sptr gpio_iface, + twinrx_cpld_regmap::sptr cpld_regmap); + + virtual ~twinrx_ctrl() {} + + enum channel_t { CH1 = 0, CH2 = 1, BOTH = 2}; + + enum preamp_state_t { PREAMP_LOWBAND, PREAMP_HIGHBAND, PREAMP_BYPASS }; + + enum signal_path_t { PATH_LOWBAND, PATH_HIGHBAND }; + + enum preselector_path_t { PRESEL_PATH1, PRESEL_PATH2, PRESEL_PATH3, PRESEL_PATH4 }; + + enum lo_source_t { LO_INTERNAL, LO_EXTERNAL, LO_COMPANION, LO_DISABLED }; + + enum lo_export_source_t { LO_CH1_SYNTH, LO_CH2_SYNTH, LO_EXPORT_DISABLED }; + + enum antenna_mapping_t { ANTX_NATIVE, ANT1_SHARED, ANT2_SHARED, ANTX_SWAPPED, ANTX_DISABLED }; + + enum lo_config_route_t { LO_CONFIG_CH1, LO_CONFIG_CH2, LO_CONFIG_BOTH, LO_CONFIG_NONE }; + + enum cal_mode_t { CAL_DISABLED, CAL_CH1, CAL_CH2 }; + + virtual void commit() = 0; + + virtual void set_chan_enabled(channel_t ch, bool enabled, bool commit = true) = 0; + + virtual void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true) = 0; + + virtual void set_preamp2(channel_t ch, bool enabled, bool commit = true) = 0; + + virtual void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true) = 0; + + virtual void set_signal_path(channel_t ch, signal_path_t path, bool commit = true) = 0; + + virtual void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true) = 0; + + virtual void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true) = 0; + + virtual void set_input_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0; + + virtual void set_lb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0; + + virtual void set_hb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0; + + virtual void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true) = 0; + + virtual void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true) = 0; + + virtual void set_lo1_export_source(lo_export_source_t source, bool commit = true) = 0; + + virtual void set_lo2_export_source(lo_export_source_t source, bool commit = true) = 0; + + virtual void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true) = 0; + + virtual void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true) = 0; + + virtual double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true) = 0; + + virtual double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true) = 0; + + virtual bool read_lo1_locked(channel_t ch) = 0; + + virtual bool read_lo2_locked(channel_t ch) = 0; +}; + +}}}} //namespaces + +#endif /* INCLUDED_DBOARD_TWINRX_CTRL_HPP */ diff --git a/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp b/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp new file mode 100644 index 000000000..ddaa4211e --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp @@ -0,0 +1,637 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "twinrx_experts.hpp" +#include "twinrx_gain_tables.hpp" +#include <uhd/utils/math.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/dict.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd::experts; +using namespace uhd::math; +using namespace uhd::usrp::dboard::twinrx; + +/*!--------------------------------------------------------- + * twinrx_freq_path_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_freq_path_expert::resolve() +{ + //Lowband/highband switch point + static const double LB_HB_THRESHOLD_FREQ = 1.8e9; + static const double LB_TARGET_IF1_FREQ = 2.345e9; + static const double HB_TARGET_IF1_FREQ = 1.25e9; + static const double INJ_SIDE_THRESHOLD_FREQ = 5.1e9; + + static const double FIXED_LO1_THRESHOLD_FREQ= 50e6; + + //Preselector filter switch point + static const double LB_FILT1_THRESHOLD_FREQ = 0.5e9; + static const double LB_FILT2_THRESHOLD_FREQ = 0.8e9; + static const double LB_FILT3_THRESHOLD_FREQ = 1.2e9; + static const double LB_FILT4_THRESHOLD_FREQ = 1.8e9; + static const double HB_FILT1_THRESHOLD_FREQ = 3.0e9; + static const double HB_FILT2_THRESHOLD_FREQ = 4.1e9; + static const double HB_FILT3_THRESHOLD_FREQ = 5.1e9; + static const double HB_FILT4_THRESHOLD_FREQ = 6.0e9; + + static const double LB_PREAMP_PRESEL_THRESHOLD_FREQ = 0.8e9; + + //Misc + static const double INST_BANDWIDTH = 80e6; + static const double MANUAL_LO_HYSTERESIS_PPM = 1.0; + + static const freq_range_t FREQ_RANGE(10e6, 6e9); + rf_freq_abs_t rf_freq(FREQ_RANGE.clip(_rf_freq_d)); + + // Choose low-band vs high-band depending on frequency + _signal_path = (rf_freq > LB_HB_THRESHOLD_FREQ) ? + twinrx_ctrl::PATH_HIGHBAND : twinrx_ctrl::PATH_LOWBAND; + if (_signal_path == twinrx_ctrl::PATH_LOWBAND) { + // Choose low-band preselector filter + if (rf_freq < LB_FILT1_THRESHOLD_FREQ) { + _lb_presel = twinrx_ctrl::PRESEL_PATH1; + } else if (rf_freq < LB_FILT2_THRESHOLD_FREQ) { + _lb_presel = twinrx_ctrl::PRESEL_PATH2; + } else if (rf_freq < LB_FILT3_THRESHOLD_FREQ) { + _lb_presel = twinrx_ctrl::PRESEL_PATH3; + } else if (rf_freq < LB_FILT4_THRESHOLD_FREQ) { + _lb_presel = twinrx_ctrl::PRESEL_PATH4; + } else { + _lb_presel = twinrx_ctrl::PRESEL_PATH4; + } + } else if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) { + // Choose high-band preselector filter + if (rf_freq < HB_FILT1_THRESHOLD_FREQ) { + _hb_presel = twinrx_ctrl::PRESEL_PATH1; + } else if (rf_freq < HB_FILT2_THRESHOLD_FREQ) { + _hb_presel = twinrx_ctrl::PRESEL_PATH2; + } else if (rf_freq < HB_FILT3_THRESHOLD_FREQ) { + _hb_presel = twinrx_ctrl::PRESEL_PATH3; + } else if (rf_freq < HB_FILT4_THRESHOLD_FREQ) { + _hb_presel = twinrx_ctrl::PRESEL_PATH4; + } else { + _hb_presel = twinrx_ctrl::PRESEL_PATH4; + } + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + + //Choose low-band preamp preselector + _lb_preamp_presel = (rf_freq > LB_PREAMP_PRESEL_THRESHOLD_FREQ); + + //Choose LO frequencies + const double target_if1_freq = (_signal_path == twinrx_ctrl::PATH_HIGHBAND) ? + HB_TARGET_IF1_FREQ : LB_TARGET_IF1_FREQ; + const double target_if2_freq = _if_freq_d; + + // LO1 + double lo1_freq_ideal = 0.0, lo2_freq_ideal = 0.0; + if (rf_freq <= FIXED_LO1_THRESHOLD_FREQ) { + //LO1 Freq static + lo1_freq_ideal = target_if1_freq + FIXED_LO1_THRESHOLD_FREQ; + } else if (rf_freq <= INJ_SIDE_THRESHOLD_FREQ) { + //High-side LO1 Injection + lo1_freq_ideal = rf_freq.get() + target_if1_freq; + } else { + //Low-side LO1 Injection + lo1_freq_ideal = rf_freq.get() - target_if1_freq; + } + + if (_lo1_freq_d.get_author() == experts::AUTHOR_USER) { + if (_lo1_freq_d.is_dirty()) { //Are we here because the LO frequency was set? + // The user explicitly requested to set the LO freq so don't touch it! + } else { + // Something else changed which may cause the LO frequency to update. + // Only commit if the frequency is stale. If the user's value is stale + // reset the author to expert. + if (rf_freq_ppm_t(lo1_freq_ideal, MANUAL_LO_HYSTERESIS_PPM) != _lo1_freq_d.get()) { + _lo1_freq_d = lo1_freq_ideal; //Reset author + } + } + } else { + // The LO frequency was never set by the user. Let the expert take care of it + _lo1_freq_d = lo1_freq_ideal; //Reset author + } + + // LO2 + lo_inj_side_t lo2_inj_side_ideal = _compute_lo2_inj_side( + lo1_freq_ideal, target_if1_freq, target_if2_freq, INST_BANDWIDTH); + if (lo2_inj_side_ideal == INJ_HIGH_SIDE) { + lo2_freq_ideal = target_if1_freq + target_if2_freq; + } else { + lo2_freq_ideal = target_if1_freq - target_if2_freq; + } + + if (_lo2_freq_d.get_author() == experts::AUTHOR_USER) { + if (_lo2_freq_d.is_dirty()) { //Are we here because the LO frequency was set? + // The user explicitly requested to set the LO freq so don't touch it! + } else { + // Something else changed which may cause the LO frequency to update. + // Only commit if the frequency is stale. If the user's value is stale + // reset the author to expert. + if (rf_freq_ppm_t(lo2_freq_ideal, MANUAL_LO_HYSTERESIS_PPM) != _lo2_freq_d.get()) { + _lo2_freq_d = lo2_freq_ideal; //Reset author + } + } + } else { + // The LO frequency was never set by the user. Let the expert take care of it + _lo2_freq_d = lo2_freq_ideal; //Reset author + } + + // Determine injection side using the final LO frequency + _lo1_inj_side = (_lo1_freq_d > rf_freq.get()) ? INJ_HIGH_SIDE : INJ_LOW_SIDE; + _lo2_inj_side = (_lo2_freq_d > target_if1_freq) ? INJ_HIGH_SIDE : INJ_LOW_SIDE; +} + +lo_inj_side_t twinrx_freq_path_expert::_compute_lo2_inj_side( + double lo1_freq, double if1_freq, double if2_freq, double bandwidth +) { + static const int MAX_SPUR_ORDER = 5; + for (int ord = MAX_SPUR_ORDER; ord >= 1; ord--) { + // Check high-side injection first + if (not _has_mixer_spurs(lo1_freq, if1_freq + if2_freq, if2_freq, bandwidth, ord)) { + return INJ_HIGH_SIDE; + } + // Check low-side injection second + if (not _has_mixer_spurs(lo1_freq, if1_freq - if2_freq, if2_freq, bandwidth, ord)) { + return INJ_LOW_SIDE; + } + } + // If we reached here, then there are spurs everywhere. Pick high-side as the default + return INJ_HIGH_SIDE; +} + +bool twinrx_freq_path_expert::_has_mixer_spurs( + double lo1_freq, double lo2_freq, double if2_freq, + double bandwidth, int spur_order +) { + // Iterate through all N-th order harmomic combinations + // of LOs... + for (int lo1h_i = 1; lo1h_i <= spur_order; lo1h_i++) { + double lo1harm_freq = lo1_freq * lo1h_i; + for (int lo2h_i = 1; lo2h_i <= spur_order; lo2h_i++) { + double lo2harm_freq = lo2_freq * lo2h_i; + double hdelta = lo1harm_freq - lo2harm_freq; + // .. and check if there is a mixer spur in the IF band + if (std::abs(hdelta + if2_freq) < bandwidth/2 or + std::abs(hdelta - if2_freq) < bandwidth/2) { + return true; + } + } + } + // No spurs were found after NxN search + return false; +} + +/*!--------------------------------------------------------- + * twinrx_freq_coercion_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_freq_coercion_expert::resolve() +{ + const double actual_if2_freq = _if_freq_d; + const double actual_if1_freq = (_lo2_inj_side == INJ_LOW_SIDE) ? + (_lo2_freq_c + actual_if2_freq) : (_lo2_freq_c - actual_if2_freq); + + _rf_freq_c = (_lo1_inj_side == INJ_LOW_SIDE) ? + (_lo1_freq_c + actual_if1_freq) : (_lo1_freq_c - actual_if1_freq); +} + +/*!--------------------------------------------------------- + * twinrx_nyquist_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_nyquist_expert::resolve() +{ + double if_freq_sign = 1.0; + if (_lo1_inj_side == INJ_HIGH_SIDE) if_freq_sign *= -1.0; + if (_lo2_inj_side == INJ_HIGH_SIDE) if_freq_sign *= -1.0; + _if_freq_c = _if_freq_d * if_freq_sign; + + _db_iface->set_fe_connection(dboard_iface::UNIT_RX, _channel, + usrp::fe_connection_t(_codec_conn, _if_freq_c)); +} + +/*!--------------------------------------------------------- + * twinrx_chan_gain_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_chan_gain_expert::resolve() +{ + if (_gain_profile != "default") { + //TODO: Implement me! + throw uhd::not_implemented_error("custom gain strategies not implemeted yet"); + } + + //Lookup table using settings + const twinrx_gain_table table = twinrx_gain_table::lookup_table( + _signal_path, + (_signal_path==twinrx_ctrl::PATH_HIGHBAND) ? _hb_presel : _lb_presel, + _gain_profile); + + //Compute minimum gain. The user-specified gain value will be interpreted as + //the gain applied on top of the minimum gain state. + //If antennas are shared or swapped, the switch has 6dB of loss + size_t gain_index = std::min(static_cast<size_t>(boost::math::round(_gain.get())), table.get_num_entries()-1); + + //Translate gain to an index in the gain table + const twinrx_gain_config_t& config = table.find_by_index(gain_index); + + _input_atten = config.atten1; + if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) { + _hb_atten = config.atten2; + } else { + _lb_atten = config.atten2; + } + + // Preamp 1 should use the Highband amp for frequencies above 3 GHz + if (_signal_path == twinrx_ctrl::PATH_HIGHBAND && _hb_presel != twinrx_ctrl::PRESEL_PATH1) { + _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_HIGHBAND : twinrx_ctrl::PREAMP_BYPASS; + } else { + _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_LOWBAND : twinrx_ctrl::PREAMP_BYPASS; + } + + _preamp2 = config.amp2; +} + +/*!--------------------------------------------------------- + * twinrx_lo_config_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_lo_config_expert::resolve() +{ + static const uhd::dict<std::string, twinrx_ctrl::lo_source_t> src_lookup = + boost::assign::map_list_of + ("internal", twinrx_ctrl::LO_INTERNAL) + ("external", twinrx_ctrl::LO_EXTERNAL) + ("companion", twinrx_ctrl::LO_COMPANION) + ("disabled", twinrx_ctrl::LO_DISABLED); + + if (src_lookup.has_key(_lo_source_ch0)) { + _lo1_src_ch0 = _lo2_src_ch0 = src_lookup[_lo_source_ch0]; + } else { + throw uhd::value_error("Invalid LO source for channel 0.Choose from {internal, external, companion}"); + } + if (src_lookup.has_key(_lo_source_ch1)) { + _lo1_src_ch1 = _lo2_src_ch1 = src_lookup[_lo_source_ch1]; + } else { + throw uhd::value_error("Invalid LO source for channel 1.Choose from {internal, external, companion}"); + } + + twinrx_ctrl::lo_export_source_t export_src = twinrx_ctrl::LO_EXPORT_DISABLED; + if (_lo_export_ch0 and (_lo_source_ch0 == "external")) { + throw uhd::value_error("Cannot export an external LO for channel 0"); + } + if (_lo_export_ch1 and (_lo_source_ch1 == "external")) { + throw uhd::value_error("Cannot export an external LO for channel 1"); + } + if (_lo_export_ch0 and _lo_export_ch1) { + throw uhd::value_error("Cannot export LOs for both channels"); + } else if (_lo_export_ch0) { + export_src = (_lo1_src_ch0 == twinrx_ctrl::LO_INTERNAL) ? + twinrx_ctrl::LO_CH1_SYNTH : twinrx_ctrl::LO_CH2_SYNTH; + } else if (_lo_export_ch1) { + export_src = (_lo1_src_ch1 == twinrx_ctrl::LO_INTERNAL) ? + twinrx_ctrl::LO_CH2_SYNTH : twinrx_ctrl::LO_CH1_SYNTH; + } + _lo1_export_src = _lo2_export_src = export_src; +} + +/*!--------------------------------------------------------- + * twinrx_lo_freq_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_lo_mapping_expert::resolve() +{ + static const size_t CH0_MSK = 0x1; + static const size_t CH1_MSK = 0x2; + + // Determine which channels are "driving" each synthesizer + // First check for explicit requests i.e. lo_source "internal" or "companion" + size_t synth_map[] = {0, 0}; + if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL) { + synth_map[0] = synth_map[0] | CH0_MSK; + } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) { + synth_map[1] = synth_map[1] | CH0_MSK; + } + if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL) { + synth_map[1] = synth_map[1] | CH1_MSK; + } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) { + synth_map[0] = synth_map[0] | CH1_MSK; + } + + // If a particular channel has its LO source disabled then the other + // channel is automatically put in hop mode i.e. the synthesizer that + // belongs to the disabled channel can be re-purposed as a redundant LO + // to overlap tuning with signal dwell time. + bool hopping_enabled = false; + if (_lox_src_ch0 == twinrx_ctrl::LO_DISABLED) { + if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL) { + synth_map[0] = synth_map[0] | CH0_MSK; + hopping_enabled = true; + } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) { + synth_map[1] = synth_map[1] | CH0_MSK; + hopping_enabled = true; + } + } + if (_lox_src_ch1 == twinrx_ctrl::LO_DISABLED) { + if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL) { + synth_map[1] = synth_map[1] | CH1_MSK; + hopping_enabled = true; + } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) { + synth_map[0] = synth_map[0] | CH1_MSK; + hopping_enabled = true; + } + } + + // For each synthesizer come up with the final mapping + for (size_t synth = 0; synth < 2; synth++) { + experts::data_writer_t<lo_synth_mapping_t>& lox_mapping = + (synth == 0) ? _lox_mapping_synth0 : _lox_mapping_synth1; + if (synth_map[synth] == (CH0_MSK|CH1_MSK)) { + lox_mapping = MAPPING_SHARED; + } else if (synth_map[synth] == CH0_MSK) { + lox_mapping = MAPPING_CH0; + } else if (synth_map[synth] == CH1_MSK) { + lox_mapping = MAPPING_CH1; + } else { + lox_mapping = MAPPING_NONE; + } + } + _lox_hopping_enabled = hopping_enabled; +} + +/*!--------------------------------------------------------- + * twinrx_antenna_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_antenna_expert::resolve() +{ + static const std::string ANT0 = "RX1", ANT1 = "RX2"; + + if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT1) { + _ant_mapping = twinrx_ctrl::ANTX_NATIVE; + } else if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT0) { + if (_enabled_ch0 and _enabled_ch1) { + _ant_mapping = twinrx_ctrl::ANT1_SHARED; + } else if (_enabled_ch0) { + _ant_mapping = twinrx_ctrl::ANTX_NATIVE; + } else if (_enabled_ch1) { + _ant_mapping = twinrx_ctrl::ANTX_SWAPPED; + } + } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT1) { + if (_enabled_ch0 and _enabled_ch1) { + _ant_mapping = twinrx_ctrl::ANT2_SHARED; + } else if (_enabled_ch0) { + _ant_mapping = twinrx_ctrl::ANTX_SWAPPED; + } else if (_enabled_ch1) { + _ant_mapping = twinrx_ctrl::ANTX_NATIVE; + } + } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT0) { + _ant_mapping = twinrx_ctrl::ANTX_SWAPPED; + } else if (_antenna_ch0 != ANT0 and _antenna_ch0 != ANT1) { + throw uhd::value_error("Invalid antenna selection " + _antenna_ch0.get() + " for channel 0. Must be " + ANT0 + " or " + ANT1); + } else if (_antenna_ch1 != ANT0 and _antenna_ch1 != ANT1) { + throw uhd::value_error("Invalid antenna selection " + _antenna_ch1.get() + " for channel 1. Must be " + ANT0 + " or " + ANT1); + } + + //TODO: Implement hooks for the calibration switch + _cal_mode = twinrx_ctrl::CAL_DISABLED; + + if (_cal_mode == twinrx_ctrl::CAL_CH1 and _lo_export_ch1) { + throw uhd::value_error("Cannot calibrate channel 0 and export the LO for channel 1."); + } else if (_cal_mode == twinrx_ctrl::CAL_CH2 and _lo_export_ch0) { + throw uhd::value_error("Cannot calibrate channel 1 and export the LO for channel 0."); + } +} + +/*!--------------------------------------------------------- + * twinrx_ant_gain_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_ant_gain_expert::resolve() +{ + switch (_ant_mapping) { + case twinrx_ctrl::ANTX_NATIVE: + _ant0_input_atten = _ch0_input_atten; + _ant0_preamp1 = _ch0_preamp1; + _ant0_preamp2 = _ch0_preamp2; + _ant0_lb_preamp_presel = _ch0_lb_preamp_presel; + _ant1_input_atten = _ch1_input_atten; + _ant1_preamp1 = _ch1_preamp1; + _ant1_preamp2 = _ch1_preamp2; + _ant1_lb_preamp_presel = _ch1_lb_preamp_presel; + break; + case twinrx_ctrl::ANTX_SWAPPED: + _ant0_input_atten = _ch1_input_atten; + _ant0_preamp1 = _ch1_preamp1; + _ant0_preamp2 = _ch1_preamp2; + _ant0_lb_preamp_presel = _ch1_lb_preamp_presel; + _ant1_input_atten = _ch0_input_atten; + _ant1_preamp1 = _ch0_preamp1; + _ant1_preamp2 = _ch0_preamp2; + _ant1_lb_preamp_presel = _ch0_lb_preamp_presel; + break; + case twinrx_ctrl::ANT1_SHARED: + if ((_ch0_input_atten != _ch1_input_atten) or + (_ch0_preamp1 != _ch1_preamp1) or + (_ch0_preamp2 != _ch1_preamp2) or + (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel)) + { + UHD_MSG(warning) << "incompatible gain settings for antenna sharing. temporarily using Ch0 settings for Ch1."; + } + _ant0_input_atten = _ch0_input_atten; + _ant0_preamp1 = _ch0_preamp1; + _ant0_preamp2 = _ch0_preamp2; + _ant0_lb_preamp_presel = _ch0_lb_preamp_presel; + + _ant1_input_atten = 0; + _ant1_preamp1 = twinrx_ctrl::PREAMP_BYPASS; + _ant1_preamp2 = false; + _ant1_lb_preamp_presel = false; + break; + case twinrx_ctrl::ANT2_SHARED: + if ((_ch0_input_atten != _ch1_input_atten) or + (_ch0_preamp1 != _ch1_preamp1) or + (_ch0_preamp2 != _ch1_preamp2) or + (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel)) + { + UHD_MSG(warning) << "incompatible gain settings for antenna sharing. temporarily using Ch0 settings for Ch1."; + } + _ant1_input_atten = _ch0_input_atten; + _ant1_preamp1 = _ch0_preamp1; + _ant1_preamp2 = _ch0_preamp2; + _ant1_lb_preamp_presel = _ch0_lb_preamp_presel; + + _ant0_input_atten = 0; + _ant0_preamp1 = twinrx_ctrl::PREAMP_BYPASS; + _ant0_preamp2 = false; + _ant0_lb_preamp_presel = false; + break; + default: + _ant0_input_atten = 0; + _ant0_preamp1 = twinrx_ctrl::PREAMP_BYPASS; + _ant0_preamp2 = false; + _ant0_lb_preamp_presel = false; + _ant1_input_atten = 0; + _ant1_preamp1 = twinrx_ctrl::PREAMP_BYPASS; + _ant1_preamp2 = false; + _ant1_lb_preamp_presel = false; + break; + } +} + +/*!--------------------------------------------------------- + * twinrx_settings_expert::resolve + * --------------------------------------------------------- + */ +const bool twinrx_settings_expert::FORCE_COMMIT = false; + +void twinrx_settings_expert::resolve() +{ + for (size_t i = 0; i < 2; i++) { + ch_settings& ch_set = (i == 1) ? _ch1 : _ch0; + twinrx_ctrl::channel_t ch = (i == 1) ? twinrx_ctrl::CH2 : twinrx_ctrl::CH1; + _ctrl->set_chan_enabled(ch, ch_set.chan_enabled, FORCE_COMMIT); + _ctrl->set_preamp1(ch, ch_set.preamp1, FORCE_COMMIT); + _ctrl->set_preamp2(ch, ch_set.preamp2, FORCE_COMMIT); + _ctrl->set_lb_preamp_preselector(ch, ch_set.lb_preamp_presel, FORCE_COMMIT); + _ctrl->set_signal_path(ch, ch_set.signal_path, FORCE_COMMIT); + _ctrl->set_lb_preselector(ch, ch_set.lb_presel, FORCE_COMMIT); + _ctrl->set_hb_preselector(ch, ch_set.hb_presel, FORCE_COMMIT); + _ctrl->set_input_atten(ch, ch_set.input_atten, FORCE_COMMIT); + _ctrl->set_lb_atten(ch, ch_set.lb_atten, FORCE_COMMIT); + _ctrl->set_hb_atten(ch, ch_set.hb_atten, FORCE_COMMIT); + _ctrl->set_lo1_source(ch, ch_set.lo1_source, FORCE_COMMIT); + _ctrl->set_lo2_source(ch, ch_set.lo2_source, FORCE_COMMIT); + } + + _resolve_lox_freq(STAGE_LO1, + _ch0.lo1_freq_d, _ch1.lo1_freq_d, _ch0.lo1_freq_c, _ch1.lo1_freq_c, + _ch0.lo1_source, _ch1.lo1_source, _lo1_synth0_mapping, _lo1_synth1_mapping, + _lo1_hopping_enabled); + _resolve_lox_freq(STAGE_LO2, + _ch0.lo2_freq_d, _ch1.lo2_freq_d, _ch0.lo2_freq_c, _ch1.lo2_freq_c, + _ch0.lo2_source, _ch1.lo2_source, _lo2_synth0_mapping, _lo2_synth1_mapping, + _lo2_hopping_enabled); + + _ctrl->set_lo1_export_source(_lo1_export_src, FORCE_COMMIT); + _ctrl->set_lo2_export_source(_lo2_export_src, FORCE_COMMIT); + _ctrl->set_antenna_mapping(_ant_mapping, FORCE_COMMIT); + //TODO: Re-enable this when we support this mode + //_ctrl->set_crossover_cal_mode(_cal_mode, FORCE_COMMIT); + + _ctrl->commit(); +} + +void twinrx_settings_expert::_resolve_lox_freq( + lo_stage_t lo_stage, + uhd::experts::data_reader_t<double>& ch0_freq_d, + uhd::experts::data_reader_t<double>& ch1_freq_d, + uhd::experts::data_writer_t<double>& ch0_freq_c, + uhd::experts::data_writer_t<double>& ch1_freq_c, + twinrx_ctrl::lo_source_t ch0_lo_source, + twinrx_ctrl::lo_source_t ch1_lo_source, + lo_synth_mapping_t synth0_mapping, + lo_synth_mapping_t synth1_mapping, + bool hopping_enabled) +{ + if (ch0_lo_source == twinrx_ctrl::LO_EXTERNAL) { + // If the LO is external then we don't need to program any synthesizers + ch0_freq_c = ch0_freq_d; + } else { + // When in hopping mode, only attempt to write the LO frequency if it is actually + // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not + // hopping, then always write the frequency because other inputs might require + // an LO re-commit + const bool freq_update_request = (not hopping_enabled) or ch0_freq_d.is_dirty(); + if (synth0_mapping == MAPPING_CH0 and freq_update_request) { + ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch0_freq_d); + } else if (synth1_mapping == MAPPING_CH0 and freq_update_request) { + ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch0_freq_d); + } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) { + // If any synthesizer is being shared then we are not in hopping mode + if (rf_freq_ppm_t(ch0_freq_d) != ch1_freq_d) { + UHD_MSG(warning) << + "Incompatible RF/LO frequencies for LO sharing. Using Ch0 settings for both channels."; + } + twinrx_ctrl::channel_t ch = (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2; + ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d); + ch1_freq_c = ch0_freq_c; + } + } + + if (ch1_lo_source == twinrx_ctrl::LO_EXTERNAL) { + // If the LO is external then we don't need to program any synthesizers + ch1_freq_c = ch1_freq_d; + } else { + // When in hopping mode, only attempt to write the LO frequency if it is actually + // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not + // hopping, then always write the frequency because other inputs might require + // an LO re-commit + const bool freq_update_request = (not hopping_enabled) or ch1_freq_d.is_dirty(); + // As an additional layer of protection from unnecessarily committing the LO, check + // if the frequency has actually changed. + if (synth0_mapping == MAPPING_CH1 and freq_update_request) { + ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch1_freq_d); + } else if (synth1_mapping == MAPPING_CH1 and freq_update_request) { + ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch1_freq_d); + } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) { + // If any synthesizer is being shared then we are not in hopping mode + if (rf_freq_ppm_t(ch0_freq_d) != ch1_freq_d) { + UHD_MSG(warning) << + "Incompatible RF/LO frequencies for LO sharing. Using Ch0 settings for both channels."; + } + twinrx_ctrl::channel_t ch = (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2; + ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d); + ch1_freq_c = ch0_freq_c; + } + } +} + +double twinrx_settings_expert::_set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq) +{ + lo_freq_cache_t* freq_cache = NULL; + if (stage == STAGE_LO1) { + freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo1_synth0_freq : &_cached_lo1_synth1_freq; + } else if (stage == STAGE_LO2) { + freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo2_synth0_freq : &_cached_lo2_synth1_freq; + } else { + throw uhd::assertion_error("Invalid LO stage"); + } + + // Check if the frequency has actually changed before configuring synthesizers + double coerced_freq = 0.0; + if (freq_cache->desired != freq) { + if (stage == STAGE_LO1) { + coerced_freq = _ctrl->set_lo1_synth_freq(ch, freq, FORCE_COMMIT); + } else { + coerced_freq = _ctrl->set_lo2_synth_freq(ch, freq, FORCE_COMMIT); + } + freq_cache->desired = rf_freq_ppm_t(freq); + freq_cache->coerced = coerced_freq; + } else { + coerced_freq = freq_cache->coerced; + } + return coerced_freq; +} + diff --git a/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp b/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp new file mode 100644 index 000000000..f2601a09b --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp @@ -0,0 +1,630 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_DBOARD_TWINRX_EXPERTS_HPP +#define INCLUDED_DBOARD_TWINRX_EXPERTS_HPP + +#include "twinrx_ctrl.hpp" +#include "expert_nodes.hpp" +#include <uhd/utils/math.hpp> + +namespace uhd { namespace usrp { namespace dboard { namespace twinrx { + +//--------------------------------------------------------- +// Misc types and definitions +//--------------------------------------------------------- + +struct rf_freq_abs_t : public uhd::math::fp_compare::fp_compare_delta<double> { + rf_freq_abs_t(double freq = 0.0, double epsilon = 1.0 /* 1Hz epsilon */) : + uhd::math::fp_compare::fp_compare_delta<double>(freq, epsilon) {} + inline double get() const { return _value; } +}; + +struct rf_freq_ppm_t : public rf_freq_abs_t { + rf_freq_ppm_t(double freq = 0.0, double epsilon_ppm = 0.1 /* 1PPM epsilon */) : + rf_freq_abs_t(freq, 1e-6 * freq * epsilon_ppm) {} +}; + +enum lo_stage_t { STAGE_LO1, STAGE_LO2 }; +enum lo_inj_side_t { INJ_LOW_SIDE, INJ_HIGH_SIDE }; +enum lo_synth_mapping_t { MAPPING_NONE, MAPPING_CH0, MAPPING_CH1, MAPPING_SHARED }; + +static const std::string prepend_ch(std::string name, const std::string& ch) { + return ch + "/" + name; +} + +static const std::string lo_stage_str(lo_stage_t stage, bool lower = false) { + std::string prefix = lower ? "lo" : "LO"; + return prefix + ((stage == STAGE_LO1) ? "1" : "2"); +} + +/*!--------------------------------------------------------- + * twinrx_freq_path_expert + * + * This expert is responsble for translating a user-specified + * RF and IF center frequency into TwinRX specific settings + * like band, preselector path, LO frequency and injection + * sides for both the LO stages. + * + * One instance of this expert is required for each channel + * --------------------------------------------------------- + */ +class twinrx_freq_path_expert : public experts::worker_node_t { +public: + twinrx_freq_path_expert(const experts::node_retriever_t& db, std::string ch) + : experts::worker_node_t(prepend_ch("twinrx_freq_path_expert", ch)), + _rf_freq_d (db, prepend_ch("freq/desired", ch)), + _if_freq_d (db, prepend_ch("if_freq/desired", ch)), + _signal_path (db, prepend_ch("ch/signal_path", ch)), + _lb_presel (db, prepend_ch("ch/lb_presel", ch)), + _hb_presel (db, prepend_ch("ch/hb_presel", ch)), + _lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", ch)), + _lo1_freq_d (db, prepend_ch("los/LO1/freq/desired", ch)), + _lo2_freq_d (db, prepend_ch("los/LO2/freq/desired", ch)), + _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)), + _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch)) + { + bind_accessor(_rf_freq_d); + bind_accessor(_if_freq_d); + bind_accessor(_signal_path); + bind_accessor(_lb_presel); + bind_accessor(_hb_presel); + bind_accessor(_lb_preamp_presel); + bind_accessor(_lo1_freq_d); + bind_accessor(_lo2_freq_d); + bind_accessor(_lo1_inj_side); + bind_accessor(_lo2_inj_side); + } + +private: + virtual void resolve(); + static lo_inj_side_t _compute_lo2_inj_side( + double lo1_freq, double if1_freq, double if2_freq, double bandwidth); + static bool _has_mixer_spurs( + double lo1_freq, double lo2_freq, double if2_freq, + double bandwidth, int spur_order); + + //Inputs + experts::data_reader_t<double> _rf_freq_d; + experts::data_reader_t<double> _if_freq_d; + //Outputs + experts::data_writer_t<twinrx_ctrl::signal_path_t> _signal_path; + experts::data_writer_t<twinrx_ctrl::preselector_path_t> _lb_presel; + experts::data_writer_t<twinrx_ctrl::preselector_path_t> _hb_presel; + experts::data_writer_t<bool> _lb_preamp_presel; + experts::data_writer_t<double> _lo1_freq_d; + experts::data_writer_t<double> _lo2_freq_d; + experts::data_writer_t<lo_inj_side_t> _lo1_inj_side; + experts::data_writer_t<lo_inj_side_t> _lo2_inj_side; +}; + +/*!--------------------------------------------------------- + * twinrx_lo_config_expert + * + * This expert is responsible for translating high level + * channel-scoped LO source and export settings to low-level + * channel-scoped settings. The expert only deals with + * the source and export attributes, not frequency. + * + * One instance of this expert is required for all channels + * --------------------------------------------------------- + */ +class twinrx_lo_config_expert : public experts::worker_node_t { +public: + twinrx_lo_config_expert(const experts::node_retriever_t& db) + : experts::worker_node_t("twinrx_lo_config_expert"), + _lo_source_ch0 (db, prepend_ch("los/all/source", "0")), + _lo_source_ch1 (db, prepend_ch("los/all/source", "1")), + _lo_export_ch0 (db, prepend_ch("los/all/export", "0")), + _lo_export_ch1 (db, prepend_ch("los/all/export", "1")), + _lo1_src_ch0 (db, prepend_ch("ch/LO1/source", "0")), + _lo1_src_ch1 (db, prepend_ch("ch/LO1/source", "1")), + _lo2_src_ch0 (db, prepend_ch("ch/LO2/source", "0")), + _lo2_src_ch1 (db, prepend_ch("ch/LO2/source", "1")), + _lo1_export_src (db, "com/LO1/export_source"), + _lo2_export_src (db, "com/LO2/export_source") + { + bind_accessor(_lo_source_ch0); + bind_accessor(_lo_source_ch1); + bind_accessor(_lo_export_ch0); + bind_accessor(_lo_export_ch1); + bind_accessor(_lo1_src_ch0); + bind_accessor(_lo1_src_ch1); + bind_accessor(_lo2_src_ch0); + bind_accessor(_lo2_src_ch1); + bind_accessor(_lo1_export_src); + bind_accessor(_lo2_export_src); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<std::string> _lo_source_ch0; + experts::data_reader_t<std::string> _lo_source_ch1; + experts::data_reader_t<bool> _lo_export_ch0; + experts::data_reader_t<bool> _lo_export_ch1; + //Outputs + experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo1_src_ch0; + experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo1_src_ch1; + experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo2_src_ch0; + experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo2_src_ch1; + experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src; + experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src; +}; + +/*!--------------------------------------------------------- + * twinrx_lo_mapping_expert + * + * This expert is responsible for translating low-level + * channel-scoped LO source and export settings to low-level + * synthesizer-scoped settings. The expert deals with the + * extremely flexible channel->synthesizer mapping and handles + * frequency hopping modes. + * + * One instance of this expert is required for each LO stage + * --------------------------------------------------------- + */ +class twinrx_lo_mapping_expert : public experts::worker_node_t { +public: + twinrx_lo_mapping_expert(const experts::node_retriever_t& db, lo_stage_t stage) + : experts::worker_node_t("twinrx_" + lo_stage_str(stage, true) + "_mapping_expert"), + _lox_src_ch0 (db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "0")), + _lox_src_ch1 (db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "1")), + _lox_mapping_synth0 (db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "0")), + _lox_mapping_synth1 (db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "1")), + _lox_hopping_enabled (db, "com/synth/" + lo_stage_str(stage) + "/hopping_enabled") + { + bind_accessor(_lox_src_ch0); + bind_accessor(_lox_src_ch1); + bind_accessor(_lox_mapping_synth0); + bind_accessor(_lox_mapping_synth1); + bind_accessor(_lox_hopping_enabled); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<twinrx_ctrl::lo_source_t> _lox_src_ch0; + experts::data_reader_t<twinrx_ctrl::lo_source_t> _lox_src_ch1; + //Outputs + experts::data_writer_t<lo_synth_mapping_t> _lox_mapping_synth0; + experts::data_writer_t<lo_synth_mapping_t> _lox_mapping_synth1; + experts::data_writer_t<bool> _lox_hopping_enabled; +}; + +/*!--------------------------------------------------------- + * twinrx_freq_coercion_expert + * + * This expert is responsible for calculating the coerced + * RF frequency after most settings and modes have been + * resolved. + * + * One instance of this expert is required for each channel + * --------------------------------------------------------- + */ +class twinrx_freq_coercion_expert : public experts::worker_node_t { +public: + twinrx_freq_coercion_expert(const experts::node_retriever_t& db, std::string ch) + : experts::worker_node_t(prepend_ch("twinrx_freq_coercion_expert", ch)), + _lo1_freq_c (db, prepend_ch("los/LO1/freq/coerced", ch)), + _lo2_freq_c (db, prepend_ch("los/LO2/freq/coerced", ch)), + _if_freq_d (db, prepend_ch("if_freq/desired", ch)), + _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)), + _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch)), + _rf_freq_c (db, prepend_ch("freq/coerced", ch)) + { + bind_accessor(_lo1_freq_c); + bind_accessor(_lo2_freq_c); + bind_accessor(_if_freq_d); + bind_accessor(_lo1_inj_side); + bind_accessor(_lo2_inj_side); + bind_accessor(_rf_freq_c); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<double> _lo1_freq_c; + experts::data_reader_t<double> _lo2_freq_c; + experts::data_reader_t<double> _if_freq_d; + experts::data_reader_t<lo_inj_side_t> _lo1_inj_side; + experts::data_reader_t<lo_inj_side_t> _lo2_inj_side; + //Outputs + experts::data_writer_t<double> _rf_freq_c; +}; + +/*!--------------------------------------------------------- + * twinrx_nyquist_expert + * + * This expert is responsible for figuring out the DSP + * front-end settings required for each channel + * + * One instance of this expert is required for each channel + * --------------------------------------------------------- + */ +class twinrx_nyquist_expert : public experts::worker_node_t { +public: + twinrx_nyquist_expert(const experts::node_retriever_t& db, std::string ch, + dboard_iface::sptr db_iface) + : experts::worker_node_t(prepend_ch("twinrx_nyquist_expert", ch)), + _channel (ch), + _codec_conn (ch=="0"?"II":"QQ"), //Ch->ADC Port mapping + _if_freq_d (db, prepend_ch("if_freq/desired", ch)), + _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)), + _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch)), + _if_freq_c (db, prepend_ch("if_freq/coerced", ch)), + _db_iface (db_iface) + { + bind_accessor(_if_freq_d); + bind_accessor(_lo1_inj_side); + bind_accessor(_lo2_inj_side); + bind_accessor(_if_freq_c); + } + +private: + virtual void resolve(); + + //Inputs + const std::string _channel; + const std::string _codec_conn; + experts::data_reader_t<double> _if_freq_d; + experts::data_reader_t<lo_inj_side_t> _lo1_inj_side; + experts::data_reader_t<lo_inj_side_t> _lo2_inj_side; + //Outputs + experts::data_writer_t<double> _if_freq_c; + dboard_iface::sptr _db_iface; +}; + +/*!--------------------------------------------------------- + * twinrx_antenna_expert + * + * This expert is responsible for translating high-level + * antenna selection settings and channel enables to low-level + * switch configurations. + * + * One instance of this expert is required for all channels + * --------------------------------------------------------- + */ +class twinrx_antenna_expert : public experts::worker_node_t { +public: + twinrx_antenna_expert(const experts::node_retriever_t& db) + : experts::worker_node_t("twinrx_antenna_expert"), + _antenna_ch0 (db, prepend_ch("antenna", "0")), + _antenna_ch1 (db, prepend_ch("antenna", "1")), + _enabled_ch0 (db, prepend_ch("enabled", "0")), + _enabled_ch1 (db, prepend_ch("enabled", "1")), + _lo_export_ch0 (db, prepend_ch("los/all/export", "0")), + _lo_export_ch1 (db, prepend_ch("los/all/export", "1")), + _ant_mapping (db, "com/ant_mapping"), + _cal_mode (db, "com/cal_mode") + { + bind_accessor(_antenna_ch0); + bind_accessor(_antenna_ch1); + bind_accessor(_enabled_ch0); + bind_accessor(_enabled_ch1); + bind_accessor(_lo_export_ch0); + bind_accessor(_lo_export_ch1); + bind_accessor(_ant_mapping); + bind_accessor(_cal_mode); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<std::string> _antenna_ch0; + experts::data_reader_t<std::string> _antenna_ch1; + experts::data_reader_t<bool> _enabled_ch0; + experts::data_reader_t<bool> _enabled_ch1; + experts::data_reader_t<bool> _lo_export_ch0; + experts::data_reader_t<bool> _lo_export_ch1; + //Outputs + experts::data_writer_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping; + experts::data_writer_t<twinrx_ctrl::cal_mode_t> _cal_mode; +}; + +/*!--------------------------------------------------------- + * twinrx_chan_gain_expert + * + * This expert is responsible for mapping high-level channel + * gain settings to individual attenuator and amp configurations + * that are also channel-scoped. This expert will implement + * the gain distribution strategy. + * + * One instance of this expert is required for each channel + * --------------------------------------------------------- + */ +class twinrx_chan_gain_expert : public experts::worker_node_t { +public: + twinrx_chan_gain_expert(const experts::node_retriever_t& db, std::string ch) + : experts::worker_node_t(prepend_ch("twinrx_chan_gain_expert", ch)), + _gain (db, prepend_ch("gain", ch)), + _gain_profile (db, prepend_ch("gain_profile", ch)), + _signal_path (db, prepend_ch("ch/signal_path", ch)), + _lb_presel (db, prepend_ch("ch/lb_presel", ch)), + _hb_presel (db, prepend_ch("ch/hb_presel", ch)), + _ant_mapping (db, "com/ant_mapping"), + _input_atten (db, prepend_ch("ch/input_atten", ch)), + _lb_atten (db, prepend_ch("ch/lb_atten", ch)), + _hb_atten (db, prepend_ch("ch/hb_atten", ch)), + _preamp1 (db, prepend_ch("ch/preamp1", ch)), + _preamp2 (db, prepend_ch("ch/preamp2", ch)) + { + bind_accessor(_gain); + bind_accessor(_gain_profile); + bind_accessor(_signal_path); + bind_accessor(_lb_presel); + bind_accessor(_hb_presel); + bind_accessor(_ant_mapping); + bind_accessor(_input_atten); + bind_accessor(_lb_atten); + bind_accessor(_hb_atten); + bind_accessor(_preamp1); + bind_accessor(_preamp2); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<double> _gain; + experts::data_reader_t<std::string> _gain_profile; + experts::data_reader_t<twinrx_ctrl::signal_path_t> _signal_path; + experts::data_reader_t<twinrx_ctrl::preselector_path_t> _lb_presel; + experts::data_reader_t<twinrx_ctrl::preselector_path_t> _hb_presel; + experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping; + //Outputs + experts::data_writer_t<boost::uint8_t> _input_atten; + experts::data_writer_t<boost::uint8_t> _lb_atten; + experts::data_writer_t<boost::uint8_t> _hb_atten; + experts::data_writer_t<twinrx_ctrl::preamp_state_t> _preamp1; + experts::data_writer_t<bool> _preamp2; +}; + +/*!--------------------------------------------------------- + * twinrx_ant_gain_expert + * + * This expert is responsible for translating between the + * channel-scoped low-level gain settings to antenna-scoped + * gain settings. + * + * One instance of this expert is required for all channels + * --------------------------------------------------------- + */ +class twinrx_ant_gain_expert : public experts::worker_node_t { +public: + twinrx_ant_gain_expert(const experts::node_retriever_t& db) + : experts::worker_node_t("twinrx_ant_gain_expert"), + _ant_mapping (db, "com/ant_mapping"), + _ch0_input_atten (db, prepend_ch("ch/input_atten", "0")), + _ch0_preamp1 (db, prepend_ch("ch/preamp1", "0")), + _ch0_preamp2 (db, prepend_ch("ch/preamp2", "0")), + _ch0_lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", "0")), + _ch1_input_atten (db, prepend_ch("ch/input_atten", "1")), + _ch1_preamp1 (db, prepend_ch("ch/preamp1", "1")), + _ch1_preamp2 (db, prepend_ch("ch/preamp2", "1")), + _ch1_lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", "1")), + _ant0_input_atten (db, prepend_ch("ant/input_atten", "0")), + _ant0_preamp1 (db, prepend_ch("ant/preamp1", "0")), + _ant0_preamp2 (db, prepend_ch("ant/preamp2", "0")), + _ant0_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "0")), + _ant1_input_atten (db, prepend_ch("ant/input_atten", "1")), + _ant1_preamp1 (db, prepend_ch("ant/preamp1", "1")), + _ant1_preamp2 (db, prepend_ch("ant/preamp2", "1")), + _ant1_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "1")) + { + bind_accessor(_ant_mapping); + bind_accessor(_ch0_input_atten); + bind_accessor(_ch0_preamp1); + bind_accessor(_ch0_preamp2); + bind_accessor(_ch0_lb_preamp_presel); + bind_accessor(_ch1_input_atten); + bind_accessor(_ch1_preamp1); + bind_accessor(_ch1_preamp2); + bind_accessor(_ch1_lb_preamp_presel); + bind_accessor(_ant0_input_atten); + bind_accessor(_ant0_preamp1); + bind_accessor(_ant0_preamp2); + bind_accessor(_ant0_lb_preamp_presel); + bind_accessor(_ant1_input_atten); + bind_accessor(_ant1_preamp1); + bind_accessor(_ant1_preamp2); + bind_accessor(_ant1_lb_preamp_presel); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping; + experts::data_reader_t<boost::uint8_t> _ch0_input_atten; + experts::data_reader_t<twinrx_ctrl::preamp_state_t> _ch0_preamp1; + experts::data_reader_t<bool> _ch0_preamp2; + experts::data_reader_t<bool> _ch0_lb_preamp_presel; + experts::data_reader_t<boost::uint8_t> _ch1_input_atten; + experts::data_reader_t<twinrx_ctrl::preamp_state_t> _ch1_preamp1; + experts::data_reader_t<bool> _ch1_preamp2; + experts::data_reader_t<bool> _ch1_lb_preamp_presel; + + //Outputs + experts::data_writer_t<boost::uint8_t> _ant0_input_atten; + experts::data_writer_t<twinrx_ctrl::preamp_state_t> _ant0_preamp1; + experts::data_writer_t<bool> _ant0_preamp2; + experts::data_writer_t<bool> _ant0_lb_preamp_presel; + experts::data_writer_t<boost::uint8_t> _ant1_input_atten; + experts::data_writer_t<twinrx_ctrl::preamp_state_t> _ant1_preamp1; + experts::data_writer_t<bool> _ant1_preamp2; + experts::data_writer_t<bool> _ant1_lb_preamp_presel; +}; + +/*!--------------------------------------------------------- + * twinrx_settings_expert + * + * This expert is responsible for gathering all low-level + * settings and writing them to hardware. All LO frequency + * settings are cached with a hysteresis. All other settings + * are always written to twinrx_ctrl and rely on register + * level caching. + * + * One instance of this expert is required for all channels + * --------------------------------------------------------- + */ +class twinrx_settings_expert : public experts::worker_node_t { +public: + twinrx_settings_expert(const experts::node_retriever_t& db, twinrx_ctrl::sptr ctrl) + : experts::worker_node_t("twinrx_settings_expert"), _ctrl(ctrl), + _ch0 (db, "0"), + _ch1 (db, "1"), + _lo1_synth0_mapping(db, "0/synth/LO1/mapping"), + _lo1_synth1_mapping(db, "1/synth/LO1/mapping"), + _lo2_synth0_mapping(db, "0/synth/LO2/mapping"), + _lo2_synth1_mapping(db, "1/synth/LO2/mapping"), + _lo1_hopping_enabled(db, "com/synth/LO1/hopping_enabled"), + _lo2_hopping_enabled(db, "com/synth/LO2/hopping_enabled"), + _lo1_export_src (db, "com/LO1/export_source"), + _lo2_export_src (db, "com/LO2/export_source"), + _ant_mapping (db, "com/ant_mapping"), + _cal_mode (db, "com/cal_mode") + { + for (size_t i = 0; i < 2; i++) { + ch_settings& ch = (i==1) ? _ch1 : _ch0; + bind_accessor(ch.chan_enabled); + bind_accessor(ch.preamp1); + bind_accessor(ch.preamp2); + bind_accessor(ch.lb_preamp_presel); + bind_accessor(ch.signal_path); + bind_accessor(ch.lb_presel); + bind_accessor(ch.hb_presel); + bind_accessor(ch.input_atten); + bind_accessor(ch.lb_atten); + bind_accessor(ch.hb_atten); + bind_accessor(ch.lo1_source); + bind_accessor(ch.lo2_source); + bind_accessor(ch.lo1_freq_d); + bind_accessor(ch.lo2_freq_d); + bind_accessor(ch.lo1_freq_c); + bind_accessor(ch.lo2_freq_c); + } + bind_accessor(_lo1_synth0_mapping); + bind_accessor(_lo1_synth1_mapping); + bind_accessor(_lo2_synth0_mapping); + bind_accessor(_lo2_synth1_mapping); + bind_accessor(_lo1_hopping_enabled); + bind_accessor(_lo2_hopping_enabled); + bind_accessor(_lo1_export_src); + bind_accessor(_lo2_export_src); + bind_accessor(_ant_mapping); + bind_accessor(_cal_mode); + } + +private: + virtual void resolve(); + void _resolve_lox_freq( + lo_stage_t lo_stage, + experts::data_reader_t<double>& ch0_freq_d, + experts::data_reader_t<double>& ch1_freq_d, + experts::data_writer_t<double>& ch0_freq_c, + experts::data_writer_t<double>& ch1_freq_c, + twinrx_ctrl::lo_source_t ch0_lo_source, + twinrx_ctrl::lo_source_t ch1_lo_source, + lo_synth_mapping_t synth0_mapping, + lo_synth_mapping_t synth1_mapping, + bool hopping_enabled); + double _set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq); + + class ch_settings { + public: + ch_settings(const experts::node_retriever_t& db, const std::string& ch) : + chan_enabled (db, prepend_ch("enabled", ch)), + preamp1 (db, prepend_ch("ant/preamp1", ch)), + preamp2 (db, prepend_ch("ant/preamp2", ch)), + lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", ch)), + signal_path (db, prepend_ch("ch/signal_path", ch)), + lb_presel (db, prepend_ch("ch/lb_presel", ch)), + hb_presel (db, prepend_ch("ch/hb_presel", ch)), + input_atten (db, prepend_ch("ant/input_atten", ch)), + lb_atten (db, prepend_ch("ch/lb_atten", ch)), + hb_atten (db, prepend_ch("ch/hb_atten", ch)), + lo1_source (db, prepend_ch("ch/LO1/source", ch)), + lo2_source (db, prepend_ch("ch/LO2/source", ch)), + lo1_freq_d (db, prepend_ch("los/LO1/freq/desired", ch)), + lo2_freq_d (db, prepend_ch("los/LO2/freq/desired", ch)), + lo1_freq_c (db, prepend_ch("los/LO1/freq/coerced", ch)), + lo2_freq_c (db, prepend_ch("los/LO2/freq/coerced", ch)) + {} + + //Inputs (channel specific) + experts::data_reader_t<bool> chan_enabled; + experts::data_reader_t<twinrx_ctrl::preamp_state_t> preamp1; + experts::data_reader_t<bool> preamp2; + experts::data_reader_t<bool> lb_preamp_presel; + experts::data_reader_t<twinrx_ctrl::signal_path_t> signal_path; + experts::data_reader_t<twinrx_ctrl::preselector_path_t> lb_presel; + experts::data_reader_t<twinrx_ctrl::preselector_path_t> hb_presel; + experts::data_reader_t<boost::uint8_t> input_atten; + experts::data_reader_t<boost::uint8_t> lb_atten; + experts::data_reader_t<boost::uint8_t> hb_atten; + experts::data_reader_t<twinrx_ctrl::lo_source_t> lo1_source; + experts::data_reader_t<twinrx_ctrl::lo_source_t> lo2_source; + experts::data_reader_t<double> lo1_freq_d; + experts::data_reader_t<double> lo2_freq_d; + + //Output (channel specific) + experts::data_writer_t<double> lo1_freq_c; + experts::data_writer_t<double> lo2_freq_c; + }; + + //External interface + twinrx_ctrl::sptr _ctrl; + + //Inputs (channel agnostic) + ch_settings _ch0; + ch_settings _ch1; + experts::data_reader_t<lo_synth_mapping_t> _lo1_synth0_mapping; + experts::data_reader_t<lo_synth_mapping_t> _lo1_synth1_mapping; + experts::data_reader_t<lo_synth_mapping_t> _lo2_synth0_mapping; + experts::data_reader_t<lo_synth_mapping_t> _lo2_synth1_mapping; + experts::data_reader_t<bool> _lo1_hopping_enabled; + experts::data_reader_t<bool> _lo2_hopping_enabled; + experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src; + experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src; + experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping; + experts::data_reader_t<twinrx_ctrl::cal_mode_t> _cal_mode; + + //Outputs (channel agnostic) + //None + + //Misc + struct lo_freq_cache_t { + rf_freq_ppm_t desired; + double coerced; + }; + lo_freq_cache_t _cached_lo1_synth0_freq; + lo_freq_cache_t _cached_lo2_synth0_freq; + lo_freq_cache_t _cached_lo1_synth1_freq; + lo_freq_cache_t _cached_lo2_synth1_freq; + + static const bool FORCE_COMMIT; +}; + + +}}}} //namespaces + +#endif /* INCLUDED_DBOARD_TWINRX_EXPERTS_HPP */ diff --git a/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp new file mode 100644 index 000000000..5cc8b49f3 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp @@ -0,0 +1,860 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "twinrx_gain_tables.hpp" +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> + +using namespace uhd::usrp::dboard::twinrx; + +static const std::vector<twinrx_gain_config_t> HIGHBAND1_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 5, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 6, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 7, -27.3, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 8, -26.3, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 9, -25.3, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 10, -24.3, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 11, -23.3, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 12, -22.3, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 13, -21.3, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 14, -20.3, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 15, -19.3, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 16, -18.3, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 17, -17.3, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 18, -16.3, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 19, -15.3, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 20, -14.3, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 21, -13.3, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 22, -12.3, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 23, -11.3, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 24, -10.3, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 25, -9.3, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 26, -8.3, 30, 12, false, false ) ) + ( twinrx_gain_config_t( 27, -7.3, 30, 11, false, false ) ) + ( twinrx_gain_config_t( 28, -6.3, 29, 11, false, false ) ) + ( twinrx_gain_config_t( 29, -5.3, 28, 11, false, false ) ) + ( twinrx_gain_config_t( 30, -4.3, 27, 11, false, false ) ) + ( twinrx_gain_config_t( 31, -3.3, 27, 10, false, false ) ) + ( twinrx_gain_config_t( 32, -2.3, 26, 10, false, false ) ) + ( twinrx_gain_config_t( 33, -1.3, 25, 10, false, false ) ) + ( twinrx_gain_config_t( 34, -0.3, 24, 10, false, false ) ) + ( twinrx_gain_config_t( 35, 0.7, 23, 10, false, false ) ) + ( twinrx_gain_config_t( 36, 1.7, 22, 10, false, false ) ) + ( twinrx_gain_config_t( 37, 2.7, 21, 10, false, false ) ) + ( twinrx_gain_config_t( 38, 3.7, 21, 9, false, false ) ) + ( twinrx_gain_config_t( 39, 4.7, 20, 9, false, false ) ) + ( twinrx_gain_config_t( 40, 5.7, 19, 9, false, false ) ) + ( twinrx_gain_config_t( 41, 6.7, 18, 9, false, false ) ) + ( twinrx_gain_config_t( 42, 7.7, 17, 9, false, false ) ) + ( twinrx_gain_config_t( 43, 8.7, 16, 9, false, false ) ) + ( twinrx_gain_config_t( 44, 9.7, 15, 9, false, false ) ) + ( twinrx_gain_config_t( 45, 10.7, 14, 9, false, false ) ) + ( twinrx_gain_config_t( 46, 11.7, 13, 9, false, false ) ) + ( twinrx_gain_config_t( 47, 12.7, 12, 9, false, false ) ) + ( twinrx_gain_config_t( 48, 13.7, 11, 9, false, false ) ) + ( twinrx_gain_config_t( 49, 14.7, 10, 9, false, false ) ) + ( twinrx_gain_config_t( 50, 15.7, 9, 9, false, false ) ) + ( twinrx_gain_config_t( 51, 16.7, 8, 9, false, false ) ) + ( twinrx_gain_config_t( 52, 17.7, 7, 9, false, false ) ) + ( twinrx_gain_config_t( 53, 18.7, 6, 9, false, false ) ) + ( twinrx_gain_config_t( 54, 19.7, 5, 9, false, false ) ) + ( twinrx_gain_config_t( 55, 20.7, 4, 9, false, false ) ) + ( twinrx_gain_config_t( 56, 21.7, 3, 9, false, false ) ) + ( twinrx_gain_config_t( 57, 22.7, 2, 9, false, false ) ) + ( twinrx_gain_config_t( 58, 23.7, 1, 9, false, false ) ) + ( twinrx_gain_config_t( 59, 24.7, 0, 9, false, false ) ) + ( twinrx_gain_config_t( 60, 25.7, 0, 8, false, false ) ) + ( twinrx_gain_config_t( 61, 26.7, 0, 7, false, false ) ) + ( twinrx_gain_config_t( 62, 27.7, 0, 6, false, false ) ) + ( twinrx_gain_config_t( 63, 28.7, 0, 5, false, false ) ) + ( twinrx_gain_config_t( 64, 29.7, 0, 4, false, false ) ) + ( twinrx_gain_config_t( 65, 30.7, 0, 3, false, false ) ) + ( twinrx_gain_config_t( 66, 31.7, 0, 2, false, false ) ) + ( twinrx_gain_config_t( 67, 32.7, 0, 1, false, false ) ) + ( twinrx_gain_config_t( 68, 33.7, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 69, 33.9, 3, 9, true, false ) ) + ( twinrx_gain_config_t( 70, 34.9, 2, 9, true, false ) ) + ( twinrx_gain_config_t( 71, 35.9, 1, 9, true, false ) ) + ( twinrx_gain_config_t( 72, 36.9, 0, 9, true, false ) ) + ( twinrx_gain_config_t( 73, 37.9, 0, 8, true, false ) ) + ( twinrx_gain_config_t( 74, 38.9, 0, 7, true, false ) ) + ( twinrx_gain_config_t( 75, 39.9, 0, 6, true, false ) ) + ( twinrx_gain_config_t( 76, 40.9, 0, 5, true, false ) ) + ( twinrx_gain_config_t( 77, 41.9, 0, 4, true, false ) ) + ( twinrx_gain_config_t( 78, 42.9, 0, 3, true, false ) ) + ( twinrx_gain_config_t( 79, 43.9, 0, 2, true, false ) ) + ( twinrx_gain_config_t( 80, 44.9, 0, 1, true, false ) ) + ( twinrx_gain_config_t( 81, 45.9, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 82, 47.3, 1, 10, true, true ) ) + ( twinrx_gain_config_t( 83, 48.3, 0, 10, true, true ) ) + ( twinrx_gain_config_t( 84, 49.3, 0, 9, true, true ) ) + ( twinrx_gain_config_t( 85, 50.3, 0, 8, true, true ) ) + ( twinrx_gain_config_t( 86, 51.3, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 52.3, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 53.3, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 54.3, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 55.3, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 56.3, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 57.3, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 58.3, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> HIGHBAND2_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 5, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 6, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 7, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 8, -29.9, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 9, -28.9, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 10, -27.9, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 11, -26.9, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 12, -25.9, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 13, -24.9, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 14, -23.9, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 15, -22.9, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 16, -21.9, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 17, -20.9, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 18, -19.9, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 19, -18.9, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 20, -17.9, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 21, -16.9, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 22, -15.9, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 23, -14.9, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 24, -13.9, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 25, -12.9, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 26, -11.9, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 27, -10.9, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 28, -9.9, 30, 11, false, false ) ) + ( twinrx_gain_config_t( 29, -8.9, 29, 11, false, false ) ) + ( twinrx_gain_config_t( 30, -7.9, 29, 10, false, false ) ) + ( twinrx_gain_config_t( 31, -6.9, 28, 10, false, false ) ) + ( twinrx_gain_config_t( 32, -5.9, 27, 10, false, false ) ) + ( twinrx_gain_config_t( 33, -4.9, 27, 9, false, false ) ) + ( twinrx_gain_config_t( 34, -3.9, 26, 9, false, false ) ) + ( twinrx_gain_config_t( 35, -2.9, 25, 9, false, false ) ) + ( twinrx_gain_config_t( 36, -1.9, 24, 9, false, false ) ) + ( twinrx_gain_config_t( 37, -0.9, 23, 9, false, false ) ) + ( twinrx_gain_config_t( 38, 0.1, 23, 8, false, false ) ) + ( twinrx_gain_config_t( 39, 1.1, 22, 8, false, false ) ) + ( twinrx_gain_config_t( 40, 2.1, 21, 8, false, false ) ) + ( twinrx_gain_config_t( 41, 3.1, 20, 8, false, false ) ) + ( twinrx_gain_config_t( 42, 4.1, 19, 8, false, false ) ) + ( twinrx_gain_config_t( 43, 5.1, 18, 8, false, false ) ) + ( twinrx_gain_config_t( 44, 6.1, 17, 8, false, false ) ) + ( twinrx_gain_config_t( 45, 7.1, 16, 8, false, false ) ) + ( twinrx_gain_config_t( 46, 8.1, 15, 8, false, false ) ) + ( twinrx_gain_config_t( 47, 9.1, 14, 8, false, false ) ) + ( twinrx_gain_config_t( 48, 10.1, 13, 8, false, false ) ) + ( twinrx_gain_config_t( 49, 11.1, 12, 8, false, false ) ) + ( twinrx_gain_config_t( 50, 12.1, 11, 8, false, false ) ) + ( twinrx_gain_config_t( 51, 13.1, 10, 8, false, false ) ) + ( twinrx_gain_config_t( 52, 14.1, 9, 8, false, false ) ) + ( twinrx_gain_config_t( 53, 15.1, 8, 8, false, false ) ) + ( twinrx_gain_config_t( 54, 16.1, 7, 8, false, false ) ) + ( twinrx_gain_config_t( 55, 17.1, 6, 8, false, false ) ) + ( twinrx_gain_config_t( 56, 18.1, 5, 8, false, false ) ) + ( twinrx_gain_config_t( 57, 19.1, 4, 8, false, false ) ) + ( twinrx_gain_config_t( 58, 20.1, 3, 8, false, false ) ) + ( twinrx_gain_config_t( 59, 21.1, 2, 8, false, false ) ) + ( twinrx_gain_config_t( 60, 22.1, 1, 8, false, false ) ) + ( twinrx_gain_config_t( 61, 23.1, 0, 8, false, false ) ) + ( twinrx_gain_config_t( 62, 24.1, 0, 7, false, false ) ) + ( twinrx_gain_config_t( 63, 25.1, 0, 6, false, false ) ) + ( twinrx_gain_config_t( 64, 26.1, 0, 5, false, false ) ) + ( twinrx_gain_config_t( 65, 27.1, 0, 4, false, false ) ) + ( twinrx_gain_config_t( 66, 28.1, 0, 3, false, false ) ) + ( twinrx_gain_config_t( 67, 29.1, 0, 2, false, false ) ) + ( twinrx_gain_config_t( 68, 30.1, 0, 1, false, false ) ) + ( twinrx_gain_config_t( 69, 31.9, 0, 10, false, true ) ) + ( twinrx_gain_config_t( 70, 31.9, 0, 10, false, true ) ) + ( twinrx_gain_config_t( 71, 32.9, 0, 9, false, true ) ) + ( twinrx_gain_config_t( 72, 33.9, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 73, 34.9, 0, 7, false, true ) ) + ( twinrx_gain_config_t( 74, 35.9, 0, 6, false, true ) ) + ( twinrx_gain_config_t( 75, 36.9, 0, 5, false, true ) ) + ( twinrx_gain_config_t( 76, 38.6, 0, 6, true, false ) ) + ( twinrx_gain_config_t( 77, 39.6, 0, 5, true, false ) ) + ( twinrx_gain_config_t( 78, 40.6, 0, 4, true, false ) ) + ( twinrx_gain_config_t( 79, 41.6, 0, 3, true, false ) ) + ( twinrx_gain_config_t( 80, 42.6, 0, 2, true, false ) ) + ( twinrx_gain_config_t( 81, 43.6, 0, 1, true, false ) ) + ( twinrx_gain_config_t( 82, 44.4, 2, 9, true, true ) ) + ( twinrx_gain_config_t( 83, 45.4, 1, 9, true, true ) ) + ( twinrx_gain_config_t( 84, 46.4, 0, 9, true, true ) ) + ( twinrx_gain_config_t( 85, 47.4, 0, 8, true, true ) ) + ( twinrx_gain_config_t( 86, 48.4, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 49.4, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 50.4, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 51.4, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 52.4, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 53.4, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 54.4, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 55.4, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> HIGHBAND3_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 5, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 6, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 7, -30.1, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 8, -29.1, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 9, -28.1, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 10, -27.1, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 11, -26.1, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 12, -25.1, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 13, -24.1, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 14, -23.1, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 15, -22.1, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 16, -21.1, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 17, -20.1, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 18, -19.1, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 19, -18.1, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 20, -17.1, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 21, -16.1, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 22, -15.1, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 23, -14.1, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 24, -13.1, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 25, -12.1, 30, 13, false, false ) ) + ( twinrx_gain_config_t( 26, -11.1, 30, 12, false, false ) ) + ( twinrx_gain_config_t( 27, -10.1, 29, 12, false, false ) ) + ( twinrx_gain_config_t( 28, -9.1, 28, 12, false, false ) ) + ( twinrx_gain_config_t( 29, -8.1, 28, 11, false, false ) ) + ( twinrx_gain_config_t( 30, -7.1, 27, 11, false, false ) ) + ( twinrx_gain_config_t( 31, -6.1, 26, 11, false, false ) ) + ( twinrx_gain_config_t( 32, -5.1, 26, 10, false, false ) ) + ( twinrx_gain_config_t( 33, -4.1, 25, 10, false, false ) ) + ( twinrx_gain_config_t( 34, -3.1, 24, 10, false, false ) ) + ( twinrx_gain_config_t( 35, -2.1, 23, 10, false, false ) ) + ( twinrx_gain_config_t( 36, -1.1, 22, 10, false, false ) ) + ( twinrx_gain_config_t( 37, -0.1, 21, 10, false, false ) ) + ( twinrx_gain_config_t( 38, 0.9, 21, 9, false, false ) ) + ( twinrx_gain_config_t( 39, 1.9, 20, 9, false, false ) ) + ( twinrx_gain_config_t( 40, 2.9, 19, 9, false, false ) ) + ( twinrx_gain_config_t( 41, 3.9, 18, 9, false, false ) ) + ( twinrx_gain_config_t( 42, 4.9, 17, 9, false, false ) ) + ( twinrx_gain_config_t( 43, 5.9, 16, 9, false, false ) ) + ( twinrx_gain_config_t( 44, 6.9, 15, 9, false, false ) ) + ( twinrx_gain_config_t( 45, 7.9, 14, 9, false, false ) ) + ( twinrx_gain_config_t( 46, 8.9, 13, 9, false, false ) ) + ( twinrx_gain_config_t( 47, 9.9, 12, 9, false, false ) ) + ( twinrx_gain_config_t( 48, 10.9, 11, 9, false, false ) ) + ( twinrx_gain_config_t( 49, 11.9, 10, 9, false, false ) ) + ( twinrx_gain_config_t( 50, 12.9, 9, 9, false, false ) ) + ( twinrx_gain_config_t( 51, 13.9, 8, 9, false, false ) ) + ( twinrx_gain_config_t( 52, 14.9, 7, 9, false, false ) ) + ( twinrx_gain_config_t( 53, 15.9, 6, 9, false, false ) ) + ( twinrx_gain_config_t( 54, 16.9, 5, 9, false, false ) ) + ( twinrx_gain_config_t( 55, 17.9, 4, 9, false, false ) ) + ( twinrx_gain_config_t( 56, 18.9, 3, 9, false, false ) ) + ( twinrx_gain_config_t( 57, 19.9, 2, 9, false, false ) ) + ( twinrx_gain_config_t( 58, 20.9, 1, 9, false, false ) ) + ( twinrx_gain_config_t( 59, 21.9, 0, 9, false, false ) ) + ( twinrx_gain_config_t( 60, 22.9, 0, 8, false, false ) ) + ( twinrx_gain_config_t( 61, 23.9, 0, 7, false, false ) ) + ( twinrx_gain_config_t( 62, 24.9, 0, 6, false, false ) ) + ( twinrx_gain_config_t( 63, 25.9, 0, 5, false, false ) ) + ( twinrx_gain_config_t( 64, 26.9, 0, 4, false, false ) ) + ( twinrx_gain_config_t( 65, 27.9, 0, 3, false, false ) ) + ( twinrx_gain_config_t( 66, 28.9, 0, 2, false, false ) ) + ( twinrx_gain_config_t( 67, 29.9, 0, 1, false, false ) ) + ( twinrx_gain_config_t( 68, 31.3, 0, 9, false, true ) ) + ( twinrx_gain_config_t( 69, 32.3, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 70, 33.3, 0, 7, false, true ) ) + ( twinrx_gain_config_t( 71, 34.3, 0, 6, false, true ) ) + ( twinrx_gain_config_t( 72, 35.3, 0, 5, false, true ) ) + ( twinrx_gain_config_t( 73, 36.3, 0, 4, false, true ) ) + ( twinrx_gain_config_t( 74, 37.3, 0, 3, false, true ) ) + ( twinrx_gain_config_t( 75, 37.6, 0, 9, true, false ) ) + ( twinrx_gain_config_t( 76, 38.6, 0, 8, true, false ) ) + ( twinrx_gain_config_t( 77, 39.6, 0, 7, true, false ) ) + ( twinrx_gain_config_t( 78, 40.6, 0, 6, true, false ) ) + ( twinrx_gain_config_t( 79, 41.6, 0, 5, true, false ) ) + ( twinrx_gain_config_t( 80, 42.6, 0, 4, true, false ) ) + ( twinrx_gain_config_t( 81, 43.6, 0, 3, true, false ) ) + ( twinrx_gain_config_t( 82, 44.6, 0, 2, true, false ) ) + ( twinrx_gain_config_t( 83, 45.6, 0, 1, true, false ) ) + ( twinrx_gain_config_t( 84, 47.0, 0, 9, true, true ) ) + ( twinrx_gain_config_t( 85, 48.0, 0, 8, true, true ) ) + ( twinrx_gain_config_t( 86, 49.0, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 50.0, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 51.0, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 52.0, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 53.0, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 54.0, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 55.0, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 56.0, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> HIGHBAND4_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 5, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 6, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 7, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 8, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 9, -36.2, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 10, -35.2, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 11, -34.2, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 12, -33.2, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 13, -32.2, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 14, -31.2, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 15, -30.2, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 16, -29.2, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 17, -28.2, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 18, -27.2, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 19, -26.2, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 20, -25.2, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 21, -24.2, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 22, -23.2, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 23, -22.2, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 24, -21.2, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 25, -20.2, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 26, -19.2, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 27, -18.2, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 28, -17.2, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 29, -16.2, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 30, -15.2, 30, 10, false, false ) ) + ( twinrx_gain_config_t( 31, -14.2, 30, 9, false, false ) ) + ( twinrx_gain_config_t( 32, -13.2, 29, 9, false, false ) ) + ( twinrx_gain_config_t( 33, -12.2, 28, 9, false, false ) ) + ( twinrx_gain_config_t( 34, -11.2, 28, 8, false, false ) ) + ( twinrx_gain_config_t( 35, -10.2, 27, 8, false, false ) ) + ( twinrx_gain_config_t( 36, -9.2, 27, 7, false, false ) ) + ( twinrx_gain_config_t( 37, -8.2, 26, 7, false, false ) ) + ( twinrx_gain_config_t( 38, -7.2, 25, 7, false, false ) ) + ( twinrx_gain_config_t( 39, -6.2, 24, 7, false, false ) ) + ( twinrx_gain_config_t( 40, -5.2, 24, 6, false, false ) ) + ( twinrx_gain_config_t( 41, -4.2, 23, 6, false, false ) ) + ( twinrx_gain_config_t( 42, -3.2, 22, 6, false, false ) ) + ( twinrx_gain_config_t( 43, -2.2, 21, 6, false, false ) ) + ( twinrx_gain_config_t( 44, -1.2, 20, 6, false, false ) ) + ( twinrx_gain_config_t( 45, -0.2, 19, 6, false, false ) ) + ( twinrx_gain_config_t( 46, 0.8, 18, 6, false, false ) ) + ( twinrx_gain_config_t( 47, 1.8, 17, 6, false, false ) ) + ( twinrx_gain_config_t( 48, 2.8, 16, 6, false, false ) ) + ( twinrx_gain_config_t( 49, 3.8, 16, 5, false, false ) ) + ( twinrx_gain_config_t( 50, 4.8, 15, 5, false, false ) ) + ( twinrx_gain_config_t( 51, 5.8, 14, 5, false, false ) ) + ( twinrx_gain_config_t( 52, 6.8, 13, 5, false, false ) ) + ( twinrx_gain_config_t( 53, 7.8, 12, 5, false, false ) ) + ( twinrx_gain_config_t( 54, 8.8, 11, 5, false, false ) ) + ( twinrx_gain_config_t( 55, 9.8, 10, 5, false, false ) ) + ( twinrx_gain_config_t( 56, 10.8, 9, 5, false, false ) ) + ( twinrx_gain_config_t( 57, 11.8, 8, 5, false, false ) ) + ( twinrx_gain_config_t( 58, 12.8, 7, 5, false, false ) ) + ( twinrx_gain_config_t( 59, 13.8, 6, 5, false, false ) ) + ( twinrx_gain_config_t( 60, 14.8, 5, 5, false, false ) ) + ( twinrx_gain_config_t( 61, 15.8, 4, 5, false, false ) ) + ( twinrx_gain_config_t( 62, 16.8, 3, 5, false, false ) ) + ( twinrx_gain_config_t( 63, 17.8, 2, 5, false, false ) ) + ( twinrx_gain_config_t( 64, 18.8, 1, 5, false, false ) ) + ( twinrx_gain_config_t( 65, 19.8, 0, 5, false, false ) ) + ( twinrx_gain_config_t( 66, 20.8, 0, 4, false, false ) ) + ( twinrx_gain_config_t( 67, 21.8, 0, 3, false, false ) ) + ( twinrx_gain_config_t( 68, 22.8, 0, 2, false, false ) ) + ( twinrx_gain_config_t( 69, 23.8, 0, 1, false, false ) ) + ( twinrx_gain_config_t( 70, 24.8, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 71, 26.1, 0, 6, false, true ) ) + ( twinrx_gain_config_t( 72, 26.1, 0, 6, false, true ) ) + ( twinrx_gain_config_t( 73, 27.1, 0, 5, false, true ) ) + ( twinrx_gain_config_t( 74, 28.1, 0, 4, false, true ) ) + ( twinrx_gain_config_t( 75, 29.1, 0, 3, false, true ) ) + ( twinrx_gain_config_t( 76, 30.1, 0, 2, false, true ) ) + ( twinrx_gain_config_t( 77, 31.1, 0, 1, false, true ) ) + ( twinrx_gain_config_t( 78, 32.1, 0, 0, false, true ) ) + ( twinrx_gain_config_t( 79, 33.3, 0, 7, true, false ) ) + ( twinrx_gain_config_t( 80, 34.3, 0, 6, true, false ) ) + ( twinrx_gain_config_t( 81, 35.3, 0, 5, true, false ) ) + ( twinrx_gain_config_t( 82, 36.3, 0, 4, true, false ) ) + ( twinrx_gain_config_t( 83, 37.3, 0, 3, true, false ) ) + ( twinrx_gain_config_t( 84, 38.3, 0, 2, true, false ) ) + ( twinrx_gain_config_t( 85, 39.3, 0, 1, true, false ) ) + ( twinrx_gain_config_t( 86, 40.3, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 87, 41.6, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 42.6, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 43.6, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 44.6, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 45.6, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 46.6, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 47.6, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> LOWBAND1_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -30.1, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 2, -29.1, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 3, -28.1, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 4, -27.1, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 5, -26.1, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 6, -25.1, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 7, -24.1, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 8, -23.1, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 9, -22.1, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 10, -21.1, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 11, -20.1, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 12, -19.1, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 13, -18.1, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 14, -17.1, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 15, -16.1, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 16, -15.1, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 17, -14.1, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 18, -13.1, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 19, -12.1, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 20, -11.1, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 21, -10.1, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 22, -9.1, 31, 9, false, false ) ) + ( twinrx_gain_config_t( 23, -8.1, 31, 8, false, false ) ) + ( twinrx_gain_config_t( 24, -7.1, 31, 7, false, false ) ) + ( twinrx_gain_config_t( 25, -6.1, 31, 6, false, false ) ) + ( twinrx_gain_config_t( 26, -5.1, 31, 5, false, false ) ) + ( twinrx_gain_config_t( 27, -4.1, 31, 4, false, false ) ) + ( twinrx_gain_config_t( 28, -3.1, 31, 3, false, false ) ) + ( twinrx_gain_config_t( 29, -2.1, 31, 2, false, false ) ) + ( twinrx_gain_config_t( 30, -1.1, 31, 1, false, false ) ) + ( twinrx_gain_config_t( 31, -0.1, 31, 0, false, false ) ) + ( twinrx_gain_config_t( 32, 0.9, 30, 0, false, false ) ) + ( twinrx_gain_config_t( 33, 1.9, 29, 0, false, false ) ) + ( twinrx_gain_config_t( 34, 2.9, 28, 0, false, false ) ) + ( twinrx_gain_config_t( 35, 3.9, 27, 0, false, false ) ) + ( twinrx_gain_config_t( 36, 4.9, 26, 0, false, false ) ) + ( twinrx_gain_config_t( 37, 5.9, 25, 0, false, false ) ) + ( twinrx_gain_config_t( 38, 6.9, 24, 0, false, false ) ) + ( twinrx_gain_config_t( 39, 7.9, 23, 0, false, false ) ) + ( twinrx_gain_config_t( 40, 8.9, 22, 0, false, false ) ) + ( twinrx_gain_config_t( 41, 9.9, 21, 0, false, false ) ) + ( twinrx_gain_config_t( 42, 10.9, 20, 0, false, false ) ) + ( twinrx_gain_config_t( 43, 11.9, 19, 0, false, false ) ) + ( twinrx_gain_config_t( 44, 12.9, 18, 0, false, false ) ) + ( twinrx_gain_config_t( 45, 13.9, 17, 0, false, false ) ) + ( twinrx_gain_config_t( 46, 14.9, 16, 0, false, false ) ) + ( twinrx_gain_config_t( 47, 15.9, 15, 0, false, false ) ) + ( twinrx_gain_config_t( 48, 16.9, 14, 0, false, false ) ) + ( twinrx_gain_config_t( 49, 17.9, 13, 0, false, false ) ) + ( twinrx_gain_config_t( 50, 18.9, 12, 0, false, false ) ) + ( twinrx_gain_config_t( 51, 19.9, 11, 0, false, false ) ) + ( twinrx_gain_config_t( 52, 20.9, 10, 0, false, false ) ) + ( twinrx_gain_config_t( 53, 21.9, 9, 0, false, false ) ) + ( twinrx_gain_config_t( 54, 22.9, 8, 0, false, false ) ) + ( twinrx_gain_config_t( 55, 23.9, 7, 0, false, false ) ) + ( twinrx_gain_config_t( 56, 24.9, 6, 0, false, false ) ) + ( twinrx_gain_config_t( 57, 25.9, 5, 0, false, false ) ) + ( twinrx_gain_config_t( 58, 26.9, 4, 0, false, false ) ) + ( twinrx_gain_config_t( 59, 27.9, 3, 0, false, false ) ) + ( twinrx_gain_config_t( 60, 28.9, 2, 0, false, false ) ) + ( twinrx_gain_config_t( 61, 29.9, 1, 0, false, false ) ) + ( twinrx_gain_config_t( 62, 30.9, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 63, 31.2, 4, 11, false, true ) ) + ( twinrx_gain_config_t( 64, 32.2, 3, 11, false, true ) ) + ( twinrx_gain_config_t( 65, 33.2, 2, 11, false, true ) ) + ( twinrx_gain_config_t( 66, 34.2, 1, 11, false, true ) ) + ( twinrx_gain_config_t( 67, 35.2, 0, 11, false, true ) ) + ( twinrx_gain_config_t( 68, 36.2, 10, 0, true, false ) ) + ( twinrx_gain_config_t( 69, 37.2, 9, 0, true, false ) ) + ( twinrx_gain_config_t( 70, 38.2, 8, 0, true, false ) ) + ( twinrx_gain_config_t( 71, 39.2, 7, 0, true, false ) ) + ( twinrx_gain_config_t( 72, 40.2, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 73, 41.2, 5, 0, true, false ) ) + ( twinrx_gain_config_t( 74, 42.2, 4, 0, true, false ) ) + ( twinrx_gain_config_t( 75, 43.2, 3, 0, true, false ) ) + ( twinrx_gain_config_t( 76, 44.2, 2, 0, true, false ) ) + ( twinrx_gain_config_t( 77, 45.2, 1, 0, true, false ) ) + ( twinrx_gain_config_t( 78, 46.2, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 79, 47.5, 4, 10, true, true ) ) + ( twinrx_gain_config_t( 80, 48.5, 3, 10, true, true ) ) + ( twinrx_gain_config_t( 81, 49.5, 3, 9, true, true ) ) + ( twinrx_gain_config_t( 82, 50.5, 2, 9, true, true ) ) + ( twinrx_gain_config_t( 83, 51.5, 1, 9, true, true ) ) + ( twinrx_gain_config_t( 84, 52.5, 1, 8, true, true ) ) + ( twinrx_gain_config_t( 85, 53.5, 0, 8, true, true ) ) + ( twinrx_gain_config_t( 86, 54.5, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 55.5, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 56.5, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 57.5, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 58.5, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 59.5, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 60.5, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 61.5, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> LOWBAND2_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -33.4, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -33.4, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -32.4, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 3, -31.4, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 4, -30.4, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 5, -29.4, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 6, -28.4, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 7, -27.4, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 8, -26.4, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 9, -25.4, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 10, -24.4, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 11, -23.4, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 12, -22.4, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 13, -21.4, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 14, -20.4, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 15, -19.4, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 16, -18.4, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 17, -17.4, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 18, -16.4, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 19, -15.4, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 20, -14.4, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 21, -13.4, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 22, -12.4, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 23, -11.4, 31, 9, false, false ) ) + ( twinrx_gain_config_t( 24, -10.4, 31, 8, false, false ) ) + ( twinrx_gain_config_t( 25, -9.4, 31, 7, false, false ) ) + ( twinrx_gain_config_t( 26, -8.4, 31, 6, false, false ) ) + ( twinrx_gain_config_t( 27, -7.4, 31, 5, false, false ) ) + ( twinrx_gain_config_t( 28, -6.4, 31, 4, false, false ) ) + ( twinrx_gain_config_t( 29, -5.4, 31, 3, false, false ) ) + ( twinrx_gain_config_t( 30, -4.4, 31, 2, false, false ) ) + ( twinrx_gain_config_t( 31, -3.4, 31, 1, false, false ) ) + ( twinrx_gain_config_t( 32, -2.4, 31, 0, false, false ) ) + ( twinrx_gain_config_t( 33, -1.4, 30, 0, false, false ) ) + ( twinrx_gain_config_t( 34, -0.4, 29, 0, false, false ) ) + ( twinrx_gain_config_t( 35, 0.6, 28, 0, false, false ) ) + ( twinrx_gain_config_t( 36, 1.6, 27, 0, false, false ) ) + ( twinrx_gain_config_t( 37, 2.6, 26, 0, false, false ) ) + ( twinrx_gain_config_t( 38, 3.6, 25, 0, false, false ) ) + ( twinrx_gain_config_t( 39, 4.6, 24, 0, false, false ) ) + ( twinrx_gain_config_t( 40, 5.6, 23, 0, false, false ) ) + ( twinrx_gain_config_t( 41, 6.6, 22, 0, false, false ) ) + ( twinrx_gain_config_t( 42, 7.6, 21, 0, false, false ) ) + ( twinrx_gain_config_t( 43, 8.6, 20, 0, false, false ) ) + ( twinrx_gain_config_t( 44, 9.6, 19, 0, false, false ) ) + ( twinrx_gain_config_t( 45, 10.6, 18, 0, false, false ) ) + ( twinrx_gain_config_t( 46, 11.6, 17, 0, false, false ) ) + ( twinrx_gain_config_t( 47, 12.6, 16, 0, false, false ) ) + ( twinrx_gain_config_t( 48, 13.6, 15, 0, false, false ) ) + ( twinrx_gain_config_t( 49, 14.6, 14, 0, false, false ) ) + ( twinrx_gain_config_t( 50, 15.6, 13, 0, false, false ) ) + ( twinrx_gain_config_t( 51, 16.6, 12, 0, false, false ) ) + ( twinrx_gain_config_t( 52, 17.6, 11, 0, false, false ) ) + ( twinrx_gain_config_t( 53, 18.6, 10, 0, false, false ) ) + ( twinrx_gain_config_t( 54, 19.6, 9, 0, false, false ) ) + ( twinrx_gain_config_t( 55, 20.6, 8, 0, false, false ) ) + ( twinrx_gain_config_t( 56, 21.6, 7, 0, false, false ) ) + ( twinrx_gain_config_t( 57, 22.6, 6, 0, false, false ) ) + ( twinrx_gain_config_t( 58, 23.6, 5, 0, false, false ) ) + ( twinrx_gain_config_t( 59, 24.6, 4, 0, false, false ) ) + ( twinrx_gain_config_t( 60, 25.6, 3, 0, false, false ) ) + ( twinrx_gain_config_t( 61, 26.6, 2, 0, false, false ) ) + ( twinrx_gain_config_t( 62, 27.6, 1, 0, false, false ) ) + ( twinrx_gain_config_t( 63, 28.6, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 64, 29.7, 5, 9, false, true ) ) + ( twinrx_gain_config_t( 65, 30.7, 4, 9, false, true ) ) + ( twinrx_gain_config_t( 66, 31.7, 3, 9, false, true ) ) + ( twinrx_gain_config_t( 67, 32.7, 2, 9, false, true ) ) + ( twinrx_gain_config_t( 68, 33.7, 1, 9, false, true ) ) + ( twinrx_gain_config_t( 69, 34.7, 0, 9, false, true ) ) + ( twinrx_gain_config_t( 70, 35.7, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 71, 36.7, 7, 0, true, false ) ) + ( twinrx_gain_config_t( 72, 37.7, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 73, 38.7, 5, 0, true, false ) ) + ( twinrx_gain_config_t( 74, 39.7, 4, 0, true, false ) ) + ( twinrx_gain_config_t( 75, 40.7, 3, 0, true, false ) ) + ( twinrx_gain_config_t( 76, 41.7, 2, 0, true, false ) ) + ( twinrx_gain_config_t( 77, 42.7, 1, 0, true, false ) ) + ( twinrx_gain_config_t( 78, 43.7, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 79, 44.8, 6, 8, true, true ) ) + ( twinrx_gain_config_t( 80, 45.8, 5, 8, true, true ) ) + ( twinrx_gain_config_t( 81, 46.8, 4, 8, true, true ) ) + ( twinrx_gain_config_t( 82, 47.8, 4, 7, true, true ) ) + ( twinrx_gain_config_t( 83, 48.8, 3, 7, true, true ) ) + ( twinrx_gain_config_t( 84, 49.8, 2, 7, true, true ) ) + ( twinrx_gain_config_t( 85, 50.8, 1, 7, true, true ) ) + ( twinrx_gain_config_t( 86, 51.8, 1, 6, true, true ) ) + ( twinrx_gain_config_t( 87, 52.8, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 53.8, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 54.8, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 55.8, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 56.8, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 57.8, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 58.8, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> LOWBAND3_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -34.0, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -34.0, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -34.0, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -33.0, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 4, -32.0, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 5, -31.0, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 6, -30.0, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 7, -29.0, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 8, -28.0, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 9, -27.0, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 10, -26.0, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 11, -25.0, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 12, -24.0, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 13, -23.0, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 14, -22.0, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 15, -21.0, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 16, -20.0, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 17, -19.0, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 18, -18.0, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 19, -17.0, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 20, -16.0, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 21, -15.0, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 22, -14.0, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 23, -13.0, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 24, -12.0, 31, 9, false, false ) ) + ( twinrx_gain_config_t( 25, -11.0, 31, 8, false, false ) ) + ( twinrx_gain_config_t( 26, -10.0, 31, 7, false, false ) ) + ( twinrx_gain_config_t( 27, -9.0, 31, 6, false, false ) ) + ( twinrx_gain_config_t( 28, -8.0, 31, 5, false, false ) ) + ( twinrx_gain_config_t( 29, -7.0, 31, 4, false, false ) ) + ( twinrx_gain_config_t( 30, -6.0, 31, 3, false, false ) ) + ( twinrx_gain_config_t( 31, -5.0, 31, 2, false, false ) ) + ( twinrx_gain_config_t( 32, -4.0, 31, 1, false, false ) ) + ( twinrx_gain_config_t( 33, -3.0, 31, 0, false, false ) ) + ( twinrx_gain_config_t( 34, -2.0, 30, 0, false, false ) ) + ( twinrx_gain_config_t( 35, -1.0, 29, 0, false, false ) ) + ( twinrx_gain_config_t( 36, -0.0, 28, 0, false, false ) ) + ( twinrx_gain_config_t( 37, 1.0, 27, 0, false, false ) ) + ( twinrx_gain_config_t( 38, 2.0, 26, 0, false, false ) ) + ( twinrx_gain_config_t( 39, 3.0, 25, 0, false, false ) ) + ( twinrx_gain_config_t( 40, 4.0, 24, 0, false, false ) ) + ( twinrx_gain_config_t( 41, 5.0, 23, 0, false, false ) ) + ( twinrx_gain_config_t( 42, 6.0, 22, 0, false, false ) ) + ( twinrx_gain_config_t( 43, 7.0, 21, 0, false, false ) ) + ( twinrx_gain_config_t( 44, 8.0, 20, 0, false, false ) ) + ( twinrx_gain_config_t( 45, 9.0, 19, 0, false, false ) ) + ( twinrx_gain_config_t( 46, 10.0, 18, 0, false, false ) ) + ( twinrx_gain_config_t( 47, 11.0, 17, 0, false, false ) ) + ( twinrx_gain_config_t( 48, 12.0, 16, 0, false, false ) ) + ( twinrx_gain_config_t( 49, 13.0, 15, 0, false, false ) ) + ( twinrx_gain_config_t( 50, 14.0, 14, 0, false, false ) ) + ( twinrx_gain_config_t( 51, 15.0, 13, 0, false, false ) ) + ( twinrx_gain_config_t( 52, 16.0, 12, 0, false, false ) ) + ( twinrx_gain_config_t( 53, 17.0, 11, 0, false, false ) ) + ( twinrx_gain_config_t( 54, 18.0, 10, 0, false, false ) ) + ( twinrx_gain_config_t( 55, 19.0, 9, 0, false, false ) ) + ( twinrx_gain_config_t( 56, 20.0, 8, 0, false, false ) ) + ( twinrx_gain_config_t( 57, 21.0, 7, 0, false, false ) ) + ( twinrx_gain_config_t( 58, 22.0, 6, 0, false, false ) ) + ( twinrx_gain_config_t( 59, 23.0, 5, 0, false, false ) ) + ( twinrx_gain_config_t( 60, 24.0, 4, 0, false, false ) ) + ( twinrx_gain_config_t( 61, 25.0, 3, 0, false, false ) ) + ( twinrx_gain_config_t( 62, 26.0, 2, 0, false, false ) ) + ( twinrx_gain_config_t( 63, 27.0, 1, 0, false, false ) ) + ( twinrx_gain_config_t( 64, 28.0, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 65, 29.5, 5, 8, false, true ) ) + ( twinrx_gain_config_t( 66, 30.5, 4, 8, false, true ) ) + ( twinrx_gain_config_t( 67, 31.5, 3, 8, false, true ) ) + ( twinrx_gain_config_t( 68, 32.5, 2, 8, false, true ) ) + ( twinrx_gain_config_t( 69, 33.5, 1, 8, false, true ) ) + ( twinrx_gain_config_t( 70, 34.5, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 71, 34.5, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 72, 36.5, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 73, 36.5, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 74, 37.5, 5, 0, true, false ) ) + ( twinrx_gain_config_t( 75, 38.5, 4, 0, true, false ) ) + ( twinrx_gain_config_t( 76, 39.5, 3, 0, true, false ) ) + ( twinrx_gain_config_t( 77, 40.5, 2, 0, true, false ) ) + ( twinrx_gain_config_t( 78, 41.5, 1, 0, true, false ) ) + ( twinrx_gain_config_t( 79, 42.5, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 80, 44.0, 6, 7, true, true ) ) + ( twinrx_gain_config_t( 81, 45.0, 5, 7, true, true ) ) + ( twinrx_gain_config_t( 82, 46.0, 4, 7, true, true ) ) + ( twinrx_gain_config_t( 83, 47.0, 3, 7, true, true ) ) + ( twinrx_gain_config_t( 84, 48.0, 3, 6, true, true ) ) + ( twinrx_gain_config_t( 85, 49.0, 2, 6, true, true ) ) + ( twinrx_gain_config_t( 86, 50.0, 1, 6, true, true ) ) + ( twinrx_gain_config_t( 87, 51.0, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 52.0, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 53.0, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 54.0, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 55.0, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 56.0, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 57.0, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> LOWBAND4_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -32.8, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -32.8, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -32.8, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -32.8, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -31.8, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 5, -30.8, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 6, -29.8, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 7, -28.8, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 8, -27.8, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 9, -26.8, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 10, -25.8, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 11, -24.8, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 12, -23.8, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 13, -22.8, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 14, -21.8, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 15, -20.8, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 16, -19.8, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 17, -18.8, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 18, -17.8, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 19, -16.8, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 20, -15.8, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 21, -14.8, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 22, -13.8, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 23, -12.8, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 24, -11.8, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 25, -10.8, 31, 9, false, false ) ) + ( twinrx_gain_config_t( 26, -9.8, 31, 8, false, false ) ) + ( twinrx_gain_config_t( 27, -8.8, 31, 7, false, false ) ) + ( twinrx_gain_config_t( 28, -7.8, 31, 6, false, false ) ) + ( twinrx_gain_config_t( 29, -6.8, 31, 5, false, false ) ) + ( twinrx_gain_config_t( 30, -5.8, 31, 4, false, false ) ) + ( twinrx_gain_config_t( 31, -4.8, 31, 3, false, false ) ) + ( twinrx_gain_config_t( 32, -3.8, 31, 2, false, false ) ) + ( twinrx_gain_config_t( 33, -2.8, 31, 1, false, false ) ) + ( twinrx_gain_config_t( 34, -1.8, 31, 0, false, false ) ) + ( twinrx_gain_config_t( 35, -0.8, 30, 0, false, false ) ) + ( twinrx_gain_config_t( 36, 0.2, 29, 0, false, false ) ) + ( twinrx_gain_config_t( 37, 1.2, 28, 0, false, false ) ) + ( twinrx_gain_config_t( 38, 2.2, 27, 0, false, false ) ) + ( twinrx_gain_config_t( 39, 3.2, 26, 0, false, false ) ) + ( twinrx_gain_config_t( 40, 4.2, 25, 0, false, false ) ) + ( twinrx_gain_config_t( 41, 5.2, 24, 0, false, false ) ) + ( twinrx_gain_config_t( 42, 6.2, 23, 0, false, false ) ) + ( twinrx_gain_config_t( 43, 7.2, 22, 0, false, false ) ) + ( twinrx_gain_config_t( 44, 8.2, 21, 0, false, false ) ) + ( twinrx_gain_config_t( 45, 9.2, 20, 0, false, false ) ) + ( twinrx_gain_config_t( 46, 10.2, 19, 0, false, false ) ) + ( twinrx_gain_config_t( 47, 11.2, 18, 0, false, false ) ) + ( twinrx_gain_config_t( 48, 12.2, 17, 0, false, false ) ) + ( twinrx_gain_config_t( 49, 13.2, 16, 0, false, false ) ) + ( twinrx_gain_config_t( 50, 14.2, 15, 0, false, false ) ) + ( twinrx_gain_config_t( 51, 15.2, 14, 0, false, false ) ) + ( twinrx_gain_config_t( 52, 16.2, 13, 0, false, false ) ) + ( twinrx_gain_config_t( 53, 17.2, 12, 0, false, false ) ) + ( twinrx_gain_config_t( 54, 18.2, 11, 0, false, false ) ) + ( twinrx_gain_config_t( 55, 19.2, 10, 0, false, false ) ) + ( twinrx_gain_config_t( 56, 20.2, 9, 0, false, false ) ) + ( twinrx_gain_config_t( 57, 21.2, 8, 0, false, false ) ) + ( twinrx_gain_config_t( 58, 22.2, 7, 0, false, false ) ) + ( twinrx_gain_config_t( 59, 23.2, 6, 0, false, false ) ) + ( twinrx_gain_config_t( 60, 24.2, 5, 0, false, false ) ) + ( twinrx_gain_config_t( 61, 25.2, 4, 0, false, false ) ) + ( twinrx_gain_config_t( 62, 26.2, 3, 0, false, false ) ) + ( twinrx_gain_config_t( 63, 27.2, 2, 0, false, false ) ) + ( twinrx_gain_config_t( 64, 28.2, 1, 0, false, false ) ) + ( twinrx_gain_config_t( 65, 29.2, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 66, 30.4, 4, 9, false, true ) ) + ( twinrx_gain_config_t( 67, 31.4, 3, 9, false, true ) ) + ( twinrx_gain_config_t( 68, 32.4, 2, 9, false, true ) ) + ( twinrx_gain_config_t( 69, 33.4, 1, 9, false, true ) ) + ( twinrx_gain_config_t( 70, 34.4, 0, 9, false, true ) ) + ( twinrx_gain_config_t( 71, 35.4, 8, 0, true, false ) ) + ( twinrx_gain_config_t( 72, 36.4, 7, 0, true, false ) ) + ( twinrx_gain_config_t( 73, 37.4, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 74, 38.4, 5, 0, true, false ) ) + ( twinrx_gain_config_t( 75, 39.4, 4, 0, true, false ) ) + ( twinrx_gain_config_t( 76, 40.4, 3, 0, true, false ) ) + ( twinrx_gain_config_t( 77, 41.4, 2, 0, true, false ) ) + ( twinrx_gain_config_t( 78, 42.4, 1, 0, true, false ) ) + ( twinrx_gain_config_t( 79, 43.4, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 80, 44.6, 4, 9, true, true ) ) + ( twinrx_gain_config_t( 81, 45.6, 4, 8, true, true ) ) + ( twinrx_gain_config_t( 82, 46.6, 3, 8, true, true ) ) + ( twinrx_gain_config_t( 83, 47.6, 2, 8, true, true ) ) + ( twinrx_gain_config_t( 84, 48.6, 1, 8, true, true ) ) + ( twinrx_gain_config_t( 85, 49.6, 1, 7, true, true ) ) + ( twinrx_gain_config_t( 86, 50.6, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 51.6, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 52.6, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 53.6, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 54.6, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 55.6, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 56.6, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 57.6, 0, 0, true, true ) ) +; + +const twinrx_gain_table twinrx_gain_table::lookup_table +( + twinrx_ctrl::signal_path_t signal_path, + twinrx_ctrl::preselector_path_t preselector_path, + std::string +) { + + if (signal_path == twinrx_ctrl::PATH_HIGHBAND) { + switch (preselector_path) { + case twinrx_ctrl::PRESEL_PATH1: + return twinrx_gain_table(HIGHBAND1_TABLE); + case twinrx_ctrl::PRESEL_PATH2: + return twinrx_gain_table(HIGHBAND2_TABLE); + case twinrx_ctrl::PRESEL_PATH3: + return twinrx_gain_table(HIGHBAND3_TABLE); + case twinrx_ctrl::PRESEL_PATH4: + return twinrx_gain_table(HIGHBAND4_TABLE); + } + } else { + switch (preselector_path) { + case twinrx_ctrl::PRESEL_PATH1: + return twinrx_gain_table(LOWBAND1_TABLE); + case twinrx_ctrl::PRESEL_PATH2: + return twinrx_gain_table(LOWBAND2_TABLE); + case twinrx_ctrl::PRESEL_PATH3: + return twinrx_gain_table(LOWBAND3_TABLE); + case twinrx_ctrl::PRESEL_PATH4: + return twinrx_gain_table(LOWBAND4_TABLE); + } + } + throw runtime_error("NO GAIN TABLE SELECTED"); + return twinrx_gain_table(HIGHBAND1_TABLE); +} + +const twinrx_gain_config_t& twinrx_gain_table::find_by_index(size_t index) const { + if (index >= get_num_entries()) throw uhd::value_error("invalid gain table index"); + return _tbl.at(index); +} + +uhd::gain_range_t twinrx_gain_table::get_gain_range() const { + double max = std::numeric_limits<double>::min(); + double min = std::numeric_limits<double>::max(); + for (size_t i = 0; i < get_num_entries(); i++) { + const twinrx_gain_config_t& config = find_by_index(i); + if (config.sys_gain > max) { + max = config.sys_gain; + } + if (config.sys_gain < min) { + min = config.sys_gain; + } + } + return uhd::gain_range_t(min, max, 1.0); +} diff --git a/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp new file mode 100644 index 000000000..0148965da --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp @@ -0,0 +1,83 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP +#define INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <uhd/types/ranges.hpp> +#include "twinrx_ctrl.hpp" + +namespace uhd { namespace usrp { namespace dboard { namespace twinrx { + +class twinrx_gain_config_t { +public: + twinrx_gain_config_t( + size_t index_, double sys_gain_, + boost::uint8_t atten1_, boost::uint8_t atten2_, + bool amp1_, bool amp2_ + ): index(index_), sys_gain(sys_gain_), atten1(atten1_), atten2(atten2_), + amp1(amp1_), amp2(amp2_) + {} + + twinrx_gain_config_t& operator=(const twinrx_gain_config_t& src) { + if (this != &src) { + this->index = src.index; + this->sys_gain = src.sys_gain; + this->atten1 = src.atten1; + this->atten2 = src.atten2; + this->amp1 = src.amp1; + this->amp2 = src.amp2; + } + return *this; + } + + size_t index; + double sys_gain; + boost::uint8_t atten1; + boost::uint8_t atten2; + bool amp1; + bool amp2; +}; + +class twinrx_gain_table { +public: + static const twinrx_gain_table lookup_table( + twinrx_ctrl::signal_path_t signal_path, + twinrx_ctrl::preselector_path_t presel_path, + std::string profile); + + twinrx_gain_table(const std::vector<twinrx_gain_config_t>& tbl) + : _tbl(tbl) {} + + const twinrx_gain_config_t& find_by_index(size_t index) const; + + inline size_t get_num_entries() const { + return _tbl.size(); + } + + uhd::gain_range_t get_gain_range() const; + +private: + const std::vector<twinrx_gain_config_t>& _tbl; +}; + + +}}}} //namespaces + +#endif /* INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP */ diff --git a/host/lib/usrp/dboard/twinrx/twinrx_io.hpp b/host/lib/usrp/dboard/twinrx/twinrx_io.hpp new file mode 100644 index 000000000..5d099e361 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_io.hpp @@ -0,0 +1,523 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_DBOARD_TWINRX_IO_HPP +#define INCLUDED_DBOARD_TWINRX_IO_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/utils/soft_register.hpp> +#include <boost/thread.hpp> +#include "gpio_atr_3000.hpp" + +namespace uhd { namespace usrp { namespace dboard { namespace twinrx { + +static const boost::uint32_t SET_ALL_BITS = 0xFFFFFFFF; + +namespace cpld { +static wb_iface::wb_addr_type addr(boost::uint8_t cpld_num, boost::uint8_t cpld_addr) { + //Decode CPLD addressing for the following bitmap: + // {CPLD1_EN, CPLD2_EN, CPLD3_EN, CPLD4_EN, CPLD_ADDR[2:0]} + boost::uint8_t addr = 0; + switch (cpld_num) { + case 1: addr = 0x8 << 3; break; + case 2: addr = 0x4 << 3; break; + case 3: addr = 0x2 << 3; break; + case 4: addr = 0x1 << 3; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + return static_cast<wb_iface::wb_addr_type>(addr | (cpld_addr & 0x7)); +} + +static boost::uint32_t get_reg(wb_iface::wb_addr_type addr) { + return static_cast<boost::uint32_t>(addr) & 0x7; +} +} + +class twinrx_gpio : public timed_wb_iface { +public: + typedef boost::shared_ptr<twinrx_gpio> sptr; + + //---------------------------------------------- + //Public GPIO fields + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_CE_CH1, /*width*/ 1, /*shift*/ 0); //GPIO[0] OUT + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_CE_CH2, /*width*/ 1, /*shift*/ 1); //GPIO[1] OUT + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_MUXOUT_CH1, /*width*/ 1, /*shift*/ 2); //GPIO[2] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_MUXOUT_CH2, /*width*/ 1, /*shift*/ 3); //GPIO[3] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_LD_CH1, /*width*/ 1, /*shift*/ 4); //GPIO[4] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_LD_CH2, /*width*/ 1, /*shift*/ 5); //GPIO[5] IN + // NO CONNECT //GPIO[8:6] + // PRIVATE //GPIO[15:9] + // NO CONNECT //GPIO[16] + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_CE_CH1, /*width*/ 1, /*shift*/ 17); //GPIO[17] OUT + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_CE_CH2, /*width*/ 1, /*shift*/ 18); //GPIO[18] OUT + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_MUXOUT_CH1, /*width*/ 1, /*shift*/ 19); //GPIO[19] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_MUXOUT_CH2, /*width*/ 1, /*shift*/ 20); //GPIO[20] IN + // NO CONNECT //GPIO[21:23] + UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_CLK, /*width*/ 1, /*shift*/ 24); //GPIO[24] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_PWR_GOOD, /*width*/ 1, /*shift*/ 25); //GPIO[25] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_EN, /*width*/ 1, /*shift*/ 26); //GPIO[26] OUT + // PRIVATE //GPIO[27:31] + //---------------------------------------------- + + twinrx_gpio(dboard_iface::sptr iface) : _db_iface(iface) { + _db_iface->set_gpio_ddr(dboard_iface::UNIT_BOTH, GPIO_OUTPUT_MASK, SET_ALL_BITS); + _db_iface->set_pin_ctrl(dboard_iface::UNIT_BOTH, GPIO_PINCTRL_MASK, SET_ALL_BITS); + _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, 0, ~GPIO_PINCTRL_MASK); + } + + ~twinrx_gpio() { + _db_iface->set_gpio_ddr(dboard_iface::UNIT_BOTH, ~GPIO_OUTPUT_MASK, SET_ALL_BITS); + } + + void set_field(const uhd::soft_reg_field_t field, const boost::uint32_t value) { + boost::lock_guard<boost::mutex> lock(_mutex); + using namespace soft_reg_field; + + _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, + (value << shift(field)), + mask<boost::uint32_t>(field)); + } + + boost::uint32_t get_field(const uhd::soft_reg_field_t field) { + boost::lock_guard<boost::mutex> lock(_mutex); + using namespace soft_reg_field; + return (_db_iface->read_gpio(dboard_iface::UNIT_BOTH) & mask<boost::uint32_t>(field)) >> shift(field); + } + + // CPLD register write-only interface + void poke32(const wb_addr_type addr, const boost::uint32_t data) { + boost::lock_guard<boost::mutex> lock(_mutex); + using namespace soft_reg_field; + + //Step 1: Write the reg offset and data to the GPIO bus and de-assert all enables + _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, + (cpld::get_reg(addr) << shift(CPLD_FULL_ADDR)) | (data << shift(CPLD_DATA)), + mask<boost::uint32_t>(CPLD_FULL_ADDR)|mask<boost::uint32_t>(CPLD_DATA)); + //Sleep for 166ns to ensure that we don't toggle the enables too quickly + //The underlying sleep function rounds to microsecond precision. + _db_iface->sleep(boost::chrono::nanoseconds(166)); + //Step 2: Write the reg offset and data, and assert the necessary enable + _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, + (static_cast<boost::uint32_t>(addr) << shift(CPLD_FULL_ADDR)) | (data << shift(CPLD_DATA)), + mask<boost::uint32_t>(CPLD_FULL_ADDR)|mask<boost::uint32_t>(CPLD_DATA)); + } + + // Timed command interface + inline time_spec_t get_time() { + return _db_iface->get_command_time(); + } + + void set_time(const time_spec_t& t) { + boost::lock_guard<boost::mutex> lock(_mutex); + _db_iface->set_command_time(t); + } + +private: //Members/definitions + static const boost::uint32_t GPIO_OUTPUT_MASK = 0xFC06FE03; + static const boost::uint32_t GPIO_PINCTRL_MASK = 0x00000000; + + //Private GPIO fields + UHD_DEFINE_SOFT_REG_FIELD(CPLD_FULL_ADDR, /*width*/ 7, /*shift*/ 9); //GPIO[15:9] + UHD_DEFINE_SOFT_REG_FIELD(CPLD_DATA, /*width*/ 5, /*shift*/ 27); //GPIO[31:27] + + //Members + dboard_iface::sptr _db_iface; + boost::mutex _mutex; +}; + +class twinrx_cpld_regmap : public uhd::soft_regmap_t { +public: + typedef boost::shared_ptr<twinrx_cpld_regmap> sptr; + + //---------------------------------------------- + // IF CCA: CPLD 1 + //---------------------------------------------- + class if0_reg0_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_IF1_EN_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LO2_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LO2_EN_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW19_CTRL_CH2, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(SW20_CTRL_CH2, /*width*/ 1, /*shift*/ 4); + + if0_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 0), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg0; + + class if0_reg1_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW20_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + + if0_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 1), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg1; + + class if0_reg2_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_IF1_EN_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_IF1_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(LO2_LE_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(LO2_LE_CH2, /*width*/ 1, /*shift*/ 4); + + if0_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 2), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg2; + + class if0_reg3_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW24_CTRL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW13_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(IF1_IF2_EN_CH1, /*width*/ 1, /*shift*/ 3); + + if0_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 3), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg3; + + class if0_reg4_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW21_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW25_CTRL, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(IF1_IF2_EN_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW19_CTRL_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(SW21_CTRL_CH1, /*width*/ 1, /*shift*/ 4); + + if0_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 4), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg4; + + class if0_reg6_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_IF1_EN_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW13_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + + if0_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 6), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg6; + + class if0_reg7_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW24_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + + if0_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 7), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg7; + + //---------------------------------------------- + // RF CCA: CPLD 2 + //---------------------------------------------- + class rf0_reg0_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_IN_CH1, /*width*/ 5, /*shift*/ 0); + + rf0_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 0), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg0; + + class rf0_reg1_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SWPA1_CTL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(HB_PREAMP_EN_CH1, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(LB_PREAMP_EN_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(SWPA3_CTRL_CH2, /*width*/ 1, /*shift*/ 4); + + rf0_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 1), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg1; + + class rf0_reg2_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW6_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW5_CTRL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW4_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(LO1_LE_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(LO1_LE_CH2, /*width*/ 1, /*shift*/ 4); + + rf0_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 2), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg2; + + class rf0_reg3_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW9_CTRL_CH2, /*width*/ 2, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW7_CTRL_CH1, /*width*/ 2, /*shift*/ 2); + + rf0_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 3), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg3; + + class rf0_reg4_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_IN_CH2, /*width*/ 5, /*shift*/ 0); + + rf0_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 4), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg4; + + class rf0_reg5_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW9_CTRL_CH1, /*width*/ 2, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(HB_PREAMP_EN_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW3_CTRL_CH1, /*width*/ 1, /*shift*/ 4); + + rf0_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 5), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg5; + + class rf0_reg6_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW6_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW5_CTRL_CH2, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW4_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SWPA4_CTRL_CH2, /*width*/ 1, /*shift*/ 4); + + rf0_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 6), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg6; + + class rf0_reg7_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SWPA1_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SWPA3_CTRL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW3_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW7_CTRL_CH2, /*width*/ 2, /*shift*/ 3); + + rf0_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 7), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg7; + + //---------------------------------------------- + // RF CCA: CPLD 3 + //---------------------------------------------- + class rf1_reg0_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_HB_CH1, /*width*/ 5, /*shift*/ 0); + + rf1_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 0), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg0; + + class rf1_reg1_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW17_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LO1_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW16_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW15_CTRL_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(SW14_CTRL_CH1, /*width*/ 1, /*shift*/ 4); + + rf1_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 1), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg1; + + class rf1_reg2_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW12_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(HB_PRESEL_PGA_EN_CH2, /*width*/ 1, /*shift*/ 2); + + rf1_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 2), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg2; + + class rf1_reg3_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW23_CTRL, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW22_CTRL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW10_CTRL_CH1, /*width*/ 2, /*shift*/ 2); + + rf1_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 3), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg3; + + class rf1_reg4_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_HB_CH2, /*width*/ 5, /*shift*/ 0); + + rf1_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 4), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg4; + + class rf1_reg5_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(AMP_LO1_EN_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW15_CTRL_CH2, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW14_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW18_CTRL_CH1, /*width*/ 1, /*shift*/ 4); + + rf1_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 5), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg5; + + class rf1_reg6_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(HB_PRESEL_PGA_EN_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW17_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW16_CTRL_CH2, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(PREAMP2_EN_CH2, /*width*/ 1, /*shift*/ 4); + + rf1_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 6), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg6; + + class rf1_reg7_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW22_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW10_CTRL_CH2, /*width*/ 2, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW12_CTRL_CH2, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_EN_CH2, /*width*/ 1, /*shift*/ 4); + + rf1_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 7), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg7; + + //---------------------------------------------- + // RF CCA: CPLD 4 + //---------------------------------------------- + class rf2_reg0_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_LB_CH1, /*width*/ 5, /*shift*/ 0); + + rf2_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 0), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg0; + + class rf2_reg2_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW11_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SWPA2_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + + rf2_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 2), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg2; + + class rf2_reg3_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(PREAMP2_EN_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW18_CTRL_CH2, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW8_CTRL_CH1, /*width*/ 2, /*shift*/ 2); + + rf2_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 3), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg3; + + class rf2_reg4_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_LB_CH2, /*width*/ 5, /*shift*/ 0); + + rf2_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 4), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg4; + + class rf2_reg5_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SWPA2_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + + rf2_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 5), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg5; + + class rf2_reg6_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(LB_PREAMP_EN_CH2, /*width*/ 1, /*shift*/ 0); + + rf2_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 6), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg6; + + class rf2_reg7_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SWPA4_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW8_CTRL_CH2, /*width*/ 2, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW11_CTRL_CH2, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_EN_CH2, /*width*/ 1, /*shift*/ 4); + + rf2_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 7), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg7; + + twinrx_cpld_regmap() : soft_regmap_t("twinrx_cpld") { + // IF CCA: CPLD 1 + add_to_map(if0_reg0, "if0_reg0"); + add_to_map(if0_reg1, "if0_reg1"); + add_to_map(if0_reg2, "if0_reg2"); + add_to_map(if0_reg3, "if0_reg3"); + add_to_map(if0_reg4, "if0_reg4"); + add_to_map(if0_reg6, "if0_reg6"); + add_to_map(if0_reg7, "if0_reg7"); + // RF CCA: CPLD 2 + add_to_map(rf0_reg0, "rf0_reg0"); + add_to_map(rf0_reg1, "rf0_reg1"); + add_to_map(rf0_reg2, "rf0_reg2"); + add_to_map(rf0_reg3, "rf0_reg3"); + add_to_map(rf0_reg4, "rf0_reg4"); + add_to_map(rf0_reg5, "rf0_reg5"); + add_to_map(rf0_reg6, "rf0_reg6"); + add_to_map(rf0_reg7, "rf0_reg7"); + // RF CCA: CPLD 3 + add_to_map(rf1_reg0, "rf1_reg0"); + add_to_map(rf1_reg1, "rf1_reg1"); + add_to_map(rf1_reg2, "rf1_reg2"); + add_to_map(rf1_reg3, "rf1_reg3"); + add_to_map(rf1_reg4, "rf1_reg4"); + add_to_map(rf1_reg5, "rf1_reg5"); + add_to_map(rf1_reg6, "rf1_reg6"); + add_to_map(rf1_reg7, "rf1_reg7"); + // RF CCA: CPLD 4 + add_to_map(rf2_reg0, "rf2_reg0"); + add_to_map(rf2_reg2, "rf2_reg2"); + add_to_map(rf2_reg3, "rf2_reg3"); + add_to_map(rf2_reg4, "rf2_reg4"); + add_to_map(rf2_reg5, "rf2_reg5"); + add_to_map(rf2_reg6, "rf2_reg6"); + add_to_map(rf2_reg7, "rf2_reg7"); + } +}; + +}}}} //namespaces + +#endif /* INCLUDED_DBOARD_TWINRX_IO_HPP */ diff --git a/host/lib/usrp/dboard_base.cpp b/host/lib/usrp/dboard_base.cpp index fe14c02b9..465b9e489 100644 --- a/host/lib/usrp/dboard_base.cpp +++ b/host/lib/usrp/dboard_base.cpp @@ -32,7 +32,7 @@ struct dboard_base::impl{ dboard_base::dboard_base(ctor_args_t args){ _impl = UHD_PIMPL_MAKE(impl, ()); - _impl->args = *static_cast<dboard_ctor_args_t *>(args); + _impl->args = dboard_ctor_args_t::cast(args); } std::string dboard_base::get_subdev_name(void){ diff --git a/host/lib/usrp/dboard_ctor_args.hpp b/host/lib/usrp/dboard_ctor_args.hpp index 99c071ff8..c8e4006d1 100644 --- a/host/lib/usrp/dboard_ctor_args.hpp +++ b/host/lib/usrp/dboard_ctor_args.hpp @@ -26,11 +26,17 @@ namespace uhd{ namespace usrp{ - struct dboard_ctor_args_t{ + class dboard_ctor_args_t { + public: std::string sd_name; dboard_iface::sptr db_iface; dboard_id_t rx_id, tx_id; property_tree::sptr rx_subtree, tx_subtree; + dboard_base::sptr rx_container, tx_container; + + static const dboard_ctor_args_t& cast(dboard_base::ctor_args_t args) { + return *static_cast<dboard_ctor_args_t*>(args); + } }; }} //namespace diff --git a/host/lib/usrp/dboard_iface.cpp b/host/lib/usrp/dboard_iface.cpp index 092e005f0..2a04095e8 100644 --- a/host/lib/usrp/dboard_iface.cpp +++ b/host/lib/usrp/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013,2015 Ettus Research LLC +// Copyright 2016 Ettus Research // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,78 +16,16 @@ // #include <uhd/usrp/dboard_iface.hpp> -#include <uhd/types/dict.hpp> using namespace uhd::usrp; -struct dboard_iface::impl{ - uhd::dict<unit_t, boost::uint16_t> pin_ctrl_shadow; - uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > atr_reg_shadow; - uhd::dict<unit_t, boost::uint16_t> gpio_ddr_shadow; - uhd::dict<unit_t, boost::uint16_t> gpio_out_shadow; -}; - -dboard_iface::dboard_iface(void){ - _impl = UHD_PIMPL_MAKE(impl, ()); -} - -dboard_iface::~dboard_iface(void) -{ - //empty -} - -template <typename T> -static T shadow_it(T &shadow, const T &value, const T &mask){ - shadow = (shadow & ~mask) | (value & mask); - return shadow; -} - -void dboard_iface::set_pin_ctrl( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_pin_ctrl(unit, shadow_it(_impl->pin_ctrl_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_pin_ctrl(unit_t unit){ - return _impl->pin_ctrl_shadow[unit]; -} - -void dboard_iface::set_atr_reg( - unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask -){ - _set_atr_reg(unit, reg, shadow_it(_impl->atr_reg_shadow[unit][reg], value, mask)); -} - -boost::uint16_t dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ - return _impl->atr_reg_shadow[unit][reg]; -} - -void dboard_iface::set_gpio_ddr( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_gpio_ddr(unit, shadow_it(_impl->gpio_ddr_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_gpio_ddr(unit_t unit){ - return _impl->gpio_ddr_shadow[unit]; -} - -void dboard_iface::set_gpio_out( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_gpio_out(unit, shadow_it(_impl->gpio_out_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_gpio_out(unit_t unit){ - return _impl->gpio_out_shadow[unit]; -} - -void dboard_iface::set_command_time(const uhd::time_spec_t&) -{ - throw uhd::not_implemented_error("timed command feature not implemented on this hardware"); -} - -uhd::time_spec_t dboard_iface::get_command_time() +void dboard_iface::sleep(const boost::chrono::nanoseconds& time) { - return uhd::time_spec_t(0.0); + //nanosleep is not really accurate in userland and it is also not very + //cross-platform. So just sleep for the minimum amount of time in us. + if (time < boost::chrono::microseconds(1)) { + boost::this_thread::sleep_for(boost::chrono::microseconds(1)); + } else { + boost::this_thread::sleep_for(time); + } } diff --git a/host/lib/usrp/dboard_manager.cpp b/host/lib/usrp/dboard_manager.cpp index 340c1d3f9..56cd08fd7 100644 --- a/host/lib/usrp/dboard_manager.cpp +++ b/host/lib/usrp/dboard_manager.cpp @@ -37,11 +37,11 @@ using namespace uhd::usrp; **********************************************************************/ class dboard_key_t{ public: - dboard_key_t(const dboard_id_t &id = dboard_id_t::none()): - _rx_id(id), _tx_id(id), _xcvr(false){} + dboard_key_t(const dboard_id_t &id = dboard_id_t::none(), bool restricted = false): + _rx_id(id), _tx_id(id), _xcvr(false), _restricted(restricted) {} - dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id): - _rx_id(rx_id), _tx_id(tx_id), _xcvr(true){} + dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id, bool restricted = false): + _rx_id(rx_id), _tx_id(tx_id), _xcvr(true), _restricted(restricted) {} dboard_id_t xx_id(void) const{ UHD_ASSERT_THROW(not this->is_xcvr()); @@ -62,9 +62,14 @@ public: return this->_xcvr; } + bool is_restricted(void) const{ + return this->_restricted; + } + private: dboard_id_t _rx_id, _tx_id; bool _xcvr; + bool _restricted; }; bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){ @@ -78,8 +83,8 @@ bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){ /*********************************************************************** * storage and registering for dboards **********************************************************************/ -//dboard registry tuple: dboard constructor, canonical name, subdev names -typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string> > args_t; +//dboard registry tuple: dboard constructor, canonical name, subdev names, container constructor +typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string>, dboard_manager::dboard_ctor_t> args_t; //map a dboard id to a dboard constructor typedef uhd::dict<dboard_key_t, args_t> id_to_args_map_t; @@ -87,9 +92,10 @@ UHD_SINGLETON_FCN(id_to_args_map_t, get_id_to_args_map) static void register_dboard_key( const dboard_key_t &dboard_key, - dboard_manager::dboard_ctor_t dboard_ctor, + dboard_manager::dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_manager::dboard_ctor_t db_container_ctor ){ UHD_LOGV(always) << "registering: " << name << std::endl; if (get_id_to_args_map().has_key(dboard_key)){ @@ -103,26 +109,49 @@ static void register_dboard_key( ) % dboard_key.xx_id().to_string() % get_id_to_args_map()[dboard_key].get<1>())); } - get_id_to_args_map()[dboard_key] = args_t(dboard_ctor, name, subdev_names); + get_id_to_args_map()[dboard_key] = args_t(db_subdev_ctor, name, subdev_names, db_container_ctor); } void dboard_manager::register_dboard( const dboard_id_t &dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor ){ - register_dboard_key(dboard_key_t(dboard_id), dboard_ctor, name, subdev_names); + register_dboard_key(dboard_key_t(dboard_id), db_subdev_ctor, name, subdev_names, db_container_ctor); } void dboard_manager::register_dboard( const dboard_id_t &rx_dboard_id, const dboard_id_t &tx_dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor +){ + register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), db_subdev_ctor, name, subdev_names, db_container_ctor); +} + +void dboard_manager::register_dboard_restricted( + const dboard_id_t &dboard_id, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor ){ - register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), dboard_ctor, name, subdev_names); + register_dboard_key(dboard_key_t(dboard_id, true), db_subdev_ctor, name, subdev_names, db_container_ctor); +} + +void dboard_manager::register_dboard_restricted( + const dboard_id_t &rx_dboard_id, + const dboard_id_t &tx_dboard_id, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor +){ + register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id, true), db_subdev_ctor, name, subdev_names, db_container_ctor); } std::string dboard_id_t::to_cname(void) const{ @@ -153,17 +182,32 @@ public: dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, dboard_iface::sptr iface, - property_tree::sptr subtree + property_tree::sptr subtree, + bool defer_db_init ); - ~dboard_manager_impl(void); + virtual ~dboard_manager_impl(void); + + inline const std::vector<std::string>& get_rx_frontends() const { + return _rx_frontends; + } + + inline const std::vector<std::string>& get_tx_frontends() const { + return _tx_frontends; + } + + void initialize_dboards(); private: - void init(dboard_id_t, dboard_id_t, property_tree::sptr); + void init(dboard_id_t, dboard_id_t, property_tree::sptr, bool); //list of rx and tx dboards in this dboard_manager //each dboard here is actually a subdevice proxy //the subdevice proxy is internal to the cpp file uhd::dict<std::string, dboard_base::sptr> _rx_dboards; uhd::dict<std::string, dboard_base::sptr> _tx_dboards; + std::vector<dboard_base::sptr> _rx_containers; + std::vector<dboard_base::sptr> _tx_containers; + std::vector<std::string> _rx_frontends; + std::vector<std::string> _tx_frontends; dboard_iface::sptr _iface; void set_nice_dboard_if(void); }; @@ -176,13 +220,14 @@ dboard_manager::sptr dboard_manager::make( dboard_id_t tx_dboard_id, dboard_id_t gdboard_id, dboard_iface::sptr iface, - property_tree::sptr subtree + property_tree::sptr subtree, + bool defer_db_init ){ return dboard_manager::sptr( new dboard_manager_impl( rx_dboard_id, (gdboard_id == dboard_id_t::none())? tx_dboard_id : gdboard_id, - iface, subtree + iface, subtree, defer_db_init ) ); } @@ -194,12 +239,13 @@ dboard_manager_impl::dboard_manager_impl( dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, dboard_iface::sptr iface, - property_tree::sptr subtree + property_tree::sptr subtree, + bool defer_db_init ): _iface(iface) { try{ - this->init(rx_dboard_id, tx_dboard_id, subtree); + this->init(rx_dboard_id, tx_dboard_id, subtree, defer_db_init); } catch(const std::exception &e){ UHD_MSG(error) << boost::format( @@ -210,12 +256,13 @@ dboard_manager_impl::dboard_manager_impl( //clean up the stuff added by the call above if (subtree->exists("rx_frontends")) subtree->remove("rx_frontends"); if (subtree->exists("tx_frontends")) subtree->remove("tx_frontends"); - this->init(dboard_id_t::none(), dboard_id_t::none(), subtree); + if (subtree->exists("iface")) subtree->remove("iface"); + this->init(dboard_id_t::none(), dboard_id_t::none(), subtree, false); } } void dboard_manager_impl::init( - dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, property_tree::sptr subtree + dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, property_tree::sptr subtree, bool defer_db_init ){ //find the dboard key matches for the dboard ids dboard_key_t rx_dboard_key, tx_dboard_key, xcvr_dboard_key; @@ -244,6 +291,11 @@ void dboard_manager_impl::init( //initialize the gpio pins before creating subdevs set_nice_dboard_if(); + //conditionally register the dboard iface in the tree + if (not (rx_dboard_key.is_restricted() or tx_dboard_key.is_restricted() or xcvr_dboard_key.is_restricted())) { + subtree->create<dboard_iface::sptr>("iface").set(_iface); + } + //dboard constructor args dboard_ctor_args_t db_ctor_args; db_ctor_args.db_iface = _iface; @@ -252,42 +304,89 @@ void dboard_manager_impl::init( if (xcvr_dboard_key.is_xcvr()){ //extract data for the xcvr dboard key - dboard_ctor_t dboard_ctor; std::string name; std::vector<std::string> subdevs; - boost::tie(dboard_ctor, name, subdevs) = get_id_to_args_map()[xcvr_dboard_key]; + dboard_ctor_t subdev_ctor; std::string name; std::vector<std::string> subdevs; dboard_ctor_t container_ctor; + boost::tie(subdev_ctor, name, subdevs, container_ctor) = get_id_to_args_map()[xcvr_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = rx_dboard_id; + db_ctor_args.tx_id = tx_dboard_id; + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + if (container_ctor) { + db_ctor_args.rx_container = container_ctor(&db_ctor_args); + } else { + db_ctor_args.rx_container = dboard_base::sptr(); + } + db_ctor_args.tx_container = db_ctor_args.rx_container; //Same TX and RX container //create the xcvr object for each subdevice BOOST_FOREACH(const std::string &subdev, subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = rx_dboard_id; - db_ctor_args.tx_id = tx_dboard_id; - db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev); - db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev); - dboard_base::sptr xcvr_dboard = dboard_ctor(&db_ctor_args); + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + dboard_base::sptr xcvr_dboard = subdev_ctor(&db_ctor_args); _rx_dboards[subdev] = xcvr_dboard; _tx_dboards[subdev] = xcvr_dboard; + xcvr_dboard->initialize(); } + + //initialize the container after all subdevs have been created + if (container_ctor) { + if (defer_db_init) { + _rx_containers.push_back(db_ctor_args.rx_container); + } else { + db_ctor_args.rx_container->initialize(); + } + } + + //Populate frontend names in-order. + //We cannot use _xx_dboards.keys() here because of the ordering requirement + _rx_frontends = subdevs; + _tx_frontends = subdevs; } //make tx and rx subdevs (separate subdevs for rx and tx dboards) - else{ - + else + { //force the rx key to the unknown board for bad combinations if (rx_dboard_key.is_xcvr() or rx_dboard_key.xx_id() == dboard_id_t::none()){ rx_dboard_key = dboard_key_t(0xfff1); } //extract data for the rx dboard key - dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs; - boost::tie(rx_dboard_ctor, rx_name, rx_subdevs) = get_id_to_args_map()[rx_dboard_key]; + dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs; dboard_ctor_t rx_cont_ctor; + boost::tie(rx_dboard_ctor, rx_name, rx_subdevs, rx_cont_ctor) = get_id_to_args_map()[rx_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = rx_dboard_id; + db_ctor_args.tx_id = dboard_id_t::none(); + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = property_tree::sptr(); + if (rx_cont_ctor) { + db_ctor_args.rx_container = rx_cont_ctor(&db_ctor_args); + } else { + db_ctor_args.rx_container = dboard_base::sptr(); + } //make the rx subdevs BOOST_FOREACH(const std::string &subdev, rx_subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = rx_dboard_id; - db_ctor_args.tx_id = dboard_id_t::none(); - db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev); - db_ctor_args.tx_subtree = property_tree::sptr(); //null + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); _rx_dboards[subdev] = rx_dboard_ctor(&db_ctor_args); + _rx_dboards[subdev]->initialize(); + } + + //initialize the container after all subdevs have been created + if (rx_cont_ctor) { + if (defer_db_init) { + _rx_containers.push_back(db_ctor_args.rx_container); + } else { + db_ctor_args.rx_container->initialize(); + } } //force the tx key to the unknown board for bad combinations @@ -296,18 +395,53 @@ void dboard_manager_impl::init( } //extract data for the tx dboard key - dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs; - boost::tie(tx_dboard_ctor, tx_name, tx_subdevs) = get_id_to_args_map()[tx_dboard_key]; + dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs; dboard_ctor_t tx_cont_ctor; + boost::tie(tx_dboard_ctor, tx_name, tx_subdevs, tx_cont_ctor) = get_id_to_args_map()[tx_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = dboard_id_t::none(); + db_ctor_args.tx_id = tx_dboard_id; + db_ctor_args.rx_subtree = property_tree::sptr(); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + if (tx_cont_ctor) { + db_ctor_args.tx_container = tx_cont_ctor(&db_ctor_args); + } else { + db_ctor_args.tx_container = dboard_base::sptr(); + } //make the tx subdevs BOOST_FOREACH(const std::string &subdev, tx_subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = dboard_id_t::none(); - db_ctor_args.tx_id = tx_dboard_id; - db_ctor_args.rx_subtree = property_tree::sptr(); //null - db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); _tx_dboards[subdev] = tx_dboard_ctor(&db_ctor_args); + _tx_dboards[subdev]->initialize(); + } + + //initialize the container after all subdevs have been created + if (tx_cont_ctor) { + if (defer_db_init) { + _tx_containers.push_back(db_ctor_args.tx_container); + } else { + db_ctor_args.tx_container->initialize(); + } } + + //Populate frontend names in-order. + //We cannot use _xx_dboards.keys() here because of the ordering requirement + _rx_frontends = rx_subdevs; + _tx_frontends = tx_subdevs; + } +} + +void dboard_manager_impl::initialize_dboards(void) { + BOOST_FOREACH(dboard_base::sptr& _rx_container, _rx_containers) { + _rx_container->initialize(); + } + + BOOST_FOREACH(dboard_base::sptr& _tx_container, _tx_containers) { + _tx_container->initialize(); } } diff --git a/host/lib/usrp/device3/CMakeLists.txt b/host/lib/usrp/device3/CMakeLists.txt new file mode 100644 index 000000000..83f01a2e7 --- /dev/null +++ b/host/lib/usrp/device3/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright 2014 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/device3_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/device3_io_impl.cpp +) diff --git a/host/lib/usrp/device3/device3_impl.cpp b/host/lib/usrp/device3/device3_impl.cpp new file mode 100644 index 000000000..7fcbc01b2 --- /dev/null +++ b/host/lib/usrp/device3/device3_impl.cpp @@ -0,0 +1,188 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "device3_impl.hpp" +#include "graph_impl.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> + +#define UHD_DEVICE3_LOG() UHD_LOGV(never) + +using namespace uhd::usrp; + +device3_impl::device3_impl() + : _sid_framer(0) +{ + _type = uhd::device::USRP; + _async_md.reset(new async_md_type(1000/*messages deep*/)); + _tree = uhd::property_tree::make(); +}; + +//! Returns true if the integer value stored in lhs is smaller than that in rhs +bool _compare_string_indexes(const std::string &lhs, const std::string &rhs) +{ + return boost::lexical_cast<size_t>(lhs) < boost::lexical_cast<size_t>(rhs); +} + +void device3_impl::merge_channel_defs( + const std::vector<uhd::rfnoc::block_id_t> &chan_ids, + const std::vector<uhd::device_addr_t> &chan_args, + const uhd::direction_t dir +) { + UHD_ASSERT_THROW(chan_ids.size() == chan_args.size()); + if (dir == uhd::DX_DIRECTION) { + merge_channel_defs(chan_ids, chan_args, RX_DIRECTION); + merge_channel_defs(chan_ids, chan_args, TX_DIRECTION); + return; + } + + uhd::fs_path chans_root = uhd::fs_path("/channels/") / (dir == RX_DIRECTION ? "rx" : "tx"); + // Store the new positions of the channels: + std::vector<size_t> chan_idxs; + + // 1. Get sorted list of currently defined channels + std::vector<std::string> curr_channels; + if (_tree->exists(chans_root)) { + curr_channels = _tree->list(chans_root); + std::sort(curr_channels.begin(), curr_channels.end(), _compare_string_indexes); + } + + // 2. Cycle through existing channels to find out where to merge + // the new channels. Rules are: + // - The order of chan_ids must be preserved + // - All block indices that are in chan_ids may be overwritten in the channel definition + // - If the channels in chan_ids are not yet in the property tree channel list, + // they are appended. + BOOST_FOREACH(const std::string &chan_idx, curr_channels) { + if (_tree->exists(chans_root / chan_idx)) { + rfnoc::block_id_t chan_block_id = _tree->access<rfnoc::block_id_t>(chans_root / chan_idx).get(); + if (std::find(chan_ids.begin(), chan_ids.end(), chan_block_id) != chan_ids.end()) { + chan_idxs.push_back(boost::lexical_cast<size_t>(chan_idx)); + } + } + } + size_t last_chan_idx = curr_channels.empty() ? 0 : (boost::lexical_cast<size_t>(curr_channels.back()) + 1); + while (chan_idxs.size() < chan_ids.size()) { + chan_idxs.push_back(last_chan_idx); + last_chan_idx++; + } + + // 3. Write the new channels + for (size_t i = 0; i < chan_ids.size(); i++) { + if (not _tree->exists(chans_root / chan_idxs[i])) { + _tree->create<rfnoc::block_id_t>(chans_root / chan_idxs[i]); + } + _tree->access<rfnoc::block_id_t>(chans_root / chan_idxs[i]).set(chan_ids[i]); + if (not _tree->exists(chans_root / chan_idxs[i] / "args")) { + _tree->create<uhd::device_addr_t>(chans_root / chan_idxs[i] / "args"); + } + _tree->access<uhd::device_addr_t>(chans_root / chan_idxs[i] / "args").set(chan_args[i]); + } +} + +/*********************************************************************** + * RFNoC-Specific + **********************************************************************/ +void device3_impl::enumerate_rfnoc_blocks( + size_t device_index, + size_t n_blocks, + size_t base_port, + const uhd::sid_t &base_sid, + uhd::device_addr_t transport_args, + uhd::endianness_t endianness +) { + // entries that are already connected to this block + uhd::sid_t ctrl_sid = base_sid; + uhd::property_tree::sptr subtree = _tree->subtree(uhd::fs_path("/mboards") / device_index); + // 1) Clean property tree entries + // TODO put this back once radios are actual rfnoc blocks!!!!!! + //if (subtree->exists("xbar")) { + //subtree->remove("xbar"); + //} + // 2) Destroy existing block controllers + // TODO: Clear out all the old block control classes + // 3) Create new block controllers + for (size_t i = 0; i < n_blocks; i++) { + UHD_DEVICE3_LOG() << "[RFNOC] ------- Block Setup -----------" << std::endl; + // First, make a transport for port number zero, because we always need that: + ctrl_sid.set_dst_xbarport(base_port + i); + ctrl_sid.set_dst_blockport(0); + both_xports_t xport = this->make_transport( + ctrl_sid, + CTRL, + transport_args + ); + UHD_DEVICE3_LOG() << str(boost::format("Setting up NoC-Shell Control for port #0 (SID: %s)...") % xport.send_sid.to_pp_string_hex()); + uhd::rfnoc::ctrl_iface::sptr ctrl = uhd::rfnoc::ctrl_iface::make( + endianness == ENDIANNESS_BIG, + xport.send, + xport.recv, + xport.send_sid, + str(boost::format("CE_%02d_Port_%02X") % i % ctrl_sid.get_dst_endpoint()) + ); + UHD_DEVICE3_LOG() << "OK" << std::endl; + uint64_t noc_id = ctrl->peek64(uhd::rfnoc::SR_READBACK_REG_ID); + UHD_DEVICE3_LOG() << str(boost::format("Port %d: Found NoC-Block with ID %016X.") % int(ctrl_sid.get_dst_endpoint()) % noc_id) << std::endl; + uhd::rfnoc::make_args_t make_args; + uhd::rfnoc::blockdef::sptr block_def = uhd::rfnoc::blockdef::make_from_noc_id(noc_id); + if (not block_def) { + UHD_DEVICE3_LOG() << "Using default block configuration." << std::endl; + block_def = uhd::rfnoc::blockdef::make_from_noc_id(uhd::rfnoc::DEFAULT_NOC_ID); + } + UHD_ASSERT_THROW(block_def); + make_args.ctrl_ifaces[0] = ctrl; + BOOST_FOREACH(const size_t port_number, block_def->get_all_port_numbers()) { + if (port_number == 0) { // We've already set this up + continue; + } + ctrl_sid.set_dst_blockport(port_number); + both_xports_t xport1 = this->make_transport( + ctrl_sid, + CTRL, + transport_args + ); + UHD_DEVICE3_LOG() << str(boost::format("Setting up NoC-Shell Control for port #%d (SID: %s)...") % port_number % xport1.send_sid.to_pp_string_hex()); + uhd::rfnoc::ctrl_iface::sptr ctrl1 = uhd::rfnoc::ctrl_iface::make( + endianness == ENDIANNESS_BIG, + xport1.send, + xport1.recv, + xport1.send_sid, + str(boost::format("CE_%02d_Port_%02d") % i % ctrl_sid.get_dst_endpoint()) + ); + UHD_DEVICE3_LOG() << "OK" << std::endl; + make_args.ctrl_ifaces[port_number] = ctrl1; + } + + make_args.base_address = xport.send_sid.get_dst(); + make_args.device_index = device_index; + make_args.tree = subtree; + make_args.is_big_endian = (endianness == ENDIANNESS_BIG); + _rfnoc_block_ctrl.push_back(uhd::rfnoc::block_ctrl_base::make(make_args, noc_id)); + } +} + + +uhd::rfnoc::graph::sptr device3_impl::create_graph(const std::string &name) +{ + return boost::make_shared<uhd::rfnoc::graph_impl>( + name, + shared_from_this() + ); +} + diff --git a/host/lib/usrp/device3/device3_impl.hpp b/host/lib/usrp/device3/device3_impl.hpp new file mode 100644 index 000000000..0d94ae21c --- /dev/null +++ b/host/lib/usrp/device3/device3_impl.hpp @@ -0,0 +1,210 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Declares the device3_impl class which is a layer between device3 and +// the different 3-rd gen device impls (e.g. x300_impl) + +#ifndef INCLUDED_DEVICE3_IMPL_HPP +#define INCLUDED_DEVICE3_IMPL_HPP + +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/endianness.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/device3.hpp> +#include "xports.hpp" +// Common FPGA cores: +#include "ctrl_iface.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "time_core_3000.hpp" +#include "gpio_atr_3000.hpp" +// RFNoC-specific includes: +#include "radio_ctrl_impl.hpp" + +namespace uhd { namespace usrp { + +/*********************************************************************** + * Default settings (any device3 may override these) + **********************************************************************/ +static const size_t DEVICE3_RX_FC_REQUEST_FREQ = 32; //per flow-control window +static const size_t DEVICE3_TX_FC_RESPONSE_FREQ = 8; +static const size_t DEVICE3_TX_FC_RESPONSE_CYCLES = 0; // Cycles: Off. + +static const size_t DEVICE3_TX_MAX_HDR_LEN = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(boost::uint64_t); // Bytes +static const size_t DEVICE3_RX_MAX_HDR_LEN = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(boost::uint64_t); // Bytes + +class device3_impl : public uhd::device3, public boost::enable_shared_from_this<device3_impl> +{ +public: + /*********************************************************************** + * device3-specific Types + **********************************************************************/ + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + + //! The purpose of a transport + enum xport_type_t { + CTRL = 0, + TX_DATA, + RX_DATA + }; + + enum xport_t {AXI, ETH, PCIE}; + + //! Stores all streaming-related options + struct stream_options_t + { + //! Max size of the header in bytes for TX + size_t tx_max_len_hdr; + //! Max size of the header in bytes for RX + size_t rx_max_len_hdr; + //! How often we send ACKs to the upstream block per one full FC window + size_t rx_fc_request_freq; + //! How often the downstream block should send ACKs per one full FC window + size_t tx_fc_response_freq; + //! How often the downstream block should send ACKs in cycles + size_t tx_fc_response_cycles; + stream_options_t(void) + : tx_max_len_hdr(DEVICE3_TX_MAX_HDR_LEN) + , rx_max_len_hdr(DEVICE3_RX_MAX_HDR_LEN) + , rx_fc_request_freq(DEVICE3_RX_FC_REQUEST_FREQ) + , tx_fc_response_freq(DEVICE3_TX_FC_RESPONSE_FREQ) + , tx_fc_response_cycles(DEVICE3_TX_FC_RESPONSE_CYCLES) + {}; + }; + + /*********************************************************************** + * I/O Interface + **********************************************************************/ + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); + bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout); + + /*********************************************************************** + * Other public APIs + **********************************************************************/ + rfnoc::graph::sptr create_graph(const std::string &name=""); + +protected: + /*********************************************************************** + * Structors + **********************************************************************/ + device3_impl(); + virtual ~device3_impl() {}; + + /*********************************************************************** + * Streaming-related + **********************************************************************/ + // The 'rate' argument is so we can use these as subscribers to rate changes +public: // TODO make these protected again + void update_rx_streamers(double rate=-1.0); + void update_tx_streamers(double rate=-1.0); +protected: + + /*********************************************************************** + * Transport-related + **********************************************************************/ + stream_options_t stream_options; + + /*! \brief Create a transport to a given endpoint. + * + * \param address The endpoint address of the block we're creating a transport to. + * The source address in this value is not considered, only the + * destination address. + * \param xport_type Specify which kind of transport this is. + * \param args Additional arguments for the transport generation. See \ref page_transport + * for valid arguments. + */ + virtual uhd::both_xports_t make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args + ) = 0; + + virtual uhd::device_addr_t get_tx_hints(size_t) { return uhd::device_addr_t(); }; + virtual uhd::device_addr_t get_rx_hints(size_t) { return uhd::device_addr_t(); }; + virtual uhd::endianness_t get_transport_endianness(size_t mb_index) = 0; + + //! Is called after a streamer is generated + virtual void post_streamer_hooks(uhd::direction_t) {}; + + /*********************************************************************** + * Channel-related + **********************************************************************/ + /*! Merge a list of channels into the existing channel definition. + * + * Intelligently merge the channels described in \p chan_ids + * into the current channel definition. If none of the channels in + * \p chan_ids is in the current definition, they simply get appended. + * Otherwise, they get overwritten in the order of \p chan_ids. + * + * \param chan_ids List of block IDs for the channels. + * \param chan_args New channel args. Must have same length as chan_ids. + * + */ + void merge_channel_defs( + const std::vector<rfnoc::block_id_t> &chan_ids, + const std::vector<uhd::device_addr_t> &chan_args, + const uhd::direction_t dir + ); + + /*********************************************************************** + * RFNoC-Specific + **********************************************************************/ + void enumerate_rfnoc_blocks( + size_t device_index, + size_t n_blocks, + size_t base_port, + const uhd::sid_t &base_sid, + uhd::device_addr_t transport_args, + uhd::endianness_t endianness + ); + + /*********************************************************************** + * Members + **********************************************************************/ + //! A counter, designed to create unique SIDs + size_t _sid_framer; + + // TODO: Maybe move these to private + uhd::dict<std::string, boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; + uhd::dict<std::string, boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; + +private: + /*********************************************************************** + * Private Members + **********************************************************************/ + //! Buffer for async metadata + boost::shared_ptr<async_md_type> _async_md; + + //! This mutex locks the get_xx_stream() functions. + boost::mutex _transport_setup_mutex; +}; + +}} /* namespace uhd::usrp */ + +#endif /* INCLUDED_DEVICE3_IMPL_HPP */ +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/device3/device3_io_impl.cpp b/host/lib/usrp/device3/device3_io_impl.cpp new file mode 100644 index 000000000..8c61f8f15 --- /dev/null +++ b/host/lib/usrp/device3/device3_io_impl.cpp @@ -0,0 +1,851 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Provides streaming-related functions which are used by device3 objects. + +#define DEVICE3_STREAMER // For the super_*_packet_handlers + +#include "device3_impl.hpp" +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include "../common/async_packet_handler.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "../../rfnoc/rx_stream_terminator.hpp" +#include "../../rfnoc/tx_stream_terminator.hpp" +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/radio_ctrl.hpp> + +#define UHD_STREAMER_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +//! CVITA uses 12-Bit sequence numbers +static const boost::uint32_t HW_SEQ_NUM_MASK = 0xfff; + + +/*********************************************************************** + * Helper functions for get_?x_stream() + **********************************************************************/ +static uhd::stream_args_t sanitize_stream_args(const uhd::stream_args_t &args_) +{ + uhd::stream_args_t args = args_; + if (args.channels.empty()) { + args.channels = std::vector<size_t>(1, 0); + } + + return args; +} + +static void check_stream_sig_compatible(const rfnoc::stream_sig_t &stream_sig, stream_args_t &args, const std::string &tx_rx) +{ + if (args.otw_format.empty()) { + if (stream_sig.item_type.empty()) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] No otw_format defined!") % tx_rx + )); + } else { + args.otw_format = stream_sig.item_type; + } + } else if (not stream_sig.item_type.empty() and stream_sig.item_type != args.otw_format) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] Conflicting OTW types defined: args.otw_format = '%s' <=> stream_sig.item_type = '%s'") + % tx_rx % args.otw_format % stream_sig.item_type + )); + } + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + if (stream_sig.packet_size) { + if (args.args.has_key("spp")) { + size_t args_spp = args.args.cast<size_t>("spp", 0); + if (args_spp * bpi != stream_sig.packet_size) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] Conflicting packet sizes defined: args yields %d bytes but stream_sig.packet_size is %d bytes") + % tx_rx % (args_spp * bpi) % stream_sig.packet_size + )); + } + } else { + args.args["spp"] = str(boost::format("%d") % (stream_sig.packet_size / bpi)); + } + } +} + +/*! \brief Returns a list of rx or tx channels for a streamer. + * + * If the given stream args contain instructions to set up channels, + * those are used. Otherwise, the current device's channel definition + * is consulted. + * + * \param args_ Stream args. + * \param[out] chan_list The list of channels in the correct order. + * \param[out] chan_args Channel args for every channel. `chan_args.size() == chan_list.size()` + */ +void generate_channel_list( + const uhd::stream_args_t &args_, + std::vector<uhd::rfnoc::block_id_t> &chan_list, + std::vector<device_addr_t> &chan_args +) { + uhd::stream_args_t args = args_; + BOOST_FOREACH(const size_t chan_idx, args.channels) { + //// Find block ID for this channel: + if (args.args.has_key(str(boost::format("block_id%d") % chan_idx))) { + chan_list.push_back( + uhd::rfnoc::block_id_t( + args.args.pop(str(boost::format("block_id%d") % chan_idx)) + ) + ); + chan_args.push_back(args.args); + } else if (args.args.has_key("block_id")) { + chan_list.push_back(args.args.get("block_id")); + chan_args.push_back(args.args); + chan_args.back().pop("block_id"); + } else { + throw uhd::runtime_error(str( + boost::format("Cannot create streamers: No block_id specified for channel %d.") + % chan_idx + )); + } + //// Find block port for this channel + if (args.args.has_key(str(boost::format("block_port%d") % chan_idx))) { + chan_args.back()["block_port"] = args.args.pop(str(boost::format("block_port%d") % chan_idx)); + } else if (args.args.has_key("block_port")) { + // We have to write it again, because the chan args from the + // property tree might have overwritten this + chan_args.back()["block_port"] = args.args.get("block_port"); + } + } +} + + +/*********************************************************************** + * RX Flow Control Functions + **********************************************************************/ +//! Stores the state of RX flow control +struct rx_fc_cache_t +{ + rx_fc_cache_t(): + last_seq_in(0){} + size_t last_seq_in; +}; + +/*! Determine the size of the flow control window in number of packets. + * + * This value depends on three things: + * - The packet size (in bytes), P + * - The size of the software buffer (in bytes), B + * - The desired buffer fullness, F + * + * The FC window size is thus X = floor(B*F/P). + * + * \param pkt_size The maximum packet size in bytes + * \param sw_buff_size Software buffer size in bytes + * \param rx_args If this has a key 'recv_buff_fullness', this value will + * be used for said fullness. Must be between 0.01 and 1. + * + * \returns The size of the flow control window in number of packets + */ +static size_t get_rx_flow_control_window( + size_t pkt_size, + size_t sw_buff_size, + const device_addr_t& rx_args +) { + double fullness_factor = rx_args.cast<double>( + "recv_buff_fullness", + uhd::rfnoc::DEFAULT_FC_RX_SW_BUFF_FULL_FACTOR + ); + + if (fullness_factor < 0.01 || fullness_factor > 1) { + throw uhd::value_error("recv_buff_fullness must be in [0.01, 1] inclusive (1% to 100%)"); + } + + size_t window_in_pkts = (static_cast<size_t>(sw_buff_size * fullness_factor) / pkt_size); + if (rx_args.has_key("max_recv_window")) { + window_in_pkts = std::min( + window_in_pkts, + rx_args.cast<size_t>("max_recv_window", window_in_pkts) + ); + } + if (window_in_pkts == 0) { + throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); + } + UHD_ASSERT_THROW(size_t(sw_buff_size * fullness_factor) >= pkt_size * window_in_pkts); + return window_in_pkts; +} + + +/*! Send out RX flow control packets. + * + * For an rx stream, this function takes care of sending back + * a flow control packet to the source telling it which + * packets have been consumed. + * + * This function should only be called by the function handling + * the rx stream, usually recv() in super_recv_packet_handler. + * + * \param sid The SID that goes into this packet. This is the reversed() + * version of the data stream's SID. + * \param xport A transport object over which to send the data + * \param big_endian Endianness of the transport + * \param seq32_state Pointer to a variable that saves the 32-Bit state + * of the sequence numbers, since we only have 12 Bit + * sequence numbers in CHDR. + * \param last_seq The value to send: The last consumed packet's sequence number. + */ +static void handle_rx_flowctrl( + const sid_t &sid, + zero_copy_if::sptr xport, + endianness_t endianness, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq +) { + static const size_t RXFC_PACKET_LEN_IN_WORDS = 2; + static const size_t RXFC_CMD_CODE_OFFSET = 0; + static const size_t RXFC_SEQ_NUM_OFFSET = 1; + + managed_send_buffer::sptr buff = xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + // Recover sequence number. The sequence numbers handled by the streamers + // are 12 Bits, but we want to know the 32-Bit sequence number. + size_t &seq32 = fc_cache->last_seq_in; + const size_t seq12 = seq32 & HW_SEQ_NUM_MASK; + if (last_seq < seq12) + seq32 += (HW_SEQ_NUM_MASK + 1); + seq32 &= ~HW_SEQ_NUM_MASK; + seq32 |= last_seq; + + // Super-verbose mode: + //static size_t fc_pkt_count = 0; + //UHD_MSG(status) << "sending flow ctrl packet " << fc_pkt_count++ << ", acking " << str(boost::format("%04d\tseq_sw==0x%08x") % last_seq % seq32) << std::endl; + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_FC; + packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = seq32; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = sid.get(); + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + if (endianness == ENDIANNESS_BIG) { + // Load Header: + vrt::chdr::if_hdr_pack_be(pkt, packet_info); + // Load Payload: (the sequence number) + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq32); + } else { + // Load Header: + vrt::chdr::if_hdr_pack_le(pkt, packet_info); + // Load Payload: (the sequence number) + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htowx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htowx<boost::uint32_t>(seq32); + } + + //std::cout << " SID=" << std::hex << sid << " hdr bits=" << packet_info.packet_type << " seq32=" << seq32 << std::endl; + //std::cout << "num_packet_words32: " << packet_info.num_packet_words32 << std::endl; + //for (size_t i = 0; i < packet_info.num_packet_words32; i++) { + //std::cout << str(boost::format("0x%08x") % pkt[i]) << " "; + //if (i % 2) { + //std::cout << std::endl; + //} + //} + + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + +/*********************************************************************** + * TX Flow Control Functions + **********************************************************************/ +//! Stores the state of TX flow control +struct tx_fc_cache_t +{ + tx_fc_cache_t(void): + stream_channel(0), + device_channel(0), + last_seq_out(0), + last_seq_ack(0), + seq_queue(1){} + size_t stream_channel; + size_t device_channel; + size_t last_seq_out; + size_t last_seq_ack; + uhd::transport::bounded_buffer<size_t> seq_queue; + boost::shared_ptr<device3_impl::async_md_type> async_queue; + boost::shared_ptr<device3_impl::async_md_type> old_async_queue; +}; + +/*! Return the size of the flow control window in packets. + * + * If the return value of this function is F, the last tx'd packet + * has index N and the last ack'd packet has index M, the amount of + * FC credit we have is C = F + M - N (i.e. we can send C more packets + * before getting another ack). + * + * Note: If `send_buff_size` is set in \p tx_hints, this will + * override hw_buff_size_. + */ +static size_t get_tx_flow_control_window( + size_t pkt_size, + const double hw_buff_size_, + const device_addr_t& tx_hints +) { + double hw_buff_size = tx_hints.cast<double>("send_buff_size", hw_buff_size_); + size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / pkt_size); + if (window_in_pkts == 0) { + throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); + } + return window_in_pkts; +} + +static managed_send_buffer::sptr get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + size_t fc_window, + const double timeout +){ + while (true) + { + // delta is the amount of FC credit we've used up + const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); + // If we want to send another packet, we must have FC credit left + if ((delta & HW_SEQ_NUM_MASK) < fc_window) + break; + + // If credit is all used up, we check seq_queue for more. + const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); + if (not ok) { + return managed_send_buffer::sptr(); //timeout waiting for flow control + } + } + + managed_send_buffer::sptr buff = xport->get_send_buff(timeout); + if (buff) { + fc_cache->last_seq_out++; //update seq, this will actually be a send + } + return buff; +} + +#define DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL 0 +/*! Handle incoming messages. If they're flow control, update the TX FC cache. + * Otherwise, send them to the async message queue for the user to poll. + * + * This is run inside a uhd::task as long as this streamer lives. + */ +static void handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + endianness_t endianness, + boost::function<double(void)> get_tick_rate +) { + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff) + return; + + //extract packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + //unpacking can fail + boost::uint32_t (*endian_conv)(boost::uint32_t) = uhd::ntohx; + try + { + if (endianness == ENDIANNESS_BIG) + { + vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); + endian_conv = uhd::ntohx; + } + else + { + vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); + endian_conv = uhd::wtohx; + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + return; + } + + double tick_rate = get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff( + endian_conv, + metadata, + if_packet_info, + packet_buff, + tick_rate, + fc_cache->stream_channel + ); + + // TODO: Shouldn't we be polling if_packet_info.packet_type == PACKET_TYPE_FC? + // Thing is, on X300, packet_type == 0, so that wouldn't work. But it seems it should. + //The FC response and the burst ack are two indicators that the radio + //consumed packets. Use them to update the FC metadata + if (metadata.event_code == DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL) { + const size_t seq = metadata.user_payload[0]; + fc_cache->seq_queue.push_with_pop_on_full(seq); + } + + //FC responses don't propagate up to the user so filter them here + if (metadata.event_code != DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL) { + fc_cache->async_queue->push_with_pop_on_full(metadata); + metadata.channel = fc_cache->device_channel; + fc_cache->old_async_queue->push_with_pop_on_full(metadata); + standard_async_msg_prints(metadata); + } +} + + + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool device3_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +) +{ + return _async_md->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +void device3_impl::update_rx_streamers(double /* rate */) +{ + BOOST_FOREACH(const std::string &block_id, _rx_streamers.keys()) { + UHD_STREAMER_LOG() << "[Device3] updating RX streamer to " << block_id << std::endl; + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[block_id].lock()); + if (my_streamer) { + double tick_rate = my_streamer->get_terminator()->get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1.0; + } + my_streamer->set_tick_rate(tick_rate); + double samp_rate = my_streamer->get_terminator()->get_output_samp_rate(); + if (samp_rate == rfnoc::rate_node_ctrl::RATE_UNDEFINED) { + samp_rate = 1.0; + } + // This formula is not derived by any scientific means -- we just need to + // increase the failure threshold as we increase rates. For 1 Msps, we use + // the default. + const size_t alignment_failure_factor = std::max(size_t(1), size_t(samp_rate * 1000 / tick_rate)); + double scaling = my_streamer->get_terminator()->get_output_scale_factor(); + if (scaling == rfnoc::scalar_node_ctrl::SCALE_UNDEFINED) { + scaling = 1/32767.; + } + UHD_STREAMER_LOG() << " New tick_rate == " << tick_rate << " New samp_rate == " << samp_rate << " New scaling == " << scaling << std::endl; + + my_streamer->set_tick_rate(tick_rate); + my_streamer->set_samp_rate(samp_rate); + // 1000 packets is the default alignment failure threshold + my_streamer->set_alignment_failure_threshold(1000 * alignment_failure_factor); + my_streamer->set_scale_factor(scaling); + } + } +} + +rx_streamer::sptr device3_impl::get_rx_stream(const stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_transport_setup_mutex); + stream_args_t args = sanitize_stream_args(args_); + + // I. Generate the channel list + std::vector<uhd::rfnoc::block_id_t> chan_list; + std::vector<device_addr_t> chan_args; + generate_channel_list(args, chan_list, chan_args); + // Note: All 'args.args' are merged into chan_args now. + + // II. Iterate over all channels + boost::shared_ptr<sph::recv_packet_streamer> my_streamer; + // The terminator's lifetime is coupled to the streamer. + // There is only one terminator. If the streamer has multiple channels, + // it will be connected to each upstream block. + rfnoc::rx_stream_terminator::sptr recv_terminator = rfnoc::rx_stream_terminator::make(); + for (size_t stream_i = 0; stream_i < chan_list.size(); stream_i++) { + // Get block ID and mb index + uhd::rfnoc::block_id_t block_id = chan_list[stream_i]; + UHD_STREAMER_LOG() << "[RX Streamer] chan " << stream_i << " connecting to " << block_id << std::endl; + // Update args so args.args is always valid for this particular channel: + args.args = chan_args[stream_i]; + size_t mb_index = block_id.get_device_no(); + size_t suggested_block_port = args.args.cast<size_t>("block_port", rfnoc::ANY_PORT); + + // Access to this channel's block control + uhd::rfnoc::source_block_ctrl_base::sptr blk_ctrl = + boost::dynamic_pointer_cast<uhd::rfnoc::source_block_ctrl_base>(get_block_ctrl(block_id)); + + // Connect the terminator with this channel's block. + size_t block_port = blk_ctrl->connect_downstream( + recv_terminator, + suggested_block_port, + args.args + ); + const size_t terminator_port = recv_terminator->connect_upstream(blk_ctrl); + blk_ctrl->set_downstream_port(block_port, terminator_port); + recv_terminator->set_upstream_port(terminator_port, block_port); + + // Check if the block connection is compatible (spp and item type) + check_stream_sig_compatible(blk_ctrl->get_output_signature(block_port), args, "RX"); + + // Setup the DSP transport hints + device_addr_t rx_hints = get_rx_hints(mb_index); + + //allocate sid and create transport + uhd::sid_t stream_address = blk_ctrl->get_address(block_port); + UHD_STREAMER_LOG() << "[RX Streamer] creating rx stream " << rx_hints.to_string() << std::endl; + both_xports_t xport = make_transport(stream_address, RX_DATA, rx_hints); + UHD_STREAMER_LOG() << std::hex << "[RX Streamer] data_sid = " << xport.send_sid << std::dec << " actual recv_buff_size = " << xport.recv_buff_size << std::endl; + + // Configure the block + blk_ctrl->set_destination(xport.send_sid.get_src(), block_port); + + blk_ctrl->sr_write(uhd::rfnoc::SR_RESP_OUT_DST_SID, xport.send_sid.get_src(), block_port); + UHD_STREAMER_LOG() << "[RX Streamer] resp_out_dst_sid == " << xport.send_sid.get_src() << std::endl; + + // Find all upstream radio nodes and set their response in SID to the host + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl> > upstream_radio_nodes = blk_ctrl->find_upstream_node<uhd::rfnoc::radio_ctrl>(); + UHD_STREAMER_LOG() << "[RX Streamer] Number of upstream radio nodes: " << upstream_radio_nodes.size() << std::endl; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl> &node, upstream_radio_nodes) { + node->sr_write(uhd::rfnoc::SR_RESP_OUT_DST_SID, xport.send_sid.get_src(), block_port); + } + + // To calculate the max number of samples per packet, we assume the maximum header length + // to avoid fragmentation should the entire header be used. + const size_t bpp = xport.recv->get_recv_frame_size() - stream_options.rx_max_len_hdr; // bytes per packet + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + const size_t spp = std::min(args.args.cast<size_t>("spp", bpp/bpi), bpp/bpi); // samples per packet + UHD_STREAMER_LOG() << "[RX Streamer] spp == " << spp << std::endl; + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + my_streamer->resize(chan_list.size()); + + //init some streamer stuff + std::string conv_endianness; + if (get_transport_endianness(mb_index) == ENDIANNESS_BIG) { + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); + conv_endianness = "le"; + } + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_" + conv_endianness; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //flow control setup + const size_t pkt_size = spp * bpi + stream_options.rx_max_len_hdr; + const size_t fc_window = get_rx_flow_control_window(pkt_size, xport.recv_buff_size, rx_hints); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / stream_options.rx_fc_request_freq); + UHD_STREAMER_LOG()<< "[RX Streamer] Flow Control Window (minus one) = " << fc_window-1 << ", Flow Control Handler Window = " << fc_handle_window << std::endl; + blk_ctrl->configure_flow_control_out( + fc_window-1, // Leave one space for overrun packets TODO make this obsolete + block_port + ); + + //Give the streamer a functor to get the recv_buffer + //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&zero_copy_if::get_recv_buff, xport.recv, _1), + true /*flush*/ + ); + + //Give the streamer a functor to handle overruns + //bind requires a weak_ptr to break the a streamer->streamer circular dependency + //Using "this" is OK because we know that this device3_impl will outlive the streamer + my_streamer->set_overflow_handler( + stream_i, + boost::bind( + &uhd::rfnoc::rx_stream_terminator::handle_overrun, recv_terminator, + boost::weak_ptr<uhd::rx_streamer>(my_streamer), stream_i + ) + ); + + //Give the streamer a functor to send flow control messages + //handle_rx_flowctrl is static and has no lifetime issues + boost::shared_ptr<rx_fc_cache_t> fc_cache(new rx_fc_cache_t()); + my_streamer->set_xport_handle_flowctrl( + stream_i, boost::bind( + &handle_rx_flowctrl, + xport.send_sid, + xport.send, + get_transport_endianness(mb_index), + fc_cache, + _1 + ), + fc_handle_window, + true/*init*/ + ); + + //Give the streamer a functor issue stream cmd + //bind requires a shared pointer to add a streamer->framer lifetime dependency + my_streamer->set_issue_stream_cmd( + stream_i, + boost::bind(&uhd::rfnoc::source_block_ctrl_base::issue_stream_cmd, blk_ctrl, _1, block_port) + ); + + // Tell the streamer which SID is valid for this channel + my_streamer->set_xport_chan_sid(stream_i, true, xport.send_sid); + } + + // Connect the terminator to the streamer + my_streamer->set_terminator(recv_terminator); + + // Notify all blocks in this chain that they are connected to an active streamer + recv_terminator->set_rx_streamer(true, 0); + + // Store a weak pointer to prevent a streamer->device3_impl->streamer circular dependency. + // Note that we store the streamer only once, and use its terminator's + // ID to do so. + _rx_streamers[recv_terminator->unique_id()] = boost::weak_ptr<sph::recv_packet_streamer>(my_streamer); + + // Sets tick rate, samp rate and scaling on this streamer. + // A registered terminator is required to do this. + update_rx_streamers(); + + post_streamer_hooks(RX_DIRECTION); + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +void device3_impl::update_tx_streamers(double /* rate */) +{ + BOOST_FOREACH(const std::string &block_id, _tx_streamers.keys()) { + UHD_STREAMER_LOG() << "[Device3] updating TX streamer: " << block_id << std::endl; + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[block_id].lock()); + if (my_streamer) { + double tick_rate = my_streamer->get_terminator()->get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1.0; + } + double samp_rate = my_streamer->get_terminator()->get_input_samp_rate(); + if (samp_rate == rfnoc::rate_node_ctrl::RATE_UNDEFINED) { + samp_rate = 1.0; + } + double scaling = my_streamer->get_terminator()->get_input_scale_factor(); + if (scaling == rfnoc::scalar_node_ctrl::SCALE_UNDEFINED) { + scaling = 32767.; + } + UHD_STREAMER_LOG() << " New tick_rate == " << tick_rate << " New samp_rate == " << samp_rate << " New scaling == " << scaling << std::endl; + my_streamer->set_tick_rate(tick_rate); + my_streamer->set_samp_rate(samp_rate); + my_streamer->set_scale_factor(scaling); + } + } +} + +tx_streamer::sptr device3_impl::get_tx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_transport_setup_mutex); + stream_args_t args = sanitize_stream_args(args_); + + // I. Generate the channel list + std::vector<uhd::rfnoc::block_id_t> chan_list; + std::vector<device_addr_t> chan_args; + generate_channel_list(args, chan_list, chan_args); + // Note: All 'args.args' are merged into chan_args now. + + //shared async queue for all channels in streamer + boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); + + // II. Iterate over all channels + boost::shared_ptr<sph::send_packet_streamer> my_streamer; + // The terminator's lifetime is coupled to the streamer. + // There is only one terminator. If the streamer has multiple channels, + // it will be connected to each downstream block. + rfnoc::tx_stream_terminator::sptr send_terminator = rfnoc::tx_stream_terminator::make(); + for (size_t stream_i = 0; stream_i < chan_list.size(); stream_i++) { + // Get block ID and mb index + uhd::rfnoc::block_id_t block_id = chan_list[stream_i]; + // Update args so args.args is always valid for this particular channel: + args.args = chan_args[stream_i]; + size_t mb_index = block_id.get_device_no(); + size_t suggested_block_port = args.args.cast<size_t>("block_port", rfnoc::ANY_PORT); + + // Access to this channel's block control + uhd::rfnoc::sink_block_ctrl_base::sptr blk_ctrl = + boost::dynamic_pointer_cast<uhd::rfnoc::sink_block_ctrl_base>(get_block_ctrl(block_id)); + + // Connect the terminator with this channel's block. + // This will throw if the connection is not possible. + size_t block_port = blk_ctrl->connect_upstream( + send_terminator, + suggested_block_port, + args.args + ); + const size_t terminator_port = send_terminator->connect_downstream(blk_ctrl); + blk_ctrl->set_upstream_port(block_port, terminator_port); + send_terminator->set_downstream_port(terminator_port, block_port); + + // Check if the block connection is compatible (spp and item type) + check_stream_sig_compatible(blk_ctrl->get_input_signature(block_port), args, "TX"); + + // Setup the dsp transport hints + device_addr_t tx_hints = get_tx_hints(mb_index); + + //allocate sid and create transport + uhd::sid_t stream_address = blk_ctrl->get_address(block_port); + UHD_STREAMER_LOG() << "[TX Streamer] creating tx stream " << tx_hints.to_string() << std::endl; + both_xports_t xport = make_transport(stream_address, TX_DATA, tx_hints); + UHD_STREAMER_LOG() << std::hex << "[TX Streamer] data_sid = " << xport.send_sid << std::dec << std::endl; + + // To calculate the max number of samples per packet, we assume the maximum header length + // to avoid fragmentation should the entire header be used. + const size_t bpp = tx_hints.cast<size_t>("bpp", xport.send->get_send_frame_size()) - stream_options.tx_max_len_hdr; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + const size_t spp = std::min(args.args.cast<size_t>("spp", bpp/bpi), bpp/bpi); // samples per packet + UHD_STREAMER_LOG() << "[TX Streamer] spp == " << spp << std::endl; + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + my_streamer->resize(chan_list.size()); + + //init some streamer stuff + std::string conv_endianness; + if (get_transport_endianness(mb_index) == ENDIANNESS_BIG) { + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); + conv_endianness = "le"; + } + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_" + conv_endianness; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //flow control setup + const size_t pkt_size = spp * bpi + stream_options.tx_max_len_hdr; + // For flow control, this value is used to determine the window size in *packets* + size_t fc_window = get_tx_flow_control_window( + pkt_size, // This is the maximum packet size + blk_ctrl->get_fifo_size(block_port), + tx_hints // This can override the value reported by the block! + ); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / stream_options.tx_fc_response_freq); + UHD_STREAMER_LOG() << "[TX Streamer] Flow Control Window = " << fc_window << ", Flow Control Handler Window = " << fc_handle_window << std::endl; + blk_ctrl->configure_flow_control_in( + stream_options.tx_fc_response_cycles, + fc_handle_window, /*pkts*/ + block_port + ); + + boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t()); + fc_cache->stream_channel = stream_i; + fc_cache->device_channel = mb_index; + fc_cache->async_queue = async_md; + fc_cache->old_async_queue = _async_md; + + boost::function<double(void)> tick_rate_retriever = boost::bind( + &rfnoc::tick_node_ctrl::get_tick_rate, + send_terminator, + std::set< rfnoc::node_ctrl_base::sptr >() // Need to specify default args with bind + ); + task::sptr task = task::make( + boost::bind( + &handle_tx_async_msgs, + fc_cache, + xport.recv, + get_transport_endianness(mb_index), + tick_rate_retriever + ) + ); + + blk_ctrl->sr_write(uhd::rfnoc::SR_RESP_IN_DST_SID, xport.recv_sid.get_dst(), block_port); + UHD_STREAMER_LOG() << "[TX Streamer] resp_in_dst_sid == " << boost::format("0x%04X") % xport.recv_sid.get_dst() << std::endl; + // Find all downstream radio nodes and set their response in SID to the host + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl> > downstream_radio_nodes = blk_ctrl->find_downstream_node<uhd::rfnoc::radio_ctrl>(); + UHD_STREAMER_LOG() << "[TX Streamer] Number of downstream radio nodes: " << downstream_radio_nodes.size() << std::endl; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl> &node, downstream_radio_nodes) { + node->sr_write(uhd::rfnoc::SR_RESP_IN_DST_SID, xport.send_sid.get_src(), block_port); + } + + //Give the streamer a functor to get the send buffer + //get_tx_buff_with_flowctrl is static so bind has no lifetime issues + //xport.send (sptr) is required to add streamer->data-transport lifetime dependency + //task (sptr) is required to add a streamer->async-handler lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&get_tx_buff_with_flowctrl, task, fc_cache, xport.send, fc_window, _1) + ); + //Give the streamer a functor handled received async messages + my_streamer->set_async_receiver( + boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) + ); + my_streamer->set_xport_chan_sid(stream_i, true, xport.send_sid); + // CHDR does not support trailers + my_streamer->set_enable_trailer(false); + } + + // Connect the terminator to the streamer + my_streamer->set_terminator(send_terminator); + + // Notify all blocks in this chain that they are connected to an active streamer + send_terminator->set_tx_streamer(true, 0); + + // Store a weak pointer to prevent a streamer->device3_impl->streamer circular dependency. + // Note that we store the streamer only once, and use its terminator's + // ID to do so. + _tx_streamers[send_terminator->unique_id()] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); + + // Sets tick rate, samp rate and scaling on this streamer + // A registered terminator is required to do this. + update_tx_streamers(); + + post_streamer_hooks(TX_DIRECTION); + return my_streamer; +} + + diff --git a/host/lib/usrp/e100/CMakeLists.txt b/host/lib/usrp/e100/CMakeLists.txt index 2a1e14eab..da77b85dc 100644 --- a/host/lib/usrp/e100/CMakeLists.txt +++ b/host/lib/usrp/e100/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP-E100 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF OFF) - IF(ENABLE_E100) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/host/lib/usrp/e100/dboard_iface.cpp b/host/lib/usrp/e100/dboard_iface.cpp index b5baf6c56..ce0ac026b 100644 --- a/host/lib/usrp/e100/dboard_iface.cpp +++ b/host/lib/usrp/e100/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011,2015 Ettus Research LLC +// Copyright 2010-2011,2015,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -66,12 +66,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -97,6 +101,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn); private: timed_wb_iface::sptr _wb_iface; @@ -127,6 +132,7 @@ void e100_dboard_iface::set_clock_rate(unit_t unit, double rate){ switch(unit){ case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + case UNIT_BOTH: set_clock_rate(UNIT_RX, rate); set_clock_rate(UNIT_TX, rate); return; } } @@ -142,14 +148,15 @@ double e100_dboard_iface::get_clock_rate(unit_t unit){ switch(unit){ case UNIT_RX: return _clock->get_rx_clock_rate(); case UNIT_TX: return _clock->get_tx_clock_rate(); + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ switch(unit){ case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } @@ -160,28 +167,40 @@ double e100_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ -void e100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void e100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void e100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t e100_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void e100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void e100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t e100_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t e100_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void e100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t e100_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void e100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void e100_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void e100_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t e100_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t e100_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -196,8 +215,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ switch(unit){ case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB; case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB; + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void e100_dboard_iface::write_spi( @@ -268,3 +287,8 @@ void e100_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _wb_iface->set_time(t); } + +void e100_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp index 6d3c08534..1f8fe84cb 100644 --- a/host/lib/usrp/e100/e100_impl.cpp +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -217,20 +217,20 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////////// //^^^ clock created up top, just reg props here... ^^^ _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) - .subscribe(boost::bind(&e100_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_tick_rate, this, _1)); - //subscribe the command time while we are at it + //add_coerced_subscriber the command time while we are at it _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create codec control objects @@ -241,18 +241,18 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(e100_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); + .set_coercer(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(e100_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) - .publish(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); + .add_coerced_subscriber(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) + .set_publisher(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&e100_impl::get_ref_locked, this)); + .set_publisher(boost::bind(&e100_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // Create the GPSDO control @@ -272,7 +272,7 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ BOOST_FOREACH(const std::string &name, _gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _gps, name)); } } else @@ -288,27 +288,27 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE)); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////////// @@ -327,20 +327,20 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _rx_dsps[dspno]->set_link_rate(E100_RX_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) - .subscribe(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////////// @@ -351,17 +351,17 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ ); _tx_dsp->set_link_rate(E100_TX_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) - .subscribe(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); //////////////////////////////////////////////////////////////////// // create time control objects @@ -375,21 +375,21 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _time64, _1)); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&e100_impl::update_clock_source, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_clock_source, this, _1)); std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); if (_gps and _gps->gps_detected()) clock_sources.push_back("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); @@ -399,7 +399,7 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _user, _1)); //////////////////////////////////////////////////////////////////// // create dboard control objects @@ -417,32 +417,31 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); //create a new dboard interface and manager - _dboard_iface = make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); _dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1)); } //initialize io handling @@ -457,8 +456,8 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// this->update_rates(); - _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter - .subscribe(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") //now add_coerced_subscriber the clock rate setter + .add_coerced_subscriber(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); //reset cordic rates and their properties to zero BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp index d00668224..b05053f84 100644 --- a/host/lib/usrp/e100/e100_impl.hpp +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -111,7 +111,6 @@ private: //dboard stuff uhd::usrp::dboard_manager::sptr _dboard_manager; - uhd::usrp::dboard_iface::sptr _dboard_iface; bool _ignore_cal_file; std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; diff --git a/host/lib/usrp/e300/CMakeLists.txt b/host/lib/usrp/e300/CMakeLists.txt index 9c8aa29b9..68c3520e4 100644 --- a/host/lib/usrp/e300/CMakeLists.txt +++ b/host/lib/usrp/e300/CMakeLists.txt @@ -24,8 +24,6 @@ ######################################################################## find_package(UDev) -LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_E300) LIST(APPEND E300_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/e300_impl.cpp diff --git a/host/lib/usrp/e300/e300_fpga_defs.hpp b/host/lib/usrp/e300/e300_fpga_defs.hpp index 594461518..36dd47383 100644 --- a/host/lib/usrp/e300/e300_fpga_defs.hpp +++ b/host/lib/usrp/e300/e300_fpga_defs.hpp @@ -21,7 +21,7 @@ namespace uhd { namespace usrp { namespace e300 { namespace fpga { static const size_t NUM_RADIOS = 2; -static const boost::uint32_t COMPAT_MAJOR = 14; +static const boost::uint32_t COMPAT_MAJOR = 16; static const boost::uint32_t COMPAT_MINOR = 0; }}}} // namespace diff --git a/host/lib/usrp/e300/e300_impl.cpp b/host/lib/usrp/e300/e300_impl.cpp index a57c86c1d..114686b4f 100644 --- a/host/lib/usrp/e300/e300_impl.cpp +++ b/host/lib/usrp/e300/e300_impl.cpp @@ -48,6 +48,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace uhd::transport; namespace fs = boost::filesystem; namespace asio = boost::asio; @@ -470,14 +471,14 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) BOOST_FOREACH(const std::string &name, _sensor_manager->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name)); + .set_publisher(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name)); } #ifdef E300_GPSD if (_gps) { BOOST_FOREACH(const std::string &name, _gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gpsd_iface::get_sensor, _gps, name)); + .set_publisher(boost::bind(&gpsd_iface::get_sensor, _gps, name)); } } #endif @@ -487,7 +488,7 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) //////////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(_eeprom_manager->get_mb_eeprom()) // set first... - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_mb_eeprom, _eeprom_manager, _1)); @@ -495,9 +496,9 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) // clocking //////////////////////////////////////////////////////////////////// _tree->create<double>(mb_path / "tick_rate") - .coerce(boost::bind(&e300_impl::_set_tick_rate, this, _1)) - .publish(boost::bind(&e300_impl::_get_tick_rate, this)) - .subscribe(boost::bind(&e300_impl::_update_tick_rate, this, _1)); + .set_coercer(boost::bind(&e300_impl::_set_tick_rate, this, _1)) + .set_publisher(boost::bind(&e300_impl::_get_tick_rate, this)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_tick_rate, this, _1)); //default some chains on -- needed for setup purposes _codec_ctrl->set_active_chains(true, false, true, false); @@ -509,42 +510,47 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) for(size_t instance = 0; instance < fpga::NUM_RADIOS; instance++) this->_setup_radio(instance); - // Radio 0 loopback through AD9361 - _codec_mgr->loopback_self_test(_radio_perifs[0].ctrl, radio::sr_addr(radio::CODEC_IDLE), radio::RB64_CODEC_READBACK); - // Radio 1 loopback through AD9361 - _codec_mgr->loopback_self_test(_radio_perifs[1].ctrl, radio::sr_addr(radio::CODEC_IDLE), radio::RB64_CODEC_READBACK); - + //now test each radio module's connection to the codec interface + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + _codec_mgr->loopback_self_test( + boost::bind( + &radio_ctrl_core_3000::poke32, perif.ctrl, radio::sr_addr(radio::CODEC_IDLE), _1 + ), + boost::bind(&radio_ctrl_core_3000::peek64, perif.ctrl, radio::RB64_CODEC_READBACK) + ); + } //////////////////////////////////////////////////////////////////// // internal gpios //////////////////////////////////////////////////////////////////// - gpio_core_200::sptr fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); + gpio_atr_3000::sptr fp_gpio = gpio_atr_3000::make(_radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) { _tree->create<boost::uint32_t>(mb_path / "gpio" / "INT0" / attr.second) - .subscribe(boost::bind(&e300_impl::_set_internal_gpio, this, fp_gpio, attr.first, _1)) + .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, fp_gpio, attr.first, _1)) .set(0); } _tree->create<boost::uint8_t>(mb_path / "gpio" / "INT0" / "READBACK") - .publish(boost::bind(&e300_impl::_get_internal_gpio, this, fp_gpio)); + .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, fp_gpio)); //////////////////////////////////////////////////////////////////// // register the time keepers - only one can be the highlander //////////////////////////////////////////////////////////////////// _tree->create<time_spec_t>(mb_path / "time" / "now") - .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) - .subscribe(boost::bind(&e300_impl::_set_time, this, _1)) + .set_publisher(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&e300_impl::_set_time, this, _1)) .set(0.0); //re-sync the times when the tick rate changes _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&e300_impl::_sync_times, this)); + .add_coerced_subscriber(boost::bind(&e300_impl::_sync_times, this)); _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1)); + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1)) + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source" / "value") - .subscribe(boost::bind(&e300_impl::_update_time_source, this, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_time_source, this, _1)) .set(e300::DEFAULT_TIME_SRC); #ifdef E300_GPSD static const std::vector<std::string> time_sources = boost::assign::list_of("none")("internal")("external")("gpsdo"); @@ -554,7 +560,7 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources); //setup reference source props _tree->create<std::string>(mb_path / "clock_source" / "value") - .subscribe(boost::bind(&e300_impl::_update_clock_source, this, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_clock_source, this, _1)) .set(e300::DEFAULT_CLOCK_SRC); static const std::vector<std::string> clock_sources = boost::assign::list_of("internal"); //external,gpsdo not supported _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_sources); @@ -565,13 +571,13 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) dboard_eeprom_t db_eeprom; _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom") .set(_eeprom_manager->get_db_eeprom()) - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_db_eeprom, _eeprom_manager, _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom") .set(_eeprom_manager->get_db_eeprom()) - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_db_eeprom, _eeprom_manager, _1)); @@ -604,10 +610,10 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1)); //////////////////////////////////////////////////////////////////// // do some post-init tasks @@ -631,37 +637,6 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); } -boost::uint8_t e300_impl::_get_internal_gpio(gpio_core_200::sptr gpio) -{ - return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); -} - -void e300_impl::_set_internal_gpio( - gpio_core_200::sptr gpio, - const gpio_attr_t attr, - const boost::uint32_t value) -{ - switch (attr) - { - case GPIO_CTRL: - return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); - case GPIO_DDR: - return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); - case GPIO_OUT: - return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); - case GPIO_ATR_0X: - return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); - case GPIO_ATR_RX: - return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); - case GPIO_ATR_TX: - return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); - case GPIO_ATR_XX: - return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); - default: - UHD_THROW_INVALID_CODE_PATH(); - } -} - uhd::sensor_value_t e300_impl::_get_fe_pll_lock(const bool is_tx) { const boost::uint32_t st = @@ -1001,7 +976,8 @@ void e300_impl::_setup_radio(const size_t dspno) //////////////////////////////////////////////////////////////////// // Set up peripherals //////////////////////////////////////////////////////////////////// - perif.atr = gpio_core_200_32wo::make(perif.ctrl, radio::sr_addr(radio::GPIO)); + perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, radio::sr_addr(radio::GPIO)); + perif.atr->set_atr_mode(MODE_ATR, 0xFFFFFFFF); perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::RX_FRONT)); perif.rx_fe->set_dc_offset(rx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); perif.rx_fe->set_dc_offset_auto(rx_frontend_core_200::DEFAULT_DC_OFFSET_ENABLE); @@ -1036,26 +1012,25 @@ void e300_impl::_setup_radio(const size_t dspno) // connect rx dsp control objects //////////////////////////////////////////////////////////////////// _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) - .subscribe(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % dspno); perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); _tree->access<double>(rx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1)) ; _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); //////////////////////////////////////////////////////////////////// // create tx dsp control objects //////////////////////////////////////////////////////////////////// _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1)) - .subscribe(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % dspno); perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); _tree->access<double>(tx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1)) ; //////////////////////////////////////////////////////////////////// @@ -1075,10 +1050,10 @@ void e300_impl::_setup_radio(const size_t dspno) // This will connect all the e300_impl-specific items _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked") - .publish(boost::bind(&e300_impl::_get_fe_pll_lock, this, dir == TX_DIRECTION)) + .set_publisher(boost::bind(&e300_impl::_get_fe_pll_lock, this, dir == TX_DIRECTION)) ; _tree->access<double>(rf_fe_path / "freq" / "value") - .subscribe(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1)) ; // Antenna Setup @@ -1086,7 +1061,7 @@ void e300_impl::_setup_radio(const size_t dspno) static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2"); _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants); _tree->create<std::string>(rf_fe_path / "antenna" / "value") - .subscribe(boost::bind(&e300_impl::_update_antenna_sel, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_antenna_sel, this, dspno, _1)) .set("RX2"); } else if (dir == TX_DIRECTION) { @@ -1315,11 +1290,11 @@ void e300_impl::_update_atrs(void) if (enb_tx) fd_reg |= tx_enables | xx_leds; - gpio_core_200_32wo::sptr atr = _radio_perifs[instance].atr; - atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, oo_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rx_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd_reg); + gpio_atr_3000::sptr atr = _radio_perifs[instance].atr; + atr->set_atr_reg(ATR_REG_IDLE, oo_reg); + atr->set_atr_reg(ATR_REG_RX_ONLY, rx_reg); + atr->set_atr_reg(ATR_REG_TX_ONLY, tx_reg); + atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd_reg); } } diff --git a/host/lib/usrp/e300/e300_impl.hpp b/host/lib/usrp/e300/e300_impl.hpp index 595b42679..e9a0b4b9a 100644 --- a/host/lib/usrp/e300/e300_impl.hpp +++ b/host/lib/usrp/e300/e300_impl.hpp @@ -41,7 +41,7 @@ #include "tx_dsp_core_3000.hpp" #include "ad9361_ctrl.hpp" #include "ad936x_manager.hpp" -#include "gpio_core_200.hpp" +#include "gpio_atr_3000.hpp" #include "e300_global_regs.hpp" #include "e300_i2c.hpp" @@ -147,7 +147,7 @@ private: // types struct radio_perifs_t { radio_ctrl_core_3000::sptr ctrl; - gpio_core_200_32wo::sptr atr; + gpio_atr::gpio_atr_3000::sptr atr; time_core_3000::sptr time64; rx_vita_core_3000::sptr framer; rx_dsp_core_3000::sptr ddc; @@ -283,14 +283,6 @@ private: // methods // get frontend lock sensor uhd::sensor_value_t _get_fe_pll_lock(const bool is_tx); - // internal gpios - boost::uint8_t _get_internal_gpio(gpio_core_200::sptr); - - void _set_internal_gpio( - gpio_core_200::sptr gpio, - const gpio_attr_t attr, - const boost::uint32_t value); - private: // members uhd::device_addr_t _device_addr; xport_t _xport_path; diff --git a/host/lib/usrp/e300/e300_io_impl.cpp b/host/lib/usrp/e300/e300_io_impl.cpp index 29d250c8f..c84042e98 100644 --- a/host/lib/usrp/e300/e300_io_impl.cpp +++ b/host/lib/usrp/e300/e300_io_impl.cpp @@ -87,7 +87,6 @@ void e300_impl::_update_tick_rate(const double rate) boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock()); if (my_streamer) my_streamer->set_tick_rate(rate); - perif.deframer->set_tick_rate(_tick_rate); } } @@ -158,10 +157,8 @@ void e300_impl::_update_subdev_spec( const std::string conn = _tree->access<std::string>( mb_path / "dboards" / spec[i].db_name / ("rx_frontends") / spec[i].sd_name / "connection").get(); - - const bool fe_swapped = (conn == "QI" or conn == "Q"); - _radio_perifs[i].ddc->set_mux(conn, fe_swapped); - _radio_perifs[i].rx_fe->set_mux(fe_swapped); + _radio_perifs[i].ddc->set_mux(usrp::fe_connection_t(conn)); + _radio_perifs[i].rx_fe->set_mux(false); } } diff --git a/host/lib/usrp/e300/e300_regs.hpp b/host/lib/usrp/e300/e300_regs.hpp index 846c759a4..74e45df00 100644 --- a/host/lib/usrp/e300/e300_regs.hpp +++ b/host/lib/usrp/e300/e300_regs.hpp @@ -41,7 +41,7 @@ static const uint32_t TIME = 128; static const uint32_t RX_DSP = 144; static const uint32_t TX_DSP = 184; static const uint32_t LEDS = 195; -static const uint32_t FP_GPIO = 200; +static const uint32_t FP_GPIO = 201; static const uint32_t RX_FRONT = 208; static const uint32_t TX_FRONT = 216; static const uint32_t CODEC_IDLE = 250; diff --git a/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp index 17a564f21..cb2583b1b 100644 --- a/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp +++ b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp @@ -36,6 +36,9 @@ public: { } + void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) {}; + void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) {}; + double set_gain(const std::string &which, const double value) { _clear(); diff --git a/host/lib/usrp/fe_connection.cpp b/host/lib/usrp/fe_connection.cpp new file mode 100644 index 000000000..071f5ecf2 --- /dev/null +++ b/host/lib/usrp/fe_connection.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/fe_connection.hpp> +#include <uhd/exception.hpp> +#include <boost/regex.hpp> +#include <uhd/utils/math.hpp> + +using namespace uhd::usrp; + +fe_connection_t::fe_connection_t( + sampling_t sampling_mode, bool iq_swapped, + bool i_inverted, bool q_inverted, double if_freq +) : _sampling_mode(sampling_mode), _iq_swapped(iq_swapped), + _i_inverted(i_inverted), _q_inverted(q_inverted), _if_freq(if_freq) +{ +} + +fe_connection_t::fe_connection_t(const std::string& conn_str, double if_freq) { + static const boost::regex conn_regex("([IQ])(b?)(([IQ])(b?))?"); + boost::cmatch matches; + if (boost::regex_match(conn_str.c_str(), matches, conn_regex)) { + if (matches[3].length() == 0) { + //Connection in {I, Q, Ib, Qb} + _sampling_mode = REAL; + _iq_swapped = (matches[1].str() == "Q"); + _i_inverted = (matches[2].length() != 0); + _q_inverted = false; //IQ is swapped after inversion + } else { + //Connection in {I(b?)Q(b?), Q(b?)I(b?), I(b?)I(b?), Q(b?)Q(b?)} + _sampling_mode = (matches[1].str() == matches[4].str()) ? HETERODYNE : QUADRATURE; + _iq_swapped = (matches[1].str() == "Q"); + size_t i_idx = _iq_swapped ? 5 : 2, q_idx = _iq_swapped ? 2 : 5; + _i_inverted = (matches[i_idx].length() != 0); + _q_inverted = (matches[q_idx].length() != 0); + + if (_sampling_mode == HETERODYNE and _i_inverted != _q_inverted) { + throw uhd::value_error("Invalid connection string: " + conn_str); + } + } + _if_freq = if_freq; + } else { + throw uhd::value_error("Invalid connection string: " + conn_str); + } +} + +bool uhd::usrp::operator==(const fe_connection_t &lhs, const fe_connection_t &rhs){ + return ((lhs.get_sampling_mode() == rhs.get_sampling_mode()) and + (lhs.is_iq_swapped() == rhs.is_iq_swapped()) and + (lhs.is_i_inverted() == rhs.is_i_inverted()) and + (lhs.is_q_inverted() == rhs.is_q_inverted()) and + uhd::math::frequencies_are_equal(lhs.get_if_freq(), rhs.get_if_freq())); +} diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 396237e24..7905a6d32 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,6 +27,7 @@ #include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/convert.hpp> #include <uhd/utils/soft_register.hpp> +#include "legacy_compat.hpp" #include <boost/assign/list_of.hpp> #include <boost/thread.hpp> #include <boost/foreach.hpp> @@ -39,6 +40,7 @@ using namespace uhd; using namespace uhd::usrp; const std::string multi_usrp::ALL_GAINS = ""; +const std::string multi_usrp::ALL_LOS = "all"; UHD_INLINE std::string string_vector_to_string(std::vector<std::string> values, std::string delimiter = std::string(" ")) { @@ -389,12 +391,28 @@ public: multi_usrp_impl(const device_addr_t &addr){ _dev = device::make(addr, device::USRP); _tree = _dev->get_tree(); + _is_device3 = bool(boost::dynamic_pointer_cast<uhd::device3>(_dev)); + + if (is_device3()) { + _legacy_compat = rfnoc::legacy_compat::make(get_device3(), addr); + } } device::sptr get_device(void){ return _dev; } + bool is_device3(void) { + return _is_device3; + } + + device3::sptr get_device3(void) { + if (not is_device3()) { + throw uhd::type_error("Cannot call get_device3() on a non-generation 3 device."); + } + return boost::dynamic_pointer_cast<uhd::device3>(_dev); + } + dict<std::string, std::string> get_usrp_rx_info(size_t chan){ mboard_chan_pair mcp = rx_chan_to_mcp(chan); dict<std::string, std::string> usrp_info; @@ -438,8 +456,10 @@ public: ******************************************************************/ void set_master_clock_rate(double rate, size_t mboard){ if (mboard != ALL_MBOARDS){ - if (_tree->exists(mb_root(mboard) / "auto_tick_rate")) { + if (_tree->exists(mb_root(mboard) / "auto_tick_rate") + and _tree->access<bool>(mb_root(mboard) / "auto_tick_rate").get()) { _tree->access<bool>(mb_root(mboard) / "auto_tick_rate").set(false); + UHD_MSG(status) << "Setting master clock rate selection to 'manual'." << std::endl; } _tree->access<double>(mb_root(mboard) / "tick_rate").set(rate); return; @@ -605,7 +625,12 @@ public: void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t chan){ if (chan != ALL_CHANS){ - _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + if (is_device3()) { + mboard_chan_pair mcp = rx_chan_to_mcp(chan); + _legacy_compat->issue_stream_cmd(stream_cmd, mcp.mboard, mcp.chan); + } else { + _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + } return; } for (size_t c = 0; c < get_rx_num_channels(); c++){ @@ -740,6 +765,9 @@ public: ******************************************************************/ rx_streamer::sptr get_rx_stream(const stream_args_t &args) { _check_link_rate(args, false); + if (is_device3()) { + return _legacy_compat->get_rx_stream(args); + } return this->get_device()->get_rx_stream(args); } @@ -830,6 +858,171 @@ public: return _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "freq" / "range").get(); } + std::vector<std::string> get_rx_lo_names(size_t chan = 0){ + std::vector<std::string> lo_names; + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + BOOST_FOREACH(const std::string &name, _tree->list(rx_rf_fe_root(chan) / "los")) { + lo_names.push_back(name); + } + } + return lo_names; + } + + void set_rx_lo_source(const std::string &src, const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) { + //Special value ALL_LOS support atomically sets the source for all LOs + _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "value").set(src); + } else { + BOOST_FOREACH(const std::string &n, _tree->list(rx_rf_fe_root(chan) / "los")) { + this->set_rx_lo_source(src, n, chan); + } + } + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / name / "source" / "value").set(src); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error("This device does not support manual configuration of LOs"); + } + } + + const std::string get_rx_lo_source(const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + //Special value ALL_LOS support atomically sets the source for all LOs + return _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "value").get(); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / name / "source" / "value").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s) then it can only be internal + return "internal"; + } + } + + std::vector<std::string> get_rx_lo_sources(const std::string &name = ALL_LOS, size_t chan = 0) { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) { + //Special value ALL_LOS support atomically sets the source for all LOs + return _tree->access< std::vector<std::string> >(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "options").get(); + } else { + return std::vector<std::string>(); + } + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access< std::vector<std::string> >(rx_rf_fe_root(chan) / "los" / name / "source" / "options").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s) then it can only be internal + return std::vector<std::string>(1, "internal"); + } + } + + void set_rx_lo_export_enabled(bool enabled, const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) { + //Special value ALL_LOS support atomically sets the source for all LOs + _tree->access<bool>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "export").set(enabled); + } else { + BOOST_FOREACH(const std::string &n, _tree->list(rx_rf_fe_root(chan) / "los")) { + this->set_rx_lo_export_enabled(enabled, n, chan); + } + } + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + _tree->access<bool>(rx_rf_fe_root(chan) / "los" / name / "export").set(enabled); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error("This device does not support manual configuration of LOs"); + } + } + + bool get_rx_lo_export_enabled(const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + //Special value ALL_LOS support atomically sets the source for all LOs + return _tree->access<bool>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "export").get(); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access<bool>(rx_rf_fe_root(chan) / "los" / name / "export").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s), assume it cannot export + return false; + } + } + + double set_rx_lo_freq(double freq, const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency must be set for each stage individually"); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").set(freq); + return _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error("This device does not support manual configuration of LOs"); + } + } + + double get_rx_lo_freq(const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency must be retrieved for each stage individually"); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // Return actual RF frequency if the daughterboard doesn't expose it's LO(s) + return _tree->access<double>(rx_rf_fe_root(chan) / "freq" /" value").get(); + } + } + + freq_range_t get_rx_lo_freq_range(const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency range must be retrieved for each stage individually"); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access<freq_range_t>(rx_rf_fe_root(chan) / "los" / name / "freq" / "range").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // Return the actual RF range if the daughterboard doesn't expose it's LO(s) + return _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "freq" / "range").get(); + } + } + void set_rx_gain(double gain, const std::string &name, size_t chan){ /* Check if any AGC mode is enable and if so warn the user */ if (chan != ALL_CHANS) { @@ -1100,6 +1293,9 @@ public: ******************************************************************/ tx_streamer::sptr get_tx_stream(const stream_args_t &args) { _check_link_rate(args, true); + if (is_device3()) { + return _legacy_compat->get_tx_stream(args); + } return this->get_device()->get_tx_stream(args); } @@ -1346,10 +1542,10 @@ public: if (attr == "CTRL") iface->set_pin_ctrl(unit, boost::uint16_t(value), boost::uint16_t(mask)); if (attr == "DDR") iface->set_gpio_ddr(unit, boost::uint16_t(value), boost::uint16_t(mask)); if (attr == "OUT") iface->set_gpio_out(unit, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_0X") iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_RX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_TX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_XX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_0X") iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_RX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_TX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_XX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, boost::uint16_t(value), boost::uint16_t(mask)); } } @@ -1367,10 +1563,10 @@ public: if (attr == "CTRL") return iface->get_pin_ctrl(unit); if (attr == "DDR") return iface->get_gpio_ddr(unit); if (attr == "OUT") return iface->get_gpio_out(unit); - if (attr == "ATR_0X") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_IDLE); - if (attr == "ATR_RX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY); - if (attr == "ATR_TX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY); - if (attr == "ATR_XX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX); + if (attr == "ATR_0X") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_IDLE); + if (attr == "ATR_RX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY); + if (attr == "ATR_TX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY); + if (attr == "ATR_XX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX); if (attr == "READBACK") return iface->read_gpio(unit); } return 0; @@ -1456,9 +1652,8 @@ public: default: throw uhd::assertion_error("multi_usrp::read_register - register has invalid bitwidth: " + path); } - } else { - throw uhd::not_implemented_error("multi_usrp::read_register - register IO not supported for this device"); } + throw uhd::not_implemented_error("multi_usrp::read_register - register IO not supported for this device"); } std::vector<std::string> enumerate_registers(const size_t mboard) @@ -1494,6 +1689,8 @@ public: private: device::sptr _dev; property_tree::sptr _tree; + bool _is_device3; + uhd::rfnoc::legacy_compat::sptr _legacy_compat; struct mboard_chan_pair{ size_t mboard, chan; @@ -1546,6 +1743,10 @@ private: fs_path rx_dsp_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->rx_dsp_root(mcp.mboard, mcp.chan); + } + if (_tree->exists(mb_root(mcp.mboard) / "rx_chan_dsp_mapping")) { std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "rx_chan_dsp_mapping").get(); UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -1566,6 +1767,10 @@ private: fs_path tx_dsp_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->tx_dsp_root(mcp.mboard, mcp.chan); + } + if (_tree->exists(mb_root(mcp.mboard) / "tx_chan_dsp_mapping")) { std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "tx_chan_dsp_mapping").get(); UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -1585,6 +1790,9 @@ private: fs_path rx_fe_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->rx_fe_root(mcp.mboard, mcp.chan); + } try { const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); @@ -1599,6 +1807,9 @@ private: fs_path tx_fe_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->tx_fe_root(mcp.mboard, mcp.chan); + } try { const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); diff --git a/host/lib/usrp/n230/CMakeLists.txt b/host/lib/usrp/n230/CMakeLists.txt new file mode 100644 index 000000000..9eaccffba --- /dev/null +++ b/host/lib/usrp/n230/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 2013 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the N230 support +######################################################################## +IF(ENABLE_N230) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/n230_cores.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_resource_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_eeprom_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_stream_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_clk_pps_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_frontend_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_uart.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_image_loader.cpp + ) +ENDIF(ENABLE_N230) diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp new file mode 100644 index 000000000..9d704b702 --- /dev/null +++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp @@ -0,0 +1,158 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_clk_pps_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <stdexcept> +#include <cmath> +#include <cstdlib> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_clk_pps_ctrl_impl : public n230_clk_pps_ctrl +{ +public: + n230_clk_pps_ctrl_impl( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores + ): _codec_ctrl(codec_ctrl), + _ref_pll_ctrl(ref_pll_ctrl), + _core_misc_reg(core_misc_reg), + _core_pps_sel_reg(core_pps_sel), + _core_status_reg(core_status_reg), + _time_cores(time_cores), + _tick_rate(0.0), + _clock_source("<undefined>"), + _time_source("<undefined>") + { + } + + virtual ~n230_clk_pps_ctrl_impl() + { + } + + double set_tick_rate(const double rate) + { + UHD_MSG(status) << "Configuring a tick rate of " << rate/1e6 << " MHz... "; + _tick_rate = _codec_ctrl->set_clock_rate(rate); + UHD_MSG(status) << "got " << _tick_rate/1e6 << " MHz\n"; + + BOOST_FOREACH(time_core_3000::sptr& time_core, _time_cores) { + time_core->set_tick_rate(_tick_rate); + time_core->self_test(); + } + + return _tick_rate; + } + + double get_tick_rate() + { + return _tick_rate; + } + + void set_clock_source(const std::string &source) + { + if (_clock_source == source) return; + + if (source == "internal") { + _ref_pll_ctrl->set_lock_to_ext_ref(false); + } else if (source == "external" || source == "gpsdo") { + _ref_pll_ctrl->set_lock_to_ext_ref(true); + } else { + throw uhd::key_error("set_clock_source: unknown source: " + source); + } + _core_misc_reg.write(fpga::core_misc_reg_t::REF_SEL, (source == "gpsdo") ? 1 : 0); + + _clock_source = source; + } + + const std::string& get_clock_source() + { + return _clock_source; + } + + uhd::sensor_value_t get_ref_locked() + { + bool locked = false; + if (_clock_source == "external" || _clock_source == "gpsdo") { + locked = (_core_status_reg.read(fpga::core_status_reg_t::REF_LOCKED) == 1); + } else { + //If the source is internal, the charge pump on the ADF4001 is tristated which + //means that the 40MHz VCTXXO is free running i.e. always "locked" + locked = true; + } + return sensor_value_t("Ref", locked, "locked", "unlocked"); + } + + void set_pps_source(const std::string &source) + { + if (_time_source == source) return; + + if (source == "none" or source == "gpsdo") { + _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 0); + } else if (source == "external") { + _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 1); + } else { + throw uhd::key_error("update_time_source: unknown source: " + source); + } + + _time_source = source; + } + + const std::string& get_pps_source() + { + return _time_source; + } + +private: + ad9361_ctrl::sptr _codec_ctrl; + n230_ref_pll_ctrl::sptr _ref_pll_ctrl; + fpga::core_misc_reg_t& _core_misc_reg; + fpga::core_pps_sel_reg_t& _core_pps_sel_reg; + fpga::core_status_reg_t& _core_status_reg; + std::vector<time_core_3000::sptr> _time_cores; + double _tick_rate; + std::string _clock_source; + std::string _time_source; +}; + +}}} //namespace + +using namespace uhd::usrp::n230; +using namespace uhd::usrp; + +n230_clk_pps_ctrl::sptr n230_clk_pps_ctrl::make( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel_reg, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores) +{ + return sptr(new n230_clk_pps_ctrl_impl( + codec_ctrl, ref_pll_ctrl, core_misc_reg, core_pps_sel_reg, core_status_reg, time_cores)); +} + diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp new file mode 100644 index 000000000..3e0a21e04 --- /dev/null +++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp @@ -0,0 +1,89 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_CLK_PPS_CTRL_HPP +#define INCLUDED_N230_CLK_PPS_CTRL_HPP + +#include "time_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_clk_pps_ctrl : boost::noncopyable +{ +public: + typedef boost::shared_ptr<n230_clk_pps_ctrl> sptr; + + static sptr make( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel_reg, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores); + + virtual ~n230_clk_pps_ctrl() {} + + /*********************************************************************** + * Tick Rate + **********************************************************************/ + /*! Set the master clock rate of the device. + * \return the clock frequency in Hz + */ + virtual double set_tick_rate(const double rate) = 0; + + /*! Get the master clock rate of the device. + * \return the clock frequency in Hz + */ + virtual double get_tick_rate() = 0; + + /*********************************************************************** + * Reference clock + **********************************************************************/ + /*! Set the reference clock source of the device. + */ + virtual void set_clock_source(const std::string &source) = 0; + + /*! Get the reference clock source of the device. + */ + virtual const std::string& get_clock_source() = 0; + + /*! Get the reference clock lock status. + */ + virtual uhd::sensor_value_t get_ref_locked() = 0; + + /*********************************************************************** + * Time source + **********************************************************************/ + /*! Set the time source of the device. + */ + virtual void set_pps_source(const std::string &source) = 0; + + /*! Get the reference clock source of the device. + */ + virtual const std::string& get_pps_source() = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_CLK_PPS_CTRL_HPP */ diff --git a/host/lib/usrp/n230/n230_cores.cpp b/host/lib/usrp/n230/n230_cores.cpp new file mode 100644 index 000000000..58c702ec1 --- /dev/null +++ b/host/lib/usrp/n230/n230_cores.cpp @@ -0,0 +1,91 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" +#include "n230_fw_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +n230_core_spi_core::n230_core_spi_core( + uhd::wb_iface::sptr iface, + perif_t default_perif) : + _spi_core(spi_core_3000::make(iface, + fpga::sr_addr(fpga::SR_CORE_SPI), + fpga::rb_addr(fpga::RB_CORE_SPI))), + _current_perif(default_perif), + _last_perif(default_perif) +{ + change_perif(default_perif); +} + +boost::uint32_t n230_core_spi_core::transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback) +{ + boost::mutex::scoped_lock lock(_mutex); + return _spi_core->transact_spi(which_slave, config, data, num_bits, readback); +} + +void n230_core_spi_core::change_perif(perif_t perif) +{ + boost::mutex::scoped_lock lock(_mutex); + _last_perif = _current_perif; + _current_perif = perif; + + switch (_current_perif) { + case CODEC: + _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::CODEC_SPI_CLOCK_FREQ); + break; + case PLL: + _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::ADF4001_SPI_CLOCK_FREQ); + break; + } +} + +void n230_core_spi_core::restore_perif() +{ + change_perif(_last_perif); +} + +n230_ref_pll_ctrl::n230_ref_pll_ctrl(n230_core_spi_core::sptr spi) : + adf4001_ctrl(spi, fpga::ADF4001_SPI_SLAVE_NUM), + _spi(spi) +{ +} + +void n230_ref_pll_ctrl::set_lock_to_ext_ref(bool external) +{ + _spi->change_perif(n230_core_spi_core::PLL); + adf4001_ctrl::set_lock_to_ext_ref(external); + _spi->restore_perif(); +} + +}}} //namespace + +using namespace uhd::usrp::n230; +using namespace uhd::usrp; + +n230_core_spi_core::sptr n230_core_spi_core::make( + uhd::wb_iface::sptr iface, n230_core_spi_core::perif_t default_perif) +{ + return sptr(new n230_core_spi_core(iface, default_perif)); +} + diff --git a/host/lib/usrp/n230/n230_cores.hpp b/host/lib/usrp/n230/n230_cores.hpp new file mode 100644 index 000000000..3f56c1889 --- /dev/null +++ b/host/lib/usrp/n230/n230_cores.hpp @@ -0,0 +1,71 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_CORES_HPP +#define INCLUDED_N230_CORES_HPP + +#include "spi_core_3000.hpp" +#include "adf4001_ctrl.hpp" +#include <boost/thread/mutex.hpp> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_core_spi_core : boost::noncopyable, public uhd::spi_iface { + +public: + typedef boost::shared_ptr<n230_core_spi_core> sptr; + + enum perif_t { + CODEC, PLL + }; + + n230_core_spi_core(uhd::wb_iface::sptr iface, perif_t default_perif); + + virtual boost::uint32_t transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback); + + void change_perif(perif_t perif); + void restore_perif(); + + static sptr make(uhd::wb_iface::sptr iface, perif_t default_perif = CODEC); + +private: + spi_core_3000::sptr _spi_core; + perif_t _current_perif; + perif_t _last_perif; + boost::mutex _mutex; +}; + +class n230_ref_pll_ctrl : public adf4001_ctrl { +public: + typedef boost::shared_ptr<n230_ref_pll_ctrl> sptr; + + n230_ref_pll_ctrl(n230_core_spi_core::sptr spi); + void set_lock_to_ext_ref(bool external); + +private: + n230_core_spi_core::sptr _spi; +}; + + +}}} //namespace + +#endif /* INCLUDED_N230_CORES_HPP */ diff --git a/host/lib/usrp/n230/n230_defaults.h b/host/lib/usrp/n230/n230_defaults.h new file mode 100644 index 000000000..a25978585 --- /dev/null +++ b/host/lib/usrp/n230/n230_defaults.h @@ -0,0 +1,65 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_DEFAULTS_H +#define INCLUDED_N230_DEFAULTS_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif +#include <uhd/transport/udp_constants.hpp> + +namespace uhd { +namespace usrp { +namespace n230 { + +static const double DEFAULT_TICK_RATE = 46.08e6; +static const double MAX_TICK_RATE = 50e6; +static const double MIN_TICK_RATE = 1e6; + +static const double DEFAULT_TX_SAMP_RATE = 1.0e6; +static const double DEFAULT_RX_SAMP_RATE = 1.0e6; +static const double DEFAULT_DDC_FREQ = 0.0; +static const double DEFAULT_DUC_FREQ = 0.0; + +static const double DEFAULT_FE_GAIN = 0.0; +static const double DEFAULT_FE_FREQ = 1.0e9; +static const double DEFAULT_FE_BW = 56e6; + +static const std::string DEFAULT_TIME_SRC = "none"; +static const std::string DEFAULT_CLOCK_SRC = "internal"; + +static const size_t DEFAULT_FRAME_SIZE = 1500 - 20 - 8; //default ipv4 mtu - ipv4 header - udp header +static const size_t MAX_FRAME_SIZE = 8000; +static const size_t MIN_FRAME_SIZE = IP_PROTOCOL_MIN_MTU_SIZE; + +static const size_t DEFAULT_NUM_FRAMES = 32; + +//A 1MiB SRAM is shared between two radios so we allocate each +//radio 0.5MiB minus 8 packets worth of buffering to ensure +//that the FIFO does not overflow +static const size_t DEFAULT_SEND_BUFF_SIZE = 500*1024; +#if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) +static const size_t DEFAULT_RECV_BUFF_SIZE = 0x100000; //1Mib +#elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) +static const size_t DEFAULT_RECV_BUFF_SIZE = 0x2000000;//32MiB +#endif + +}}} //namespace + +#endif /* INCLUDED_N230_DEFAULTS_H */ diff --git a/host/lib/usrp/n230/n230_device_args.hpp b/host/lib/usrp/n230/n230_device_args.hpp new file mode 100644 index 000000000..014a6cd14 --- /dev/null +++ b/host/lib/usrp/n230/n230_device_args.hpp @@ -0,0 +1,128 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_DEV_ARGS_HPP +#define INCLUDED_N230_DEV_ARGS_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include "../common/constrained_device_args.hpp" +#include "n230_defaults.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_device_args_t : public constrained_device_args_t +{ +public: + enum loopback_mode_t { LOOPBACK_OFF=0, LOOPBACK_RADIO=1, LOOPBACK_CODEC=2 }; + + n230_device_args_t(): + _master_clock_rate("master_clock_rate", n230::DEFAULT_TICK_RATE), + _send_frame_size("send_frame_size", n230::DEFAULT_FRAME_SIZE), + _recv_frame_size("recv_frame_size", n230::DEFAULT_FRAME_SIZE), + _num_send_frames("num_send_frames", n230::DEFAULT_NUM_FRAMES), + _num_recv_frames("num_recv_frames", n230::DEFAULT_NUM_FRAMES), + _send_buff_size("send_buff_size", n230::DEFAULT_SEND_BUFF_SIZE), + _recv_buff_size("recv_buff_size", n230::DEFAULT_RECV_BUFF_SIZE), + _safe_mode("safe_mode", false), + _loopback_mode("loopback_mode", LOOPBACK_OFF, boost::assign::list_of("off")("radio")("codec")) + {} + + double get_master_clock_rate() const { + return _master_clock_rate.get(); + } + size_t get_send_frame_size() const { + return _send_frame_size.get(); + } + size_t get_recv_frame_size() const { + return _recv_frame_size.get(); + } + size_t get_num_send_frames() const { + return _num_send_frames.get(); + } + size_t get_num_recv_frames() const { + return _num_recv_frames.get(); + } + size_t get_send_buff_size() const { + return _send_buff_size.get(); + } + size_t get_recv_buff_size() const { + return _recv_buff_size.get(); + } + bool get_safe_mode() const { + return _safe_mode.get(); + } + loopback_mode_t get_loopback_mode() const { + return _loopback_mode.get(); + } + + inline virtual std::string to_string() const { + return _master_clock_rate.to_string() + ", " + + _send_frame_size.to_string() + ", " + + _recv_frame_size.to_string() + ", " + + _num_send_frames.to_string() + ", " + + _num_recv_frames.to_string() + ", " + + _send_buff_size.to_string() + ", " + + _recv_buff_size.to_string() + ", " + + _safe_mode.to_string() + ", " + + _loopback_mode.to_string(); + } +private: + virtual void _parse(const device_addr_t& dev_args) { + //Extract parameters from dev_args + if (dev_args.has_key(_master_clock_rate.key())) + _master_clock_rate.parse(dev_args[_master_clock_rate.key()]); + if (dev_args.has_key(_send_frame_size.key())) + _send_frame_size.parse(dev_args[_send_frame_size.key()]); + if (dev_args.has_key(_recv_frame_size.key())) + _recv_frame_size.parse(dev_args[_recv_frame_size.key()]); + if (dev_args.has_key(_num_send_frames.key())) + _num_send_frames.parse(dev_args[_num_send_frames.key()]); + if (dev_args.has_key(_num_recv_frames.key())) + _num_recv_frames.parse(dev_args[_num_recv_frames.key()]); + if (dev_args.has_key(_send_buff_size.key())) + _send_buff_size.parse(dev_args[_send_buff_size.key()]); + if (dev_args.has_key(_recv_buff_size.key())) + _recv_buff_size.parse(dev_args[_recv_buff_size.key()]); + if (dev_args.has_key(_safe_mode.key())) + _safe_mode.parse(dev_args[_safe_mode.key()]); + if (dev_args.has_key(_loopback_mode.key())) + _loopback_mode.parse(dev_args[_loopback_mode.key()], false /* assert invalid */); + + //Sanity check params + _enforce_range(_master_clock_rate, MIN_TICK_RATE, MAX_TICK_RATE); + _enforce_range(_send_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE); + _enforce_range(_recv_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE); + _enforce_range(_num_send_frames, (size_t)2, (size_t)UINT_MAX); + _enforce_range(_num_recv_frames, (size_t)2, (size_t)UINT_MAX); + } + + constrained_device_args_t::num_arg<double> _master_clock_rate; + constrained_device_args_t::num_arg<size_t> _send_frame_size; + constrained_device_args_t::num_arg<size_t> _recv_frame_size; + constrained_device_args_t::num_arg<size_t> _num_send_frames; + constrained_device_args_t::num_arg<size_t> _num_recv_frames; + constrained_device_args_t::num_arg<size_t> _send_buff_size; + constrained_device_args_t::num_arg<size_t> _recv_buff_size; + constrained_device_args_t::bool_arg _safe_mode; + constrained_device_args_t::enum_arg<loopback_mode_t> _loopback_mode; +}; + +}}} //namespace + +#endif //INCLUDED_N230_DEV_ARGS_HPP diff --git a/host/lib/usrp/n230/n230_eeprom.h b/host/lib/usrp/n230/n230_eeprom.h new file mode 100644 index 000000000..b6c2a0c76 --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom.h @@ -0,0 +1,124 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_EEPROM_H +#define INCLUDED_N230_EEPROM_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define N230_NUM_ETH_PORTS 2 +#define N230_MAX_NUM_ETH_PORTS 2 + +#if (N230_NUM_ETH_PORTS > N230_MAX_NUM_ETH_PORTS) +#error +#endif + +#define N230_EEPROM_VER_MAJOR 1 +#define N230_EEPROM_VER_MINOR 1 +#define N230_EEPROM_SERIAL_LEN 9 +#define N230_EEPROM_NAME_LEN 32 + +typedef struct +{ + uint8_t mac_addr[6]; + uint8_t _pad[2]; + uint32_t subnet; + uint32_t ip_addr; +} n230_eth_eeprom_map_t; + +typedef struct +{ + //Data format version + uint16_t data_version_major; + uint16_t data_version_minor; + + //HW identification info + uint16_t hw_revision; + uint16_t hw_product; + uint8_t serial[N230_EEPROM_SERIAL_LEN]; + uint8_t _pad_serial; + uint16_t hw_revision_compat; + uint8_t _pad0[18 - (N230_EEPROM_SERIAL_LEN + 1)]; + + //Ethernet specific + uint32_t gateway; + n230_eth_eeprom_map_t eth_info[N230_MAX_NUM_ETH_PORTS]; + + //User specific + uint8_t user_name[N230_EEPROM_NAME_LEN]; +} n230_eeprom_map_t; + +#ifdef __cplusplus +} //extern "C" +#endif + +// The following definitions are only useful in firmware. Exclude in host code. +#ifndef __cplusplus + +/*! + * Read the eeprom and update caches. + * Returns true if read was successful. + * If the read was not successful then the cache is initialized with + * default values and marked as dirty. + */ +bool read_n230_eeprom(); + +/*! + * Write the contents of the cache to the eeprom. + * Returns true if write was successful. + */ +bool write_n230_eeprom(); + +/*! + * Returns the dirty state of the cache. + */ +bool is_n230_eeprom_cache_dirty(); + +/*! + * Returns a const pointer to the EEPROM map. + */ +const n230_eeprom_map_t* get_n230_const_eeprom_map(); + +/*! + * Returns the settings for the the 'iface'th ethernet interface + */ +const n230_eth_eeprom_map_t* get_n230_ethernet_info(uint32_t iface); + +/*! + * Returns a non-const pointer to the EEPROM map. Will mark the cache as dirty. + */ +n230_eeprom_map_t* get_n230_eeprom_map(); + +/*! + * FPGA Image operations + */ +inline void read_n230_fpga_image_page(uint32_t offset, void *buf, uint32_t num_bytes); + +inline bool write_n230_fpga_image_page(uint32_t offset, const void *buf, uint32_t num_bytes); + +inline bool erase_n230_fpga_image_sector(uint32_t offset); + +#endif //ifdef __cplusplus + +#endif /* INCLUDED_N230_EEPROM_H */ diff --git a/host/lib/usrp/n230/n230_eeprom_manager.cpp b/host/lib/usrp/n230/n230_eeprom_manager.cpp new file mode 100644 index 000000000..b19deb23a --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.cpp @@ -0,0 +1,207 @@ +// +// Copyright 2013-2014,2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_eeprom.h" +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/mac_addr.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include "n230_eeprom_manager.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +const double n230_eeprom_manager::UDP_TIMEOUT_IN_SEC = 2.0; + +n230_eeprom_manager::n230_eeprom_manager(const std::string& addr): + _seq_num(0) +{ + _udp_xport = transport::udp_simple::make_connected( + addr, BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT)); + read_mb_eeprom(); +} + +static const std::string _bytes_to_string(const uint8_t* bytes, size_t max_len) +{ + std::string out; + for (size_t i = 0; i < max_len; i++) { + if (bytes[i] < 32 or bytes[i] > 127) return out; + out += bytes[i]; + } + return out; +} + +static void _string_to_bytes(const std::string &string, size_t max_len, uint8_t* buffer) +{ + byte_vector_t bytes; + const size_t len = std::min(string.size(), max_len); + for (size_t i = 0; i < len; i++){ + buffer[i] = string[i]; + } + if (len < max_len - 1) buffer[len] = '\0'; +} + +const mboard_eeprom_t& n230_eeprom_manager::read_mb_eeprom() +{ + boost::mutex::scoped_lock lock(_mutex); + + //Read EEPROM from device + _transact(N230_FLASH_COMM_CMD_READ_NV_DATA); + const n230_eeprom_map_t* map_ptr = reinterpret_cast<const n230_eeprom_map_t*>(_response.data); + const n230_eeprom_map_t& map = *map_ptr; + + uint16_t ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major); + uint16_t ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor); + + _mb_eeprom["product"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_product)); + _mb_eeprom["revision"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_revision)); + //The revision_compat field does not exist in version 1.0 + //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set + //revision_compat = revision + if (ver_major == 1 and ver_minor == 0) { + _mb_eeprom["revision_compat"] = _mb_eeprom["revision"]; + } else { + _mb_eeprom["revision_compat"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_revision_compat)); + } + _mb_eeprom["serial"] = _bytes_to_string( + map.serial, N230_EEPROM_SERIAL_LEN); + + //Extract ethernet info + _mb_eeprom["gateway"] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.gateway)).to_string(); + for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) { + const std::string n(1, i+'0'); + _mb_eeprom["ip-addr"+n] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.eth_info[i].ip_addr)).to_string(); + _mb_eeprom["subnet"+n] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.eth_info[i].subnet)).to_string(); + byte_vector_t mac_addr(map.eth_info[i].mac_addr, map.eth_info[i].mac_addr + 6); + _mb_eeprom["mac-addr"+n] = mac_addr_t::from_bytes(mac_addr).to_string(); + } + + _mb_eeprom["name"] = _bytes_to_string( + map.user_name, N230_EEPROM_NAME_LEN); + + return _mb_eeprom; +} + +void n230_eeprom_manager::write_mb_eeprom(const mboard_eeprom_t& eeprom) +{ + boost::mutex::scoped_lock lock(_mutex); + + _mb_eeprom = eeprom; + + n230_eeprom_map_t* map_ptr = reinterpret_cast<n230_eeprom_map_t*>(_request.data); + memset(map_ptr, 0xff, sizeof(n230_eeprom_map_t)); //Initialize to erased state + //Read EEPROM from device + _transact(N230_FLASH_COMM_CMD_READ_NV_DATA); + memcpy(map_ptr, _response.data, sizeof(n230_eeprom_map_t)); + n230_eeprom_map_t& map = *map_ptr; + + // Automatic version upgrade handling + uint16_t old_ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major); + uint16_t old_ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor); + + //The revision_compat field does not exist for version 1.0 so force write it + //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set + //revision_compat = revision for the upgrade + bool force_write_version_compat = (old_ver_major == 1 and old_ver_minor == 0); + + map.data_version_major = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MAJOR); + map.data_version_minor = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MINOR); + + if (_mb_eeprom.has_key("product")) { + map.hw_product = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["product"])); + } + if (_mb_eeprom.has_key("revision")) { + map.hw_revision = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision"])); + } + if (_mb_eeprom.has_key("revision_compat")) { + map.hw_revision_compat = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision_compat"])); + } else if (force_write_version_compat) { + map.hw_revision_compat = map.hw_revision; + } + if (_mb_eeprom.has_key("serial")) { + _string_to_bytes(_mb_eeprom["serial"], N230_EEPROM_SERIAL_LEN, map.serial); + } + + //Push ethernet info + if (_mb_eeprom.has_key("gateway")){ + map.gateway = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["gateway"]).to_ulong()); + } + for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) { + const std::string n(1, i+'0'); + if (_mb_eeprom.has_key("ip-addr"+n)){ + map.eth_info[i].ip_addr = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["ip-addr"+n]).to_ulong()); + } + if (_mb_eeprom.has_key("subnet"+n)){ + map.eth_info[i].subnet = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["subnet"+n]).to_ulong()); + } + if (_mb_eeprom.has_key("mac-addr"+n)) { + byte_vector_t mac_addr = mac_addr_t::from_string(_mb_eeprom["mac-addr"+n]).to_bytes(); + std::copy(mac_addr.begin(), mac_addr.end(), map.eth_info[i].mac_addr); + } + } + //store the name + if (_mb_eeprom.has_key("name")) { + _string_to_bytes(_mb_eeprom["name"], N230_EEPROM_NAME_LEN, map.user_name); + } + + //Write EEPROM to device + _transact(N230_FLASH_COMM_CMD_WRITE_NV_DATA); +} + +void n230_eeprom_manager::_transact(const boost::uint32_t command) +{ + //Load request struct + _request.flags = uhd::htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK | command); + _request.seq = uhd::htonx<boost::uint32_t>(_seq_num++); + + //Send request + _flush_xport(); + _udp_xport->send(boost::asio::buffer(&_request, sizeof(_request))); + + //Recv reply + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&_response, sizeof(_response)), UDP_TIMEOUT_IN_SEC); + if (nbytes == 0) throw uhd::io_error("n230_eeprom_manager::_transact failure"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(_response.flags); + UHD_ASSERT_THROW(nbytes == sizeof(_response)); + UHD_ASSERT_THROW(_response.seq == _request.seq); + UHD_ASSERT_THROW(flags & command); +} + +void n230_eeprom_manager::_flush_xport() +{ + char buff[sizeof(n230_flash_prog_t)] = {}; + while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { + /*NOP*/ + } +} + +}}}; //namespace diff --git a/host/lib/usrp/n230/n230_eeprom_manager.hpp b/host/lib/usrp/n230/n230_eeprom_manager.hpp new file mode 100644 index 000000000..cc5aee9f3 --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.hpp @@ -0,0 +1,58 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_EEPROM_MANAGER_HPP +#define INCLUDED_N230_EEPROM_MANAGER_HPP + +#include <boost/thread/mutex.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include "n230_fw_host_iface.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_eeprom_manager : boost::noncopyable +{ +public: + n230_eeprom_manager(const std::string& addr); + + const mboard_eeprom_t& read_mb_eeprom(); + void write_mb_eeprom(const mboard_eeprom_t& eeprom); + + inline const mboard_eeprom_t& get_mb_eeprom() { + return _mb_eeprom; + } + +private: //Functions + void _transact(const boost::uint32_t command); + void _flush_xport(); + +private: //Members + mboard_eeprom_t _mb_eeprom; + transport::udp_simple::sptr _udp_xport; + n230_flash_prog_t _request; + n230_flash_prog_t _response; + boost::uint32_t _seq_num; + boost::mutex _mutex; + + static const double UDP_TIMEOUT_IN_SEC; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_EEPROM_MANAGER_HPP */ diff --git a/host/lib/usrp/n230/n230_fpga_defs.h b/host/lib/usrp/n230/n230_fpga_defs.h new file mode 100644 index 000000000..3aa96643f --- /dev/null +++ b/host/lib/usrp/n230/n230_fpga_defs.h @@ -0,0 +1,207 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_FPGA_DEFS_H +#define INCLUDED_N230_FPGA_DEFS_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif +#include <uhd/utils/soft_register.hpp> + +namespace uhd { +namespace usrp { +namespace n230 { +namespace fpga { + +static inline uint32_t sr_addr(uint32_t offset) { + return (offset*4); +} + +static inline uint32_t rb_addr(uint32_t offset) { + return (offset*8); +} + +static const size_t NUM_RADIOS = 2; +static const double BUS_CLK_RATE = 80e6; + +/******************************************************************* + * CVITA Routing + *******************************************************************/ +static const uint32_t CVITA_UDP_PORT = 49153; +static const bool CVITA_BIG_ENDIAN = true; + +enum xb_endpoint_t { + N230_XB_DST_E0 = 0, + N230_XB_DST_E1 = 1, + N230_XB_DST_R0 = 2, + N230_XB_DST_R1 = 3, + N230_XB_DST_GCTRL = 4, + N230_XB_DST_UART = 5 +}; + +static const boost::uint8_t RADIO_CTRL_SUFFIX = 0x00; +static const boost::uint8_t RADIO_FC_SUFFIX = 0x01; +static const boost::uint8_t RADIO_DATA_SUFFIX = 0x02; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_CORE_RADIO_CONTROL = 3; +static const uint32_t SR_CORE_LOOPBACK = 4; +static const uint32_t SR_CORE_BIST1 = 5; +static const uint32_t SR_CORE_BIST2 = 6; +static const uint32_t SR_CORE_SPI = 8; +static const uint32_t SR_CORE_MISC = 16; +static const uint32_t SR_CORE_DATA_DELAY = 17; +static const uint32_t SR_CORE_CLK_DELAY = 18; +static const uint32_t SR_CORE_COMPAT = 24; +static const uint32_t SR_CORE_READBACK = 32; +static const uint32_t SR_CORE_GPSDO_ST = 40; +static const uint32_t SR_CORE_PPS_SEL = 48; +static const uint32_t SR_CORE_MS0_GPIO = 50; +static const uint32_t SR_CORE_MS1_GPIO = 58; + +static const uint32_t RB_CORE_SIGNATUE = 0; +static const uint32_t RB_CORE_SPI = 1; +static const uint32_t RB_CORE_STATUS = 2; +static const uint32_t RB_CORE_BIST = 3; +static const uint32_t RB_CORE_VERSION_HASH = 4; +static const uint32_t RB_CORE_MS0_GPIO = 5; +static const uint32_t RB_CORE_MS1_GPIO = 6; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_RADIO_SPI = 8; +static const uint32_t SR_RADIO_ATR = 12; +static const uint32_t SR_RADIO_SW_RST = 20; +static const uint32_t SR_RADIO_TEST = 21; +static const uint32_t SR_RADIO_CODEC_IDLE = 22; +static const uint32_t SR_RADIO_READBACK = 32; +static const uint32_t SR_RADIO_TX_CTRL = 64; +static const uint32_t SR_RADIO_RX_CTRL = 96; +static const uint32_t SR_RADIO_RX_DSP = 144; +static const uint32_t SR_RADIO_TX_DSP = 184; +static const uint32_t SR_RADIO_TIME = 128; +static const uint32_t SR_RADIO_RX_FMT = 136; +static const uint32_t SR_RADIO_TX_FMT = 138; +static const uint32_t SR_RADIO_USER_SR = 253; + +static const uint32_t RB_RADIO_TEST = 0; +static const uint32_t RB_RADIO_TIME_NOW = 1; +static const uint32_t RB_RADIO_TIME_PPS = 2; +static const uint32_t RB_RADIO_CODEC_DATA = 3; +static const uint32_t RB_RADIO_DEBUG = 4; +static const uint32_t RB_RADIO_FRAMER = 5; +static const uint32_t SR_RADIO_USER_RB = 7; + +static const uint32_t AD9361_SPI_SLAVE_NUM = 0x1; +static const uint32_t ADF4001_SPI_SLAVE_NUM = 0x2; + +static const uint32_t RB_N230_PRODUCT_ID = 1; +static const uint32_t RB_N230_COMPAT_MAJOR = 0x20; +static const uint32_t RB_N230_COMPAT_SAFE = 0xC0; + +/******************************************************************* + * Codec Interface Specific + *******************************************************************/ + +// Matches delay setting of 0x00 in AD9361 register 0x006 +static const uint32_t CODEC_DATA_DELAY = 0; +static const uint32_t CODEC_CLK_DELAY = 16; + +//This number must be < 46.08MHz to make sure we don't +//violate timing for radio_clk. It is only used during +//initialization so the exact value does not matter. +static const double CODEC_DEFAULT_CLK_RATE = 40e6; + +/******************************************************************* + * Link Specific + *******************************************************************/ +static const double N230_LINK_RATE_BPS = 1e9/8; + +/******************************************************************* + * GPSDO + *******************************************************************/ +static const uint32_t GPSDO_UART_BAUDRATE = 115200; +static const uint32_t GPSDO_ST_ABSENT = 0x83; +/******************************************************************* + * Register Objects + *******************************************************************/ +class core_radio_ctrl_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(MIMO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(CODEC_ARST, /*width*/ 1, /*shift*/ 1); //[1] + + core_radio_ctrl_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_RADIO_CONTROL)) + { + //Initial values + set(CODEC_ARST, 0); + set(MIMO, 1); //MIMO always ON for now + } +}; + +class core_misc_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(REF_SEL, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_C, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_B, /*width*/ 1, /*shift*/ 2); //[2] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_A, /*width*/ 1, /*shift*/ 3); //[3] + UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_B, /*width*/ 1, /*shift*/ 4); //[4] + UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_A, /*width*/ 1, /*shift*/ 5); //[5] + + core_misc_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_MISC)) + { + //Initial values + set(REF_SEL, 0); + set(RX_BANDSEL_C, 0); + set(RX_BANDSEL_B, 0); + set(RX_BANDSEL_A, 0); + set(TX_BANDSEL_B, 0); + set(TX_BANDSEL_A, 0); + } +}; + +class core_pps_sel_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(EXT_PPS_EN, /*width*/ 1, /*shift*/ 0); //[0] + + core_pps_sel_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_PPS_SEL)) + { + //Initial values + set(EXT_PPS_EN, 0); + } +}; + +class core_status_reg_t : public soft_reg64_ro_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(REF_LOCKED, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(GPSDO_STATUS, /*width*/ 8, /*shift*/ 32); //[32:39] + + core_status_reg_t(): + soft_reg64_ro_t(fpga::rb_addr(fpga::RB_CORE_STATUS)) + { } +}; + +}}}} //namespace + +#endif /* INCLUDED_N230_FPGA_DEFS_H */ diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.cpp b/host/lib/usrp/n230/n230_frontend_ctrl.cpp new file mode 100644 index 000000000..e0820d9b2 --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2013-2014,2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_frontend_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +/* ATR Control Bits */ +static const boost::uint32_t TX_ENABLE = (1 << 7); +static const boost::uint32_t SFDX_RX = (1 << 6); +static const boost::uint32_t SFDX_TX = (1 << 5); +static const boost::uint32_t SRX_RX = (1 << 4); +static const boost::uint32_t SRX_TX = (1 << 3); +static const boost::uint32_t LED_RX = (1 << 2); +static const boost::uint32_t LED_TXRX_RX = (1 << 1); +static const boost::uint32_t LED_TXRX_TX = (1 << 0); + +/* ATR State Definitions. */ +static const boost::uint32_t STATE_OFF = 0x00; +static const boost::uint32_t STATE_RX_RX2 = (SFDX_RX + | SFDX_TX + | LED_RX); +static const boost::uint32_t STATE_RX_TXRX = (SRX_RX + | SRX_TX + | LED_TXRX_RX); +static const boost::uint32_t STATE_FDX_TXRX = (TX_ENABLE + | SFDX_RX + | SFDX_TX + | LED_TXRX_TX + | LED_RX); +static const boost::uint32_t STATE_TX_TXRX = (TX_ENABLE + | SFDX_RX + | SFDX_TX + | LED_TXRX_TX); + +using namespace uhd::usrp; + +class n230_frontend_ctrl_impl : public n230_frontend_ctrl +{ +public: + n230_frontend_ctrl_impl( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores + ): _core_ctrl(core_ctrl), + _codec_ctrl(codec_ctrl), + _gpio_cores(gpio_cores), + _core_misc_reg(core_misc_reg) + { + } + + virtual ~n230_frontend_ctrl_impl() + { + } + + void set_antenna_sel(const size_t which, const std::string &ant) + { + if (ant != "TX/RX" and ant != "RX2") + throw uhd::value_error("n230: unknown RX antenna option: " + ant); + + _fe_states[which].rx_ant = ant; + _flush_atr_state(); + } + + void set_stream_state(const fe_state_t fe0_state_, const fe_state_t fe1_state_) + { + //Update soft-state + _fe_states[0].state = fe0_state_; + _fe_states[1].state = fe1_state_; + + const fe_state_t fe0_state = _fe_states[0].state; + const fe_state_t fe1_state = (_gpio_cores.size() > 1) ? _fe_states[1].state : NONE_STREAMING; + + const size_t num_tx = (_is_tx(fe0_state) ? 1 : 0) + (_is_tx(fe1_state) ? 1 : 0); + const size_t num_rx = (_is_rx(fe0_state) ? 1 : 0) + (_is_rx(fe1_state) ? 1 : 0); + + //setup the active chains in the codec + if ((num_rx + num_tx) == 0) { + _codec_ctrl->set_active_chains( + true, false, + true, false); //enable something + } else { + _codec_ctrl->set_active_chains( + _is_tx(fe0_state), _is_tx(fe1_state), + _is_rx(fe0_state), _is_rx(fe1_state)); + } + + _core_misc_reg.flush(); + //atrs change based on enables + _flush_atr_state(); + } + + + void set_stream_state(const size_t which, const fe_state_t state) + { + if (which == 0) { + set_stream_state(state, _fe_states[1].state); + } else if (which == 1) { + set_stream_state(_fe_states[0].state, state); + } else { + throw uhd::value_error( + str(boost::format("n230: unknown stream index option: %d") % which) + ); + } + } + + void set_bandsel(const std::string& which, double freq) + { + using namespace n230::fpga; + + if(which[0] == 'R') { + if(freq < 2.2e9) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 1); + } else if((freq >= 2.2e9) && (freq < 4e9)) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 1); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0); + } else if((freq >= 4e9) && (freq <= 6e9)) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 1); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } else if(which[0] == 'T') { + if(freq < 2.5e9) { + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 1); + } else if((freq >= 2.5e9) && (freq <= 6e9)) { + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 1); + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 0); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + + _core_misc_reg.flush(); + } + + void set_self_test_mode(self_test_mode_t mode) + { + switch (mode) { + case LOOPBACK_RADIO: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x1); + } break; + case LOOPBACK_CODEC: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0); + _codec_ctrl->data_port_loopback(true); + } break; + //Default = disable + default: + case LOOPBACK_DISABLED: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0); + _codec_ctrl->data_port_loopback(false); + } break; + } + } + +private: + void _flush_atr_state() + { + for (size_t i = 0; i < _gpio_cores.size(); i++) { + const fe_state_cache_t& fe_state_cache = _fe_states[i]; + const bool enb_rx = _is_rx(fe_state_cache.state); + const bool enb_tx = _is_tx(fe_state_cache.state); + const bool is_rx2 = (fe_state_cache.rx_ant == "RX2"); + const size_t rxonly = (enb_rx)? ((is_rx2)? STATE_RX_RX2 : STATE_RX_TXRX) : STATE_OFF; + const size_t txonly = (enb_tx)? (STATE_TX_TXRX) : STATE_OFF; + size_t fd = STATE_OFF; + if (enb_rx and enb_tx) fd = STATE_FDX_TXRX; + if (enb_rx and not enb_tx) fd = rxonly; + if (not enb_rx and enb_tx) fd = txonly; + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_IDLE, STATE_OFF); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, rxonly); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, txonly); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, fd); + } + } + + inline static bool _is_tx(const fe_state_t state) + { + return state == TX_STREAMING || state == TXRX_STREAMING; + } + + inline static bool _is_rx(const fe_state_t state) + { + return state == RX_STREAMING || state == TXRX_STREAMING; + } + +private: + struct fe_state_cache_t { + fe_state_cache_t() : state(NONE_STREAMING), rx_ant("RX2") + {} + fe_state_t state; + std::string rx_ant; + }; + + radio_ctrl_core_3000::sptr _core_ctrl; + ad9361_ctrl::sptr _codec_ctrl; + std::vector<gpio_atr::gpio_atr_3000::sptr> _gpio_cores; + fpga::core_misc_reg_t& _core_misc_reg; + uhd::dict<size_t, fe_state_cache_t> _fe_states; +}; + +}}} //namespace + +using namespace uhd::usrp::n230; + +n230_frontend_ctrl::sptr n230_frontend_ctrl::make( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores) +{ + return sptr(new n230_frontend_ctrl_impl(core_ctrl, core_misc_reg, codec_ctrl, gpio_cores)); +} + diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.hpp b/host/lib/usrp/n230/n230_frontend_ctrl.hpp new file mode 100644 index 000000000..377d23ba8 --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.hpp @@ -0,0 +1,76 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_FRONTEND_CTRL_HPP +#define INCLUDED_N230_FRONTEND_CTRL_HPP + +#include "radio_ctrl_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include "gpio_atr_3000.hpp" +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +enum fe_state_t { + NONE_STREAMING, TX_STREAMING, RX_STREAMING, TXRX_STREAMING +}; + +enum self_test_mode_t { + LOOPBACK_DISABLED, LOOPBACK_RADIO, LOOPBACK_CODEC +}; + + +class n230_frontend_ctrl : boost::noncopyable +{ +public: + typedef boost::shared_ptr<n230_frontend_ctrl> sptr; + + static sptr make( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores); + + virtual ~n230_frontend_ctrl() {} + + virtual void set_antenna_sel( + const size_t which, + const std::string &ant) = 0; + + virtual void set_stream_state( + const size_t which, + const fe_state_t state) = 0; + + virtual void set_stream_state( + const fe_state_t fe0_state, + const fe_state_t fe1_state) = 0; + + virtual void set_bandsel( + const std::string& which, + double freq) = 0; + + virtual void set_self_test_mode( + self_test_mode_t mode) = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_FRONTEND_CTRL_HPP */ diff --git a/host/lib/usrp/n230/n230_fw_defs.h b/host/lib/usrp/n230/n230_fw_defs.h new file mode 100644 index 000000000..fbdc67ebb --- /dev/null +++ b/host/lib/usrp/n230/n230_fw_defs.h @@ -0,0 +1,137 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_FW_DEFS_H +#define INCLUDED_N230_FW_DEFS_H + +#include <stdint.h> + +/*! + * Constants specific to N230 firmware. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + * However, if it is included from within the host code, + * it will be namespaced appropriately + */ +#ifdef __cplusplus +namespace uhd { +namespace usrp { +namespace n230 { +namespace fw { +#endif + +static inline uint32_t reg_addr(uint32_t base, uint32_t offset) { + return ((base) + (offset)*4); +} + +/******************************************************************* + * Global + *******************************************************************/ +static const uint32_t CPU_CLOCK_FREQ = 80000000; +static const uint32_t PER_MILLISEC_CRON_JOBID = 0; +static const uint32_t PER_SECOND_CRON_JOBID = 1; + +/******************************************************************* + * Wishbone slave addresses + *******************************************************************/ +static const uint32_t WB_MAIN_RAM_BASE = 0x0000; +static const uint32_t WB_PKT_RAM_BASE = 0x8000; +static const uint32_t WB_SBRB_BASE = 0xa000; +static const uint32_t WB_SPI_FLASH_BASE = 0xb000; +static const uint32_t WB_ETH0_MAC_BASE = 0xc000; +static const uint32_t WB_ETH1_MAC_BASE = 0xd000; +static const uint32_t WB_XB_SBRB_BASE = 0xe000; +static const uint32_t WB_ETH0_I2C_BASE = 0xf600; +static const uint32_t WB_ETH1_I2C_BASE = 0xf700; +static const uint32_t WB_DBG_UART_BASE = 0xf900; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_ZPU_SW_RST = 0; +static const uint32_t SR_ZPU_BOOT_DONE = 1; +static const uint32_t SR_ZPU_LEDS = 2; +static const uint32_t SR_ZPU_XB_LOCAL = 4; +static const uint32_t SR_ZPU_SFP_CTRL0 = 16; +static const uint32_t SR_ZPU_SFP_CTRL1 = 17; +static const uint32_t SR_ZPU_ETHINT0 = 64; +static const uint32_t SR_ZPU_ETHINT1 = 80; + +static const uint32_t SR_ZPU_SW_RST_NONE = 0x0; +static const uint32_t SR_ZPU_SW_RST_PHY = 0x1; +static const uint32_t SR_ZPU_SW_RST_RADIO = 0x2; + +/******************************************************************* + * Readback addresses + *******************************************************************/ +static const uint32_t RB_ZPU_COMPAT = 0; +static const uint32_t RB_ZPU_COUNTER = 1; +static const uint32_t RB_ZPU_SFP_STATUS0 = 2; +static const uint32_t RB_ZPU_SFP_STATUS1 = 3; +static const uint32_t RB_ZPU_ETH0_PKT_CNT = 6; +static const uint32_t RB_ZPU_ETH1_PKT_CNT = 7; + +/******************************************************************* + * Ethernet + *******************************************************************/ +static const uint32_t WB_PKT_RAM_CTRL_OFFSET = 0x1FFC; + +static const uint32_t SR_ZPU_ETHINT_FRAMER_BASE = 0; +static const uint32_t SR_ZPU_ETHINT_DISPATCHER_BASE = 8; + +//Eth framer constants +static const uint32_t ETH_FRAMER_SRC_MAC_HI = 0; +static const uint32_t ETH_FRAMER_SRC_MAC_LO = 1; +static const uint32_t ETH_FRAMER_SRC_IP_ADDR = 2; +static const uint32_t ETH_FRAMER_SRC_UDP_PORT = 3; +static const uint32_t ETH_FRAMER_DST_RAM_ADDR = 4; +static const uint32_t ETH_FRAMER_DST_IP_ADDR = 5; +static const uint32_t ETH_FRAMER_DST_UDP_MAC = 6; +static const uint32_t ETH_FRAMER_DST_MAC_LO = 7; + +/******************************************************************* + * CODEC + *******************************************************************/ +static const uint32_t CODEC_SPI_CLOCK_FREQ = 4000000; //4MHz +static const uint32_t ADF4001_SPI_CLOCK_FREQ = 200000; //200kHz + +/******************************************************************* + * UART + *******************************************************************/ +static const uint32_t DBG_UART_BAUD = 115200; + +/******************************************************************* + * Build Compatability Numbers + *******************************************************************/ +static const uint8_t PRODUCT_NUM = 0x01; +static const uint8_t COMPAT_MAJOR = 0x00; +static const uint16_t COMPAT_MINOR = 0x0000; + +static inline uint8_t get_prod_num(uint32_t compat_reg) { + return (compat_reg >> 24) & 0xFF; +} +static inline uint8_t get_compat_major(uint32_t compat_reg) { + return (compat_reg >> 16) & 0xFF; +} +static inline uint8_t get_compat_minor(uint32_t compat_reg) { + return compat_reg & 0xFFFF; +} + +#ifdef __cplusplus +}}}} //namespace +#endif +#endif /* INCLUDED_N230_FW_DEFS_H */ diff --git a/host/lib/usrp/n230/n230_fw_host_iface.h b/host/lib/usrp/n230/n230_fw_host_iface.h new file mode 100644 index 000000000..0391af0d9 --- /dev/null +++ b/host/lib/usrp/n230/n230_fw_host_iface.h @@ -0,0 +1,128 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_FW_HOST_IFACE_H +#define INCLUDED_N230_FW_HOST_IFACE_H + +#include <stdint.h> + +/*! + * Structs and constants for N230 communication between firmware and host. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------- +// Ethernet related +// +#define N230_DEFAULT_ETH0_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0xff} +#define N230_DEFAULT_ETH1_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0x33} +#define N230_DEFAULT_ETH0_IP (192 << 24 | 168 << 16 | 10 << 8 | 2 << 0) +#define N230_DEFAULT_ETH1_IP (192 << 24 | 168 << 16 | 20 << 8 | 2 << 0) +#define N230_DEFAULT_ETH0_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define N230_DEFAULT_ETH1_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define N230_DEFAULT_GATEWAY (192 << 24 | 168 << 16 | 10 << 8 | 1 << 0) + +#define N230_FW_COMMS_UDP_PORT 49152 +#define N230_FW_COMMS_CVITA_PORT 49153 +#define N230_FW_COMMS_FLASH_PROG_PORT 49154 +// +//-------------------------------------------------- + +//-------------------------------------------------- +// Memory shared with host +// +#define N230_FW_HOST_SHMEM_BASE_ADDR 0x10000 +#define N230_FW_HOST_SHMEM_RW_BASE_ADDR 0x1000C +#define N230_FW_HOST_SHMEM_NUM_WORDS (sizeof(n230_host_shared_mem_data_t)/sizeof(uint32_t)) + +#define N230_FW_HOST_SHMEM_MAX_ADDR \ + (N230_FW_HOST_SHMEM_BASE_ADDR + ((N230_FW_HOST_SHMEM_NUM_WORDS - 1) * sizeof(uint32_t))) + +#define N230_FW_HOST_SHMEM_OFFSET(member) \ + (N230_FW_HOST_SHMEM_BASE_ADDR + ((uint32_t)offsetof(n230_host_shared_mem_data_t, member))) + +//The shared memory block can only be accessed on 32-bit boundaries +typedef struct { //All fields must be 32-bit wide to avoid packing directives + //Read-Only fields (N230_FW_HOST_SHMEM_BASE_ADDR) + uint32_t fw_compat_num; //Compat number must be at offset 0 + uint32_t fw_version_hash; + uint32_t claim_status; + + //Read-Write fields (N230_FW_HOST_SHMEM_RW_BASE_ADDR) + uint32_t scratch; + uint32_t claim_time; + uint32_t claim_src; +} n230_host_shared_mem_data_t; + +typedef union +{ + uint32_t buff[N230_FW_HOST_SHMEM_NUM_WORDS]; + n230_host_shared_mem_data_t data; +} n230_host_shared_mem_t; + +#define N230_FW_PRODUCT_ID 1 +#define N230_FW_COMPAT_NUM_MAJOR 32 +#define N230_FW_COMPAT_NUM_MINOR 0 +#define N230_FW_COMPAT_NUM (((N230_FW_COMPAT_NUM_MAJOR & 0xFF) << 16) | (N230_FW_COMPAT_NUM_MINOR & 0xFFFF)) +// +//-------------------------------------------------- + +//-------------------------------------------------- +// Flash read-write interface for host +// +#define N230_FLASH_COMM_FLAGS_ACK 0x00000001 +#define N230_FLASH_COMM_FLAGS_CMD_MASK 0x00000FF0 +#define N230_FLASH_COMM_FLAGS_ERROR_MASK 0xFF000000 + +#define N230_FLASH_COMM_CMD_READ_NV_DATA 0x00000010 +#define N230_FLASH_COMM_CMD_WRITE_NV_DATA 0x00000020 +#define N230_FLASH_COMM_CMD_READ_FPGA 0x00000030 +#define N230_FLASH_COMM_CMD_WRITE_FPGA 0x00000040 +#define N230_FLASH_COMM_CMD_ERASE_FPGA 0x00000050 + +#define N230_FLASH_COMM_ERR_PKT_ERROR 0x80000000 +#define N230_FLASH_COMM_ERR_CMD_ERROR 0x40000000 +#define N230_FLASH_COMM_ERR_SIZE_ERROR 0x20000000 + +#define N230_FLASH_COMM_MAX_PAYLOAD_SIZE 128 + +typedef struct +{ + uint32_t flags; + uint32_t seq; + uint32_t offset; + uint32_t size; + uint8_t data[N230_FLASH_COMM_MAX_PAYLOAD_SIZE]; +} n230_flash_prog_t; +// +//-------------------------------------------------- + +#define N230_HW_REVISION_COMPAT 1 +#define N230_HW_REVISION_MIN 1 + + +#define N230_CLAIMER_TIMEOUT_IN_MS 2000 + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_N230_FW_HOST_IFACE_H */ diff --git a/host/lib/usrp/n230/n230_image_loader.cpp b/host/lib/usrp/n230/n230_image_loader.cpp new file mode 100644 index 000000000..9dd4a252d --- /dev/null +++ b/host/lib/usrp/n230/n230_image_loader.cpp @@ -0,0 +1,209 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <fstream> +#include <algorithm> +#include <uhd/image_loader.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include "n230_fw_host_iface.h" +#include "n230_impl.hpp" + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +struct xil_bitfile_hdr_t { + xil_bitfile_hdr_t(): + valid(false), userid(0), product(""), + fpga(""), timestamp(""), filesize(0) + {} + + bool valid; + boost::uint32_t userid; + std::string product; + std::string fpga; + std::string timestamp; + boost::uint32_t filesize; +}; + +static inline boost::uint16_t _to_uint16(boost::uint8_t* buf) { + return (static_cast<boost::uint16_t>(buf[0]) << 8) | + (static_cast<boost::uint16_t>(buf[1]) << 0); +} + +static inline boost::uint32_t _to_uint32(boost::uint8_t* buf) { + return (static_cast<boost::uint32_t>(buf[0]) << 24) | + (static_cast<boost::uint32_t>(buf[1]) << 16) | + (static_cast<boost::uint32_t>(buf[2]) << 8) | + (static_cast<boost::uint32_t>(buf[3]) << 0); +} + +static void _parse_bitfile_header(const std::string& filepath, xil_bitfile_hdr_t& hdr) { + // Read header into memory + std::ifstream img_file(filepath.c_str(), std::ios::binary); + static const size_t MAX_HDR_SIZE = 1024; + boost::scoped_array<char> hdr_buf(new char[MAX_HDR_SIZE]); + img_file.seekg(0, std::ios::beg); + img_file.read(hdr_buf.get(), MAX_HDR_SIZE); + img_file.close(); + + //Parse header + size_t ptr = 0; + boost::uint8_t* buf = reinterpret_cast<boost::uint8_t*>(hdr_buf.get()); //Shortcut + + boost::uint8_t signature[10] = {0x00, 0x09, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0}; + if (memcmp(buf, signature, 10) == 0) { //Validate signature + ptr += _to_uint16(buf + ptr) + 2; + ptr += _to_uint16(buf + ptr) + 1; + + std::string fields[4]; + for (size_t i = 0; i < 4; i++) { + size_t key = buf[ptr++] - 'a'; + boost::uint16_t len = _to_uint16(buf + ptr); ptr += 2; + fields[key] = std::string(reinterpret_cast<char*>(buf + ptr), size_t(len)); ptr += len; + } + + hdr.filesize = _to_uint32(buf + ++ptr); ptr += 4; + hdr.fpga = fields[1]; + hdr.timestamp = fields[2] + std::string(" ") + fields[3]; + + std::vector<std::string> tokens; + boost::split(tokens, fields[0], boost::is_any_of(";")); + if (tokens.size() == 3) { + hdr.product = tokens[0]; + std::vector<std::string> uidtokens; + boost::split(uidtokens, tokens[1], boost::is_any_of("=")); + if (uidtokens.size() == 2 and uidtokens[0] == "UserID") { + std::stringstream stream; + stream << uidtokens[1]; + stream >> std::hex >> hdr.userid; + hdr.valid = true; + } + } + } +} + +static size_t _send_and_recv( + udp_simple::sptr xport, + n230_flash_prog_t& out, n230_flash_prog_t& in) +{ + static boost::uint32_t seqno = 0; + out.seq = htonx<boost::uint32_t>(++seqno); + xport->send(boost::asio::buffer(&out, sizeof(n230_flash_prog_t))); + size_t len = xport->recv(boost::asio::buffer(&in, udp_simple::mtu), 0.5); + if (len != sizeof(n230_flash_prog_t) or ntohx<boost::uint32_t>(in.seq) != seqno) { + throw uhd::io_error("Error communicating with the device."); + } + return len; +} + + +static bool n230_image_loader(const image_loader::image_loader_args_t &loader_args){ + // Run discovery routine and ensure that exactly one N230 is specified + device_addrs_t devs = usrp::n230::n230_impl::n230_find(loader_args.args); + if (devs.size() == 0 or !loader_args.load_fpga) return false; + if (devs.size() > 1) { + throw uhd::runtime_error("Multiple devices match the specified args. To avoid accidentally updating the " + "wrong device, please narrow the search by specifying a unique \"addr\" argument."); + } + device_addr_t dev = devs[0]; + + // Sanity check the specified bitfile + std::string fpga_img_path = loader_args.fpga_path; + bool fpga_path_specified = !loader_args.fpga_path.empty(); + if (not fpga_path_specified) { + fpga_img_path = ( + fs::path(uhd::get_pkg_path()) / "share" / "uhd" / "images" / "usrp_n230_fpga.bit" + ).string(); + } + + if (not boost::filesystem::exists(fpga_img_path)) { + if (fpga_path_specified) { + throw uhd::runtime_error(str(boost::format("The file \"%s\" does not exist.") % fpga_img_path)); + } else { + throw uhd::runtime_error(str(boost::format( + "Could not find the default FPGA image: %s.\n" + "Either specify the --fpga-path argument or download the latest prebuilt images:\n" + "%s\n") + % fpga_img_path % print_utility_error("uhd_images_downloader.py"))); + } + } + xil_bitfile_hdr_t hdr; + _parse_bitfile_header(fpga_img_path, hdr); + + // Create a UDP communication link + udp_simple::sptr udp_xport = + udp_simple::make_connected(dev["addr"],BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT)); + + if (hdr.valid and hdr.product == "n230") { + if (hdr.userid != 0x5AFE0000) { + std::cout << boost::format("Unit: USRP N230 (%s, %s)\n-- FPGA Image: %s\n") + % dev["addr"] % dev["serial"] % fpga_img_path; + + // Write image + std::ifstream image(fpga_img_path.c_str(), std::ios::binary); + size_t image_size = boost::filesystem::file_size(fpga_img_path); + + static const size_t SECTOR_SIZE = 65536; + static const size_t IMAGE_BASE = 0x400000; + + n230_flash_prog_t out, in; + size_t bytes_written = 0; + while (bytes_written < image_size) { + size_t payload_size = std::min<size_t>(image_size - bytes_written, N230_FLASH_COMM_MAX_PAYLOAD_SIZE); + if (bytes_written % SECTOR_SIZE == 0) { + out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA); + out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE); + out.size = htonx<boost::uint32_t>(payload_size); + _send_and_recv(udp_xport, out, in); + } + out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_FPGA); + out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE); + out.size = htonx<boost::uint32_t>(payload_size); + image.read((char*)out.data, payload_size); + _send_and_recv(udp_xport, out, in); + bytes_written += ntohx<boost::uint32_t>(in.size); + std::cout << boost::format("\r-- Loading FPGA image: %d%%") + % (int(double(bytes_written) / double(image_size) * 100.0)) + << std::flush; + } + std::cout << std::endl << "FPGA image loaded successfully." << std::endl; + std::cout << std::endl << "Power-cycle the device to run the image." << std::endl; + return true; + } else { + throw uhd::runtime_error("This utility cannot burn a failsafe image!"); + } + } else { + throw uhd::runtime_error(str(boost::format("The file at path \"%s\" is not a valid USRP N230 FPGA image.") + % fpga_img_path)); + } +} + +UHD_STATIC_BLOCK(register_n230_image_loader){ + std::string recovery_instructions = "Aborting. Your USRP N230 device will likely boot in safe mode.\n" + "Please re-run this command with the additional \"safe_mode\" device argument\n" + "to recover your device."; + + image_loader::register_image_loader("n230", n230_image_loader, recovery_instructions); +} diff --git a/host/lib/usrp/n230/n230_impl.cpp b/host/lib/usrp/n230/n230_impl.cpp new file mode 100644 index 000000000..5e8aa37b7 --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.cpp @@ -0,0 +1,591 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_impl.hpp" + +#include "usrp3_fw_ctrl_iface.hpp" +#include "validate_subdev_spec.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/make_shared.hpp> + +#include "../common/fw_comm_protocol.h" +#include "n230_defaults.h" +#include "n230_fpga_defs.h" +#include "n230_fw_defs.h" +#include "n230_fw_host_iface.h" + +namespace uhd { namespace usrp { namespace n230 { + +using namespace uhd::transport; +namespace asio = boost::asio; + +//---------------------------------------------------------- +// Static device registration with framework +//---------------------------------------------------------- +UHD_STATIC_BLOCK(register_n230_device) +{ + device::register_device(&n230_impl::n230_find, &n230_impl::n230_make, device::USRP); +} + +//---------------------------------------------------------- +// Device discovery +//---------------------------------------------------------- +uhd::device_addrs_t n230_impl::n230_find(const uhd::device_addr_t &multi_dev_hint) +{ + //handle the multi-device discovery + device_addrs_t hints = separate_device_addr(multi_dev_hint); + if (hints.size() > 1){ + device_addrs_t found_devices; + std::string error_msg; + BOOST_FOREACH(const device_addr_t &hint_i, hints){ + device_addrs_t found_devices_i = n230_find(hint_i); + if (found_devices_i.size() != 1) error_msg += str(boost::format( + "Could not resolve device hint \"%s\" to a single device." + ) % hint_i.to_string()); + else found_devices.push_back(found_devices_i[0]); + } + if (found_devices.empty()) return device_addrs_t(); + if (not error_msg.empty()) throw uhd::value_error(error_msg); + return device_addrs_t(1, combine_device_addrs(found_devices)); + } + + //initialize the hint for a single device case + UHD_ASSERT_THROW(hints.size() <= 1); + hints.resize(1); //in case it was empty + device_addr_t hint = hints[0]; + device_addrs_t n230_addrs; + + //return an empty list of addresses when type is set to non-n230 + if (hint.has_key("type") and hint["type"] != "n230") return n230_addrs; + + //Return an empty list of addresses when a resource is specified, + //since a resource is intended for a different, non-networked, device. + if (hint.has_key("resource")) return n230_addrs; + + //if no address was specified, send a broadcast on each interface + if (not hint.has_key("addr")) { + BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()) { + //avoid the loopback device + if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + + //create a new hint with this broadcast address + device_addr_t new_hint = hint; + new_hint["addr"] = if_addrs.bcast; + + //call discover with the new hint and append results + device_addrs_t new_n230_addrs = n230_find(new_hint); + n230_addrs.insert(n230_addrs.begin(), + new_n230_addrs.begin(), new_n230_addrs.end() + ); + } + return n230_addrs; + } + + std::vector<std::string> discovered_addrs = + usrp3::usrp3_fw_ctrl_iface::discover_devices( + hint["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); + + BOOST_FOREACH(const std::string& addr, discovered_addrs) + { + device_addr_t new_addr; + new_addr["type"] = "n230"; + new_addr["addr"] = addr; + + //Attempt a simple 2-way communication with a connected socket. + //Reason: Although the USRP will respond the broadcast above, + //we may not be able to communicate directly (non-broadcast). + udp_simple::sptr ctrl_xport = udp_simple::make_connected(new_addr["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)); + + //Corner case: If two devices have the same IP but different MAC + //addresses and are used back-to-back it takes a while for ARP tables + //on the host to update in which period brodcasts will respond but + //connected communication can fail. Retry the following call to allow + //the stack to update + size_t first_conn_retries = 10; + usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl; + while (first_conn_retries > 0) { + try { + fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make(ctrl_xport, N230_FW_PRODUCT_ID, false /*verbose*/); + break; + } catch (uhd::io_error& ex) { + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + first_conn_retries--; + } + } + if (first_conn_retries > 0) { + uint32_t compat_reg = fw_ctrl->peek32(fw::reg_addr(fw::WB_SBRB_BASE, fw::RB_ZPU_COMPAT)); + if (fw::get_prod_num(compat_reg) == fw::PRODUCT_NUM) { + if (!n230_resource_manager::is_device_claimed(fw_ctrl)) { + //Not claimed by another process or host + try { + //Try to read the EEPROM to get the name and serial + n230_eeprom_manager eeprom_mgr(new_addr["addr"]); + const mboard_eeprom_t& eeprom = eeprom_mgr.get_mb_eeprom(); + new_addr["name"] = eeprom["name"]; + new_addr["serial"] = eeprom["serial"]; + } + catch(const std::exception &) + { + //set these values as empty string so the device may still be found + //and the filter's below can still operate on the discovered device + new_addr["name"] = ""; + new_addr["serial"] = ""; + } + //filter the discovered device below by matching optional keys + if ((not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"])) + { + n230_addrs.push_back(new_addr); + } + } + } + } + } + + return n230_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +device::sptr n230_impl::n230_make(const device_addr_t &device_addr) +{ + return device::sptr(new n230_impl(device_addr)); +} + +/*********************************************************************** + * n230_impl::ctor + **********************************************************************/ +n230_impl::n230_impl(const uhd::device_addr_t& dev_addr) +{ + UHD_MSG(status) << "N230 initialization sequence..." << std::endl; + _dev_args.parse(dev_addr); + _tree = uhd::property_tree::make(); + + //TODO: Only supports one motherboard per device class. + const fs_path mb_path = "/mboards/0"; + + //Initialize addresses + std::vector<std::string> ip_addrs(1, dev_addr["addr"]); + if (dev_addr.has_key("secondary-addr")) { + ip_addrs.push_back(dev_addr["secondary-addr"]); + } + + //Read EEPROM and perform version checks before talking to HW + _eeprom_mgr = boost::make_shared<n230_eeprom_manager>(ip_addrs[0]); + const mboard_eeprom_t& mb_eeprom = _eeprom_mgr->get_mb_eeprom(); + bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom"); + if (recover_mb_eeprom) { + UHD_MSG(warning) << "UHD is operating in EEPROM Recovery Mode which disables hardware version " + "checks.\nOperating in this mode may cause hardware damage and unstable " + "radio performance!"<< std::endl; + } + boost::uint16_t hw_rev = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision"]); + boost::uint16_t hw_rev_compat = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision_compat"]); + if (not recover_mb_eeprom) { + if (hw_rev_compat > N230_HW_REVISION_COMPAT) { + throw uhd::runtime_error(str(boost::format( + "Hardware is too new for this software. Please upgrade to a driver that supports hardware revision %d.") + % hw_rev)); + } + } + + //Initialize all subsystems + _resource_mgr = boost::make_shared<n230_resource_manager>(ip_addrs, _dev_args.get_safe_mode()); + _stream_mgr = boost::make_shared<n230_stream_manager>(_dev_args, _resource_mgr, _tree); + + //Build property tree + _initialize_property_tree(mb_path); + + //Debug loopback mode + switch(_dev_args.get_loopback_mode()) { + case n230_device_args_t::LOOPBACK_RADIO: + UHD_MSG(status) << "DEBUG: Running in TX->RX Radio loopback mode.\n"; + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_RADIO); + break; + case n230_device_args_t::LOOPBACK_CODEC: + UHD_MSG(status) << "DEBUG: Running in TX->RX CODEC loopback mode.\n"; + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_CODEC); + break; + default: + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_DISABLED); + break; + } +} + +/*********************************************************************** + * n230_impl::dtor + **********************************************************************/ +n230_impl::~n230_impl() +{ + _stream_mgr.reset(); + _eeprom_mgr.reset(); + _resource_mgr.reset(); + _tree.reset(); +} + +/*********************************************************************** + * n230_impl::get_rx_stream + **********************************************************************/ +rx_streamer::sptr n230_impl::get_rx_stream(const uhd::stream_args_t &args) +{ + return _stream_mgr->get_rx_stream(args); +} + +/*********************************************************************** + * n230_impl::get_tx_stream + **********************************************************************/ +tx_streamer::sptr n230_impl::get_tx_stream(const uhd::stream_args_t &args) +{ + return _stream_mgr->get_tx_stream(args); +} + +/*********************************************************************** + * n230_impl::recv_async_msg + **********************************************************************/ +bool n230_impl::recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout) +{ + return _stream_mgr->recv_async_msg(async_metadata, timeout); +} + +/*********************************************************************** + * _initialize_property_tree + **********************************************************************/ +void n230_impl::_initialize_property_tree(const fs_path& mb_path) +{ + //------------------------------------------------------------------ + // General info + //------------------------------------------------------------------ + _tree->create<std::string>("/name").set("N230 Device"); + + _tree->create<std::string>(mb_path / "name").set("N230"); + _tree->create<std::string>(mb_path / "codename").set("N230"); + _tree->create<std::string>(mb_path / "dboards").set("none"); //No dboards. + + _tree->create<std::string>(mb_path / "fw_version").set(str(boost::format("%u.%u") + % _resource_mgr->get_version(FIRMWARE, COMPAT_MAJOR) + % _resource_mgr->get_version(FIRMWARE, COMPAT_MINOR))); + _tree->create<std::string>(mb_path / "fw_version_hash").set(str(boost::format("%s") + % _resource_mgr->get_version_hash(FIRMWARE))); + _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") + % _resource_mgr->get_version(FPGA, COMPAT_MAJOR) + % _resource_mgr->get_version(FPGA, COMPAT_MINOR))); + _tree->create<std::string>(mb_path / "fpga_version_hash").set(str(boost::format("%s") + % _resource_mgr->get_version_hash(FPGA))); + + _tree->create<double>(mb_path / "link_max_rate").set(_resource_mgr->get_max_link_rate()); + + //------------------------------------------------------------------ + // EEPROM + //------------------------------------------------------------------ + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(_eeprom_mgr->get_mb_eeprom()) //Set first... + .add_coerced_subscriber(boost::bind(&n230_eeprom_manager::write_mb_eeprom, _eeprom_mgr, _1)); //..then enable writer + + //------------------------------------------------------------------ + // Create codec nodes + //------------------------------------------------------------------ + const fs_path rx_codec_path = mb_path / ("rx_codecs") / "A"; + _tree->create<std::string>(rx_codec_path / "name") + .set("N230 RX dual ADC"); + _tree->create<int>(rx_codec_path / "gains"); //Empty because gains are in frontend + + const fs_path tx_codec_path = mb_path / ("tx_codecs") / "A"; + _tree->create<std::string>(tx_codec_path / "name") + .set("N230 TX dual DAC"); + _tree->create<int>(tx_codec_path / "gains"); //Empty because gains are in frontend + + //------------------------------------------------------------------ + // Create clock and time control nodes + //------------------------------------------------------------------ + _tree->create<double>(mb_path / "tick_rate") + .set_coercer(boost::bind(&n230_clk_pps_ctrl::set_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set_publisher(boost::bind(&n230_clk_pps_ctrl::get_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr())) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_tick_rate, _stream_mgr, _1)); + + //Register time now and pps onto available radio cores + //radio0 is the master + _tree->create<time_spec_t>(mb_path / "time" / "cmd"); + _tree->create<time_spec_t>(mb_path / "time" / "now") + .set_publisher(boost::bind(&time_core_3000::get_time_now, _resource_mgr->get_radio(0).time)); + _tree->create<time_spec_t>(mb_path / "time" / "pps") + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _resource_mgr->get_radio(0).time)); + + //Setup time source props + _tree->create<std::string>(mb_path / "time_source" / "value") + .add_coerced_subscriber(boost::bind(&n230_impl::_check_time_source, this, _1)) + .add_coerced_subscriber(boost::bind(&n230_clk_pps_ctrl::set_pps_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set(n230::DEFAULT_TIME_SRC); + static const std::vector<std::string> time_sources = boost::assign::list_of("none")("external")("gpsdo"); + _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options") + .set(time_sources); + + //Setup reference source props + _tree->create<std::string>(mb_path / "clock_source" / "value") + .add_coerced_subscriber(boost::bind(&n230_impl::_check_clock_source, this, _1)) + .add_coerced_subscriber(boost::bind(&n230_clk_pps_ctrl::set_clock_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set(n230::DEFAULT_CLOCK_SRC); + static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("gpsdo"); + _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options") + .set(clock_sources); + _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") + .set_publisher(boost::bind(&n230_clk_pps_ctrl::get_ref_locked, _resource_mgr->get_clk_pps_ctrl_sptr())); + + //------------------------------------------------------------------ + // Create frontend mapping + //------------------------------------------------------------------ + _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") + .set(subdev_spec_t()) + .add_coerced_subscriber(boost::bind(&n230_impl::_update_rx_subdev_spec, this, _1)); + _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") + .set(subdev_spec_t()) + .add_coerced_subscriber(boost::bind(&n230_impl::_update_tx_subdev_spec, this, _1)); + + //------------------------------------------------------------------ + // Create a fake dboard to put frontends in + //------------------------------------------------------------------ + //For completeness we give it a fake EEPROM as well + dboard_eeprom_t db_eeprom; //Default state: ID is 0xffff, Version and serial empty + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom").set(db_eeprom); + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom").set(db_eeprom); + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "gdb_eeprom").set(db_eeprom); + + //------------------------------------------------------------------ + // Create radio specific nodes + //------------------------------------------------------------------ + for (size_t radio_instance = 0; radio_instance < fpga::NUM_RADIOS; radio_instance++) { + _initialize_radio_properties(mb_path, radio_instance); + } + //Update tick rate on newly created radio objects + _tree->access<double>(mb_path / "tick_rate").set(_dev_args.get_master_clock_rate()); + + //------------------------------------------------------------------ + // Initialize subdev specs + //------------------------------------------------------------------ + subdev_spec_t rx_spec, tx_spec; + BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "rx_frontends")) + { + rx_spec.push_back(subdev_spec_pair_t("A", fe)); + } + BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "tx_frontends")) + { + tx_spec.push_back(subdev_spec_pair_t("A", fe)); + } + _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec); + _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); + + //------------------------------------------------------------------ + // MiniSAS GPIO + //------------------------------------------------------------------ + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "DDR") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_DDR, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "DDR") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_DDR, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "OUT") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_OUT, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "OUT") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_OUT, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(0))); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(1))); + + //------------------------------------------------------------------ + // GPSDO sensors + //------------------------------------------------------------------ + if (_resource_mgr->is_gpsdo_present()) { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + BOOST_FOREACH(const std::string &name, gps_ctrl->get_sensors()) + { + _tree->create<sensor_value_t>(mb_path / "sensors" / name) + .set_publisher(boost::bind(&gps_ctrl::get_sensor, gps_ctrl, name)); + } + } +} + +/*********************************************************************** + * _initialize_radio_properties + **********************************************************************/ +void n230_impl::_initialize_radio_properties(const fs_path& mb_path, size_t instance) +{ + radio_resource_t& perif = _resource_mgr->get_radio(instance); + + //Time + _tree->access<time_spec_t>(mb_path / "time" / "cmd") + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); + _tree->access<time_spec_t>(mb_path / "time" / "now") + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_now, perif.time, _1)); + _tree->access<time_spec_t>(mb_path / "time" / "pps") + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, perif.time, _1)); + + //RX DSP + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); + const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % instance); + _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range") + .set_publisher(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); + _tree->create<double>(rx_dsp_path / "rate" / "value") + .set_coercer(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1)) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_rx_samp_rate, _stream_mgr, instance, _1)) + .set(n230::DEFAULT_RX_SAMP_RATE); + _tree->create<double>(rx_dsp_path / "freq" / "value") + .set_coercer(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) + .set(n230::DEFAULT_DDC_FREQ); + _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range") + .set_publisher(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + + //TX DSP + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); + const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % instance); + _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range") + .set_publisher(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); + _tree->create<double>(tx_dsp_path / "rate" / "value") + .set_coercer(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1)) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_tx_samp_rate, _stream_mgr, instance, _1)) + .set(n230::DEFAULT_TX_SAMP_RATE); + _tree->create<double>(tx_dsp_path / "freq" / "value") + .set_coercer(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) + .set(n230::DEFAULT_DUC_FREQ); + _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range") + .set_publisher(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc)); + + //RF Frontend Interfacing + static const std::vector<direction_t> data_directions = boost::assign::list_of(RX_DIRECTION)(TX_DIRECTION); + BOOST_FOREACH(direction_t direction, data_directions) { + const std::string dir_str = (direction == RX_DIRECTION) ? "rx" : "tx"; + const std::string key = boost::to_upper_copy(dir_str) + str(boost::format("%u") % (instance + 1)); + const fs_path rf_fe_path = mb_path / "dboards" / "A" / (dir_str + "_frontends") / ((instance==0)?"A":"B"); + + //CODEC subtree + _resource_mgr->get_codec_mgr().populate_frontend_subtree(_tree->subtree(rf_fe_path), key, direction); + + //User settings + _tree->create<uhd::wb_iface::sptr>(rf_fe_path / "user_settings" / "iface") + .set(perif.user_settings); + + //Setup antenna stuff + if (key[0] == 'R') { + static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2"); + _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options") + .set(ants); + _tree->create<std::string>(rf_fe_path / "antenna" / "value") + .add_coerced_subscriber(boost::bind(&n230_frontend_ctrl::set_antenna_sel, _resource_mgr->get_frontend_ctrl_sptr(), instance, _1)) + .set("RX2"); + } + if (key[0] == 'T') { + static const std::vector<std::string> ants(1, "TX/RX"); + _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options") + .set(ants); + _tree->create<std::string>(rf_fe_path / "antenna" / "value") + .set("TX/RX"); + } + } +} + +void n230_impl::_update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +{ + //sanity checking + if (spec.size()) validate_subdev_spec(_tree, spec, "rx"); + UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS); + + if (spec.size() > 0) { + UHD_ASSERT_THROW(spec[0].db_name == "A"); + UHD_ASSERT_THROW(spec[0].sd_name == "A"); + } + if (spec.size() > 1) { + //TODO we can support swapping at a later date, only this combo is supported + UHD_ASSERT_THROW(spec[1].db_name == "A"); + UHD_ASSERT_THROW(spec[1].sd_name == "B"); + } + + _stream_mgr->update_stream_states(); +} + +void n230_impl::_update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +{ + //sanity checking + if (spec.size()) validate_subdev_spec(_tree, spec, "tx"); + UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS); + + if (spec.size() > 0) { + UHD_ASSERT_THROW(spec[0].db_name == "A"); + UHD_ASSERT_THROW(spec[0].sd_name == "A"); + } + if (spec.size() > 1) { + //TODO we can support swapping at a later date, only this combo is supported + UHD_ASSERT_THROW(spec[1].db_name == "A"); + UHD_ASSERT_THROW(spec[1].sd_name == "B"); + } + + _stream_mgr->update_stream_states(); +} + +void n230_impl::_check_time_source(std::string source) +{ + if (source == "gpsdo") + { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + if (not (gps_ctrl and gps_ctrl->gps_detected())) + throw uhd::runtime_error("GPSDO time source not available"); + } +} + +void n230_impl::_check_clock_source(std::string source) +{ + if (source == "gpsdo") + { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + if (not (gps_ctrl and gps_ctrl->gps_detected())) + throw uhd::runtime_error("GPSDO clock source not available"); + } +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_impl.hpp b/host/lib/usrp/n230/n230_impl.hpp new file mode 100644 index 000000000..b644dd8a3 --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.hpp @@ -0,0 +1,81 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_IMPL_HPP +#define INCLUDED_N230_IMPL_HPP + +#include <uhd/property_tree.hpp> +#include <uhd/device.hpp> +#include <uhd/usrp/subdev_spec.hpp> + +#include "n230_device_args.hpp" +#include "n230_eeprom_manager.hpp" +#include "n230_resource_manager.hpp" +#include "n230_stream_manager.hpp" +#include "recv_packet_demuxer_3000.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_impl : public uhd::device +{ +public: //Functions + // ctor and dtor + n230_impl(const uhd::device_addr_t& device_addr); + virtual ~n230_impl(void); + + //--------------------------------------------------------------------- + // uhd::device interface + // + static sptr make(const uhd::device_addr_t &hint, size_t which = 0); + + //! Make a new receive streamer from the streamer arguments + virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + + //! Make a new transmit streamer from the streamer arguments + virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + + //!Receive and asynchronous message from the device. + virtual bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout = 0.1); + + //!Registration methods the discovery and factory system. + //[static void register_device(const find_t &find, const make_t &make)] + static uhd::device_addrs_t n230_find(const uhd::device_addr_t &hint); + static uhd::device::sptr n230_make(const uhd::device_addr_t &device_addr); + // + //--------------------------------------------------------------------- + + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + +private: //Functions + void _initialize_property_tree(const fs_path& mb_path); + void _initialize_radio_properties(const fs_path& mb_path, size_t instance); + + void _update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void _update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void _check_time_source(std::string); + void _check_clock_source(std::string); + +private: //Classes and Members + n230_device_args_t _dev_args; + boost::shared_ptr<n230_resource_manager> _resource_mgr; + boost::shared_ptr<n230_eeprom_manager> _eeprom_mgr; + boost::shared_ptr<n230_stream_manager> _stream_mgr; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_IMPL_HPP */ diff --git a/host/lib/usrp/n230/n230_resource_manager.cpp b/host/lib/usrp/n230/n230_resource_manager.cpp new file mode 100644 index 000000000..f13dd0b33 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.cpp @@ -0,0 +1,569 @@ +// +// Copyright 2013 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_resource_manager.hpp" + +#include "usrp3_fw_ctrl_iface.hpp" +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/platform.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/functional/hash.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/make_shared.hpp> +#include "n230_fw_defs.h" +#include "n230_fw_host_iface.h" + +#define IF_DATA_I_MASK 0xFFF00000 +#define IF_DATA_Q_MASK 0x0000FFF0 + +namespace uhd { namespace usrp { namespace n230 { + +//Constants +static const uint8_t N230_HOST_SRC_ADDR_ETH0 = 0; +static const uint8_t N230_HOST_SRC_ADDR_ETH1 = 1; +static const uint8_t N230_HOST_DEST_ADDR = 2; + +static const uint8_t N230_ETH0_IFACE_ID = 0; +static const uint8_t N230_ETH1_IFACE_ID = 1; + +class n230_ad9361_client_t : public ad9361_params { +public: + ~n230_ad9361_client_t() {} + double get_band_edge(frequency_band_t band) { + switch (band) { + case AD9361_RX_BAND0: return 2.2e9; + case AD9361_RX_BAND1: return 4.0e9; + case AD9361_TX_BAND0: return 2.5e9; + default: return 0; + } + } + clocking_mode_t get_clocking_mode() { + return AD9361_XTAL_N_CLK_PATH; + } + digital_interface_mode_t get_digital_interface_mode() { + return AD9361_DDR_FDD_LVDS; + } + digital_interface_delays_t get_digital_interface_timing() { + digital_interface_delays_t delays; + delays.rx_clk_delay = 0; + delays.rx_data_delay = 0; + delays.tx_clk_delay = 0; + delays.tx_data_delay = 2; + return delays; + } +}; + +n230_resource_manager::n230_resource_manager( + const std::vector<std::string> ip_addrs, + const bool safe_mode +) : + _safe_mode(safe_mode), + _last_host_enpoint(0) +{ + if (_safe_mode) UHD_MSG(warning) << "Initializing device in safe mode\n"; + UHD_MSG(status) << "Setup basic communication...\n"; + + //Discover ethernet interfaces + bool dual_eth_expected = (ip_addrs.size() > 1); + BOOST_FOREACH(const std::string& addr, ip_addrs) { + n230_eth_conn_t conn_iface; + conn_iface.ip_addr = addr; + + boost::uint32_t iface_id = 0xFFFFFFFF; + try { + iface_id = usrp3::usrp3_fw_ctrl_iface::get_iface_id( + conn_iface.ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); + } catch (uhd::io_error&) { + throw uhd::io_error(str(boost::format( + "Could not communicate with the device over address %s") % + conn_iface.ip_addr)); + } + switch (iface_id) { + case N230_ETH0_IFACE_ID: conn_iface.type = ETH0; break; + case N230_ETH1_IFACE_ID: conn_iface.type = ETH1; break; + default: { + if (dual_eth_expected) { + throw uhd::runtime_error("N230 Initialization Error: Could not detect ethernet port number."); + } else { + //For backwards compatibility, if only one port is specified, assume that a detection + //failure means that the device does not support dual-ethernet behavior. + conn_iface.type = ETH0; break; + } + } + } + _eth_conns.push_back(conn_iface); + } + if (_eth_conns.size() < 1) { + throw uhd::runtime_error("N230 Initialization Error: No eth interfaces specified.)"); + } + + //Create firmware communication interface + _fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make( + transport::udp_simple::make_connected( + _get_conn(PRI_ETH).ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)), N230_FW_PRODUCT_ID); + if (_fw_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create n230_ctrl_iface.)"); + } + _check_fw_compat(); + + //Start the device claimer + _claimer_task = uhd::task::make(boost::bind(&n230_resource_manager::_claimer_loop, this)); + + //Create common settings interface + const sid_t core_sid = _generate_sid(CORE, _get_conn(PRI_ETH).type); + + transport::udp_zero_copy::buff_params dummy_out_params; + transport::zero_copy_if::sptr core_xport = + _create_transport(_get_conn(PRI_ETH), core_sid, device_addr_t(), dummy_out_params); + if (core_xport.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create settings transport.)"); + } + _core_ctrl = radio_ctrl_core_3000::make( + fpga::CVITA_BIG_ENDIAN, core_xport, core_xport, core_sid.get()); + if (_core_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create settings ctrl.)"); + } + _check_fpga_compat(); + + UHD_MSG(status) << boost::format("Version signatures... Firmware:%s FPGA:%s...\n") + % _fw_version.get_hash_str() % _fpga_version.get_hash_str(); + + _core_radio_ctrl_reg.initialize(*_core_ctrl, true /*flush*/); + _core_misc_reg.initialize(*_core_ctrl, true /*flush*/); + _core_pps_sel_reg.initialize(*_core_ctrl, true /*flush*/); + _core_status_reg.initialize(*_core_ctrl); + + //Create common SPI interface + _core_spi_ctrl = n230_core_spi_core::make(_core_ctrl); + if (_core_spi_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create SPI ctrl.)"); + } + + //Create AD9361 interface + UHD_MSG(status) << "Initializing CODEC...\n"; + _codec_ctrl = ad9361_ctrl::make_spi( + boost::make_shared<n230_ad9361_client_t>(), _core_spi_ctrl, fpga::AD9361_SPI_SLAVE_NUM); + if (_codec_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create Catalina ctrl.)"); + } + _codec_ctrl->set_clock_rate(fpga::CODEC_DEFAULT_CLK_RATE); + _codec_mgr = ad936x_manager::make(_codec_ctrl, fpga::NUM_RADIOS); + _codec_mgr->init_codec(); + + //Create AD4001 interface + _ref_pll_ctrl = boost::make_shared<n230_ref_pll_ctrl>(_core_spi_ctrl); + if (_ref_pll_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create ADF4001 ctrl.)"); + } + + //Reset SERDES interface and synchronize to frame sync from AD9361 + _reset_codec_digital_interface(); + + std::vector<time_core_3000::sptr> time_cores; + std::vector<gpio_atr::gpio_atr_3000::sptr> gpio_cores; + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + _initialize_radio(i); + time_cores.push_back(_radios[i].time); + gpio_cores.push_back(_radios[i].gpio_atr); + } + + //Create clock and PPS control interface + _clk_pps_ctrl = n230_clk_pps_ctrl::make( + _codec_ctrl, _ref_pll_ctrl, _core_misc_reg, _core_pps_sel_reg, _core_status_reg, time_cores); + if (_clk_pps_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create clock and PPS ctrl.)"); + } + + //Create front-end control interface + _frontend_ctrl = n230_frontend_ctrl::make(_core_ctrl, _core_misc_reg, _codec_ctrl, gpio_cores); + if (_frontend_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create front-end ctrl.)"); + } + + //Create miniSAS GPIO interfaces + _ms0_gpio = gpio_atr::gpio_atr_3000::make( + _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS0_GPIO), fpga::rb_addr(fpga::RB_CORE_MS0_GPIO)); + _ms0_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _ms1_gpio = gpio_atr::gpio_atr_3000::make( + _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS1_GPIO), fpga::rb_addr(fpga::RB_CORE_MS1_GPIO)); + _ms1_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + + //Create GPSDO interface + if (_core_status_reg.read(fpga::core_status_reg_t::GPSDO_STATUS) != fpga::GPSDO_ST_ABSENT) { + UHD_MSG(status) << "Detecting GPSDO.... " << std::flush; + try { + const sid_t gps_uart_sid = _generate_sid(GPS_UART, _get_conn(PRI_ETH).type); + transport::zero_copy_if::sptr gps_uart_xport = + _create_transport(_get_conn(PRI_ETH), gps_uart_sid, device_addr_t(), dummy_out_params); + _gps_uart = n230_uart::make(gps_uart_xport, uhd::htonx(gps_uart_sid.get())); + _gps_uart->set_baud_divider(fpga::BUS_CLK_RATE/fpga::GPSDO_UART_BAUDRATE); + _gps_uart->write_uart("\n"); //cause the baud and response to be setup + boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for a little propagation + _gps_ctrl = gps_ctrl::make(_gps_uart); + } catch(std::exception &e) { + UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; + } + if (not is_gpsdo_present()) { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_GPSDO_ST), fpga::GPSDO_ST_ABSENT); + } + } + + //Perform data self-tests + _frontend_ctrl->set_stream_state(TXRX_STREAMING, TXRX_STREAMING); + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + _frontend_ctrl->set_self_test_mode(LOOPBACK_RADIO); + bool radio_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); + if (!radio_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Data loopback test failed.)"); + } + + _frontend_ctrl->set_self_test_mode(LOOPBACK_CODEC); + bool codec_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); + if (!codec_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Codec loopback test failed.)"); + } + } + _frontend_ctrl->set_self_test_mode(LOOPBACK_DISABLED); + _frontend_ctrl->set_stream_state(NONE_STREAMING, NONE_STREAMING); +} + +n230_resource_manager::~n230_resource_manager() +{ + _claimer_task.reset(); + { //Critical section + boost::mutex::scoped_lock(_claimer_mutex); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), 0); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), 0); + } +} + +transport::zero_copy_if::sptr n230_resource_manager::create_transport( + n230_data_dir_t direction, + size_t radio_instance, + const device_addr_t ¶ms, + sid_t& sid_pair, + transport::udp_zero_copy::buff_params& buff_out_params) +{ + const n230_eth_conn_t& conn = _get_conn((radio_instance==1)?SEC_ETH:PRI_ETH); + const sid_t temp_sid_pair = + _generate_sid(direction==RX_DATA?RADIO_RX_DATA:RADIO_TX_DATA, conn.type, radio_instance); + transport::zero_copy_if::sptr xport = _create_transport(conn, temp_sid_pair, params, buff_out_params); + if (xport.get() == NULL) { + throw uhd::runtime_error("N230 Create Data Transport: Could not create data transport.)"); + } else { + sid_pair = temp_sid_pair; + } + return xport; +} + +bool n230_resource_manager::is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl) +{ + boost::mutex::scoped_lock(_claimer_mutex); + + //If timed out then device is definitely unclaimed + if (fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_status)) == 0) + return false; + + //otherwise check claim src to determine if another thread with the same src has claimed the device + return fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_src)) != get_process_hash(); +} + +void n230_resource_manager::_claimer_loop() +{ + { //Critical section + boost::mutex::scoped_lock(_claimer_mutex); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), time(NULL)); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), get_process_hash()); + } + boost::this_thread::sleep(boost::posix_time::milliseconds(N230_CLAIMER_TIMEOUT_IN_MS / 2)); +} + +void n230_resource_manager::_initialize_radio(size_t instance) +{ + radio_resource_t& radio = _radios[instance]; + + //Create common settings interface + const sid_t ctrl_sid = _generate_sid(RADIO_CONTROL, _get_conn(PRI_ETH).type, instance); + transport::udp_zero_copy::buff_params buff_out_params; + transport::zero_copy_if::sptr ctrl_xport = + _create_transport(_get_conn(PRI_ETH), ctrl_sid, device_addr_t(), buff_out_params); + if (ctrl_xport.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create radio transport.)"); + } + radio.ctrl = radio_ctrl_core_3000::make( + fpga::CVITA_BIG_ENDIAN, ctrl_xport, ctrl_xport, ctrl_sid.get()); + if (radio.ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create radio ctrl.)"); + } + + //Perform register loopback test to verify the radio clock + bool reg_selftest_pass = _radio_register_loopback_self_test(radio.ctrl); + if (!reg_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Register loopback test failed.)"); + } + + //Write-only ATR interface + radio.gpio_atr = gpio_atr::gpio_atr_3000::make_write_only(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_ATR)); + radio.gpio_atr->set_atr_mode(gpio_atr::MODE_ATR,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + + //Core VITA time interface + time_core_3000::readback_bases_type time_bases; + time_bases.rb_now = fpga::rb_addr(fpga::RB_RADIO_TIME_NOW); + time_bases.rb_pps = fpga::rb_addr(fpga::RB_RADIO_TIME_PPS); + radio.time = time_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TIME), time_bases); + if (radio.time.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create time core.)"); + } + + //RX DSP + radio.framer = rx_vita_core_3000::make( + radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_CTRL)); + radio.ddc = rx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_DSP), true /*old DDC?*/); + if (radio.framer.get() == NULL || radio.ddc.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); + } + radio.ddc->set_link_rate(fpga::N230_LINK_RATE_BPS); + + //TX DSP + radio.deframer = tx_vita_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_CTRL)); + radio.duc = tx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_DSP)); + if (radio.deframer.get() == NULL || radio.duc.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); + } + radio.duc->set_link_rate(fpga::N230_LINK_RATE_BPS); + + //User settings + radio.user_settings = user_settings_core_3000::make(radio.ctrl, + fpga::sr_addr(fpga::SR_RADIO_USER_SR), fpga::rb_addr(fpga::SR_RADIO_USER_RB)); + if (radio.user_settings.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create user settings bus.)"); + } +} + +boost::uint8_t xb_ep_to_sid(fpga::xb_endpoint_t ep) { + return static_cast<boost::uint8_t>(ep) << 4; +} + +const sid_t n230_resource_manager::_generate_sid(const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance) +{ + fpga::xb_endpoint_t xb_dest_ep; + boost::uint8_t sid_dest_ep = 0; + fpga::xb_endpoint_t xb_ret_ep = (xport == ETH1) ? fpga::N230_XB_DST_E1 : fpga::N230_XB_DST_E0; + boost::uint8_t sid_ret_addr = (xport == ETH1) ? N230_HOST_SRC_ADDR_ETH1 : N230_HOST_SRC_ADDR_ETH0; + + if (type == CORE or type == GPS_UART) { + //Non-radio endpoints + xb_dest_ep = (type == CORE) ? fpga::N230_XB_DST_GCTRL : fpga::N230_XB_DST_UART; + sid_dest_ep = xb_ep_to_sid(xb_dest_ep); + } else { + //Radio endpoints + xb_dest_ep = (instance == 1) ? fpga::N230_XB_DST_R1 : fpga::N230_XB_DST_R0; + sid_dest_ep = xb_ep_to_sid(xb_dest_ep); + switch (type) { + case RADIO_TX_DATA: + sid_dest_ep |= fpga::RADIO_DATA_SUFFIX; + break; + case RADIO_RX_DATA: + sid_dest_ep |= fpga::RADIO_FC_SUFFIX; + break; + default: + sid_dest_ep |= fpga::RADIO_CTRL_SUFFIX; + break; + } + } + + //Increment last host logical endpoint + sid_t sid(sid_ret_addr, ++_last_host_enpoint, N230_HOST_DEST_ADDR, sid_dest_ep); + + //Program the crossbar addr + _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, fw::SR_ZPU_XB_LOCAL), sid.get_dst_addr()); + // Program CAM entry for returning packets to us + // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM + _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, sid.get_src_addr()), static_cast<boost::uint32_t>(xb_ret_ep)); + // Program CAM entry for outgoing packets matching a N230 resource (for example a Radio) + // This type of packet does matches the XB_LOCAL address and is looked up in the upper half of the CAM + _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, 256 + sid.get_dst_endpoint()), static_cast<boost::uint32_t>(xb_dest_ep)); + + return sid; +} + +transport::zero_copy_if::sptr n230_resource_manager::_create_transport( + const n230_eth_conn_t& eth_conn, + const sid_t& sid, const device_addr_t &buff_params, + transport::udp_zero_copy::buff_params& buff_params_out) +{ + transport::zero_copy_xport_params default_buff_args; + default_buff_args.recv_frame_size = transport::udp_simple::mtu; + default_buff_args.send_frame_size = transport::udp_simple::mtu; + default_buff_args.num_recv_frames = 32; + default_buff_args.num_send_frames = 32; + + transport::zero_copy_if::sptr xport = transport::udp_zero_copy::make( + eth_conn.ip_addr, boost::lexical_cast<std::string>(fpga::CVITA_UDP_PORT), + default_buff_args, buff_params_out, buff_params); + + if (xport.get()) { + _program_dispatcher(*xport, eth_conn.type, sid); + } + return xport; +} + +void n230_resource_manager::_program_dispatcher( + transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid) +{ + //Send a mini packet with SID into the ZPU + //ZPU will reprogram the ethernet framer + transport::managed_send_buffer::sptr buff = xport.get_send_buff(); + buff->cast<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0 + buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid.get()); + buff->commit(8); + buff.reset(); + + //reprogram the ethernet dispatcher's udp port (should be safe to always set) + uint32_t disp_base_offset = + ((port == ETH1) ? fw::SR_ZPU_ETHINT1 : fw::SR_ZPU_ETHINT0) + fw::SR_ZPU_ETHINT_DISPATCHER_BASE; + _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, disp_base_offset + fw::ETH_FRAMER_SRC_UDP_PORT), fpga::CVITA_UDP_PORT); + + //Do a peek to an arbitrary address to guarantee that the + //ethernet framer has been programmed before we return. + _fw_ctrl->peek32(0); +} + +void n230_resource_manager::_reset_codec_digital_interface() +{ + //Set timing registers + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_DATA_DELAY), fpga::CODEC_DATA_DELAY); + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_CLK_DELAY), fpga::CODEC_CLK_DELAY); + + _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 1); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 0); +} + +bool n230_resource_manager::_radio_register_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + size_t hash = static_cast<size_t>(time(NULL)); + for (size_t i = 0; i < 100; i++) { + boost::hash_combine(hash, i); + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_TEST), boost::uint32_t(hash)); + test_fail = iface->peek32(fpga::rb_addr(fpga::RB_RADIO_TEST)) != boost::uint32_t(hash); + if (test_fail) break; //exit loop on any failure + } + return !test_fail; +} + +bool n230_resource_manager::_radio_data_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + size_t hash = size_t(time(NULL)); + for (size_t i = 0; i < 100; i++) { + boost::hash_combine(hash, i); + const boost::uint32_t word32 = boost::uint32_t(hash) & (IF_DATA_I_MASK | IF_DATA_Q_MASK); + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), word32); + iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); //block until request completes + boost::this_thread::sleep(boost::posix_time::microseconds(100)); //wait for loopback to propagate through codec + const boost::uint64_t rb_word64 = iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); + const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); + const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); + test_fail = word32 != rb_tx or word32 != rb_rx; + if (test_fail) + UHD_MSG(fastpath) << boost::format("mismatch (exp:%x, got:%x and %x)... ") % word32 % rb_tx % rb_rx; + break; //exit loop on any failure + } + + /* Zero out the idle data. */ + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), 0); + return !test_fail; +} + +std::string n230_resource_manager::_get_fpga_upgrade_msg() { + std::string img_loader_path = + (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); + + return str(boost::format( + "\nDownload the appropriate FPGA images for this version of UHD.\n" + "%s\n\n" + "Then burn a new image to the on-board flash storage of your\n" + "USRP N230 device using the image loader utility. Use this command:\n" + "\n \"%s\" --args=\"type=n230,addr=%s\"\n") + % print_utility_error("uhd_images_downloader.py") + % img_loader_path % _get_conn(PRI_ETH).ip_addr); + +} + +void n230_resource_manager::_check_fw_compat() +{ + boost::uint32_t compat_num = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_compat_num)); + _fw_version.compat_major = compat_num >> 16; + _fw_version.compat_minor = compat_num; + _fw_version.version_hash = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_version_hash)); + + if (_fw_version.compat_major != N230_FW_COMPAT_NUM_MAJOR){ + throw uhd::runtime_error(str(boost::format( + "Expected firmware compatibility number %d.x, but got %d.%d\n" + "The firmware build is not compatible with the host code build.\n" + "%s" + ) % static_cast<boost::uint32_t>(N230_FW_COMPAT_NUM_MAJOR) + % static_cast<boost::uint32_t>(_fw_version.compat_major) + % static_cast<boost::uint32_t>(_fw_version.compat_minor) + % _get_fpga_upgrade_msg())); + } +} + +void n230_resource_manager::_check_fpga_compat() +{ + const boost::uint64_t compat = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_SIGNATUE)); + const boost::uint32_t signature = boost::uint32_t(compat >> 32); + const boost::uint16_t product_id = boost::uint8_t(compat >> 24); + _fpga_version.compat_major = static_cast<boost::uint8_t>(compat >> 16); + _fpga_version.compat_minor = static_cast<boost::uint16_t>(compat); + + const boost::uint64_t version_hash = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_VERSION_HASH)); + _fpga_version.version_hash = boost::uint32_t(version_hash); + + if (signature != 0x0ACE0BA5E || product_id != fpga::RB_N230_PRODUCT_ID) + throw uhd::runtime_error("Signature check failed. Please contact support."); + + bool is_safe_image = (_fpga_version.compat_major > fpga::RB_N230_COMPAT_SAFE); + + if (is_safe_image && !_safe_mode) { + throw uhd::runtime_error( + "The device appears to have the failsafe FPGA image loaded\n" + "This could have happened because the production FPGA image in the flash was either corrupt or non-existent\n" + "To remedy this error, please burn a valid FPGA image to the flash.\n" + "To continue using the failsafe image with UHD, create the UHD device with the \"safe_mode\" device arg.\n" + "Radio functionality/performance not guaranteed when operating in safe mode.\n"); + } else if (_fpga_version.compat_major != fpga::RB_N230_COMPAT_MAJOR && !is_safe_image) { + throw uhd::runtime_error(str(boost::format( + "Expected FPGA compatibility number %d.x, but got %d.%d:\n" + "The FPGA build is not compatible with the host code build.\n" + "%s" + ) % static_cast<boost::uint32_t>(fpga::RB_N230_COMPAT_MAJOR) + % static_cast<boost::uint32_t>(_fpga_version.compat_major) + % static_cast<boost::uint32_t>(_fpga_version.compat_minor) + % _get_fpga_upgrade_msg())); + } +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_resource_manager.hpp b/host/lib/usrp/n230/n230_resource_manager.hpp new file mode 100644 index 000000000..0a1178bd2 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.hpp @@ -0,0 +1,318 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_RESOURCE_MANAGER_HPP +#define INCLUDED_N230_RESOURCE_MANAGER_HPP + +#include "radio_ctrl_core_3000.hpp" +#include "spi_core_3000.hpp" +#include "gpio_atr_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "user_settings_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include "ad936x_manager.hpp" +#include <uhd/utils/tasks.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/utils/soft_register.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/usrp/gps_ctrl.hpp> + +#include "usrp3_fw_ctrl_iface.hpp" +#include "n230_clk_pps_ctrl.hpp" +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" +#include "n230_frontend_ctrl.hpp" +#include "n230_uart.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +enum n230_eth_port_t { + ETH0, + ETH1 +}; + +enum n230_eth_pref_t { + PRI_ETH, + SEC_ETH +}; + +enum n230_endpoint_t { + RADIO_TX_DATA, + RADIO_RX_DATA, + RADIO_CONTROL, + CORE, + GPS_UART +}; + +enum n230_ver_src_t { + SOFTWARE, + FIRMWARE, + FPGA +}; + +enum n230_version_t { + COMPAT_MAJOR, + COMPAT_MINOR +}; + +enum n230_data_dir_t { + RX_DATA, TX_DATA +}; + +//Radio resources +class radio_resource_t : public boost::noncopyable { +public: + radio_ctrl_core_3000::sptr ctrl; + gpio_atr::gpio_atr_3000::sptr gpio_atr; + time_core_3000::sptr time; + rx_vita_core_3000::sptr framer; + rx_dsp_core_3000::sptr ddc; + tx_vita_core_3000::sptr deframer; + tx_dsp_core_3000::sptr duc; + user_settings_core_3000::sptr user_settings; +}; + +class n230_resource_manager : public boost::noncopyable +{ +public: //Methods + n230_resource_manager(const std::vector<std::string> ip_addrs, const bool safe_mode); + virtual ~n230_resource_manager(); + + static bool is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl); + + inline bool is_device_claimed() { + if (_fw_ctrl.get()) { + return is_device_claimed(_fw_ctrl); + } else { + return false; + } + } + + inline boost::uint32_t get_version(n230_ver_src_t src, n230_version_t type) { + switch (src) { + case FPGA: return _fpga_version.get(type); + case FIRMWARE: return _fw_version.get(type); + default: return 0; + } + } + + inline const std::string get_version_hash(n230_ver_src_t src) { + switch (src) { + case FPGA: return _fpga_version.get_hash_str(); + case FIRMWARE: return _fw_version.get_hash_str(); + default: return ""; + } + } + + //Firmware control interface + inline wb_iface& get_fw_ctrl() const { + return *_fw_ctrl; + } + inline wb_iface::sptr get_fw_ctrl_sptr() { + return _fw_ctrl; + } + + //Core settings control interface + inline radio_ctrl_core_3000& get_core_ctrl() const { + return *_core_ctrl; + } + inline radio_ctrl_core_3000::sptr get_core_ctrl_sptr() { + return _core_ctrl; + } + + //AD931 control interface + inline ad9361_ctrl& get_codec_ctrl() const { + return *_codec_ctrl; + } + inline ad9361_ctrl::sptr get_codec_ctrl_sptr() { + return _codec_ctrl; + } + inline uhd::usrp::ad936x_manager& get_codec_mgr() const { + return *_codec_mgr; + } + + //Clock PPS controls + inline n230_ref_pll_ctrl& get_ref_pll_ctrl() const { + return *_ref_pll_ctrl; + } + inline n230_ref_pll_ctrl::sptr get_ref_pll_ctrl_sptr() { + return _ref_pll_ctrl; + } + + //Clock PPS controls + inline n230_clk_pps_ctrl& get_clk_pps_ctrl() const { + return *_clk_pps_ctrl; + } + inline n230_clk_pps_ctrl::sptr get_clk_pps_ctrl_sptr() { + return _clk_pps_ctrl; + } + + //Front-end control + inline n230_frontend_ctrl& get_frontend_ctrl() const { + return *_frontend_ctrl; + } + inline n230_frontend_ctrl::sptr get_frontend_ctrl_sptr() { + return _frontend_ctrl; + } + + //MiniSAS GPIO control + inline gpio_atr::gpio_atr_3000::sptr get_minisas_gpio_ctrl_sptr(size_t idx) { + return idx == 0 ? _ms0_gpio : _ms1_gpio; + } + + inline gpio_atr::gpio_atr_3000& get_minisas_gpio_ctrl(size_t idx) { + return *get_minisas_gpio_ctrl_sptr(idx); + } + + //GPSDO control + inline bool is_gpsdo_present() { + return _gps_ctrl.get() and _gps_ctrl->gps_detected(); + } + + inline uhd::gps_ctrl::sptr get_gps_ctrl(void) { + return _gps_ctrl; + } + + inline radio_resource_t& get_radio(size_t instance) { + return _radios[instance]; + } + + //Transport to stream data + transport::zero_copy_if::sptr create_transport( + n230_data_dir_t direction, size_t radio_instance, + const device_addr_t ¶ms, sid_t& sid, + transport::udp_zero_copy::buff_params& buff_out_params); + + //Misc + inline double get_max_link_rate() { + return fpga::N230_LINK_RATE_BPS * _eth_conns.size(); + } + +private: + struct ver_info_t { + boost::uint8_t compat_major; + boost::uint16_t compat_minor; + boost::uint32_t version_hash; + + boost::uint32_t get(n230_version_t type) { + switch (type) { + case COMPAT_MAJOR: return compat_major; + case COMPAT_MINOR: return compat_minor; + default: return 0; + } + } + + const std::string get_hash_str() { + return (str(boost::format("%07x%s") + % (version_hash & 0x0FFFFFFF) + % ((version_hash & 0xF0000000) ? "(modified)" : ""))); + } + }; + + struct n230_eth_conn_t { + std::string ip_addr; + n230_eth_port_t type; + }; + + //-- Functions -- + + void _claimer_loop(); + + void _initialize_radio(size_t instance); + + std::string _get_fpga_upgrade_msg(); + void _check_fw_compat(); + void _check_fpga_compat(); + + const sid_t _generate_sid( + const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance = 0); + + transport::zero_copy_if::sptr _create_transport( + const n230_eth_conn_t& eth_conn, + const sid_t& sid, const device_addr_t &buff_params, + transport::udp_zero_copy::buff_params& buff_params_out); + + void _program_dispatcher( + transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid); + + void _reset_codec_digital_interface(); + + bool _radio_register_loopback_self_test(wb_iface::sptr iface); + + bool _radio_data_loopback_self_test(wb_iface::sptr iface); + + inline const n230_eth_conn_t& _get_conn(const n230_eth_pref_t pref) { + if (_eth_conns.size() == 1) + return _eth_conns[0]; + else + return _eth_conns[(pref==PRI_ETH)?0:1]; + } + + //-- Members -- + + std::vector<n230_eth_conn_t> _eth_conns; + const bool _safe_mode; + ver_info_t _fw_version; + ver_info_t _fpga_version; + + //Firmware register interface + uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr _fw_ctrl; + uhd::task::sptr _claimer_task; + static boost::mutex _claimer_mutex; //All claims and checks in this process are serialized + + //Transport + boost::uint8_t _last_host_enpoint; + + //Radio settings interface + radio_ctrl_core_3000::sptr _core_ctrl; + n230_core_spi_core::sptr _core_spi_ctrl; + ad9361_ctrl::sptr _codec_ctrl; + uhd::usrp::ad936x_manager::sptr _codec_mgr; + + //Core Registers + fpga::core_radio_ctrl_reg_t _core_radio_ctrl_reg; + fpga::core_misc_reg_t _core_misc_reg; + fpga::core_pps_sel_reg_t _core_pps_sel_reg; + fpga::core_status_reg_t _core_status_reg; + + //Radio peripherals + radio_resource_t _radios[fpga::NUM_RADIOS]; + + //Misc IO peripherals + n230_ref_pll_ctrl::sptr _ref_pll_ctrl; + n230_clk_pps_ctrl::sptr _clk_pps_ctrl; + n230_frontend_ctrl::sptr _frontend_ctrl; + + //miniSAS GPIO + gpio_atr::gpio_atr_3000::sptr _ms0_gpio; + gpio_atr::gpio_atr_3000::sptr _ms1_gpio; + + //GPSDO + n230_uart::sptr _gps_uart; + uhd::gps_ctrl::sptr _gps_ctrl; + +}; + +}}} //namespace + +#endif //INCLUDED_N230_RESOURCE_MANAGER_HPP diff --git a/host/lib/usrp/n230/n230_stream_manager.cpp b/host/lib/usrp/n230/n230_stream_manager.cpp new file mode 100644 index 000000000..e7624ecd6 --- /dev/null +++ b/host/lib/usrp/n230/n230_stream_manager.cpp @@ -0,0 +1,562 @@ +// +// Copyright 2013 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_stream_manager.hpp" + +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "async_packet_handler.hpp" +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/log.hpp> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> + +static const double N230_RX_SW_BUFF_FULL_FACTOR = 0.90; //Buffer should ideally be 90% full. +static const size_t N230_RX_FC_REQUEST_FREQ = 32; //per flow-control window +static const size_t N230_TX_MAX_ASYNC_MESSAGES = 1000; +static const size_t N230_TX_MAX_SPP = 4092; +static const size_t N230_TX_FC_RESPONSE_FREQ = 10; //per flow-control window + +static const boost::uint32_t N230_EVENT_CODE_FLOW_CTRL = 0; + +namespace uhd { namespace usrp { namespace n230 { + +using namespace uhd::transport; + +n230_stream_manager::~n230_stream_manager() +{ +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +n230_stream_manager::n230_stream_manager( + const n230_device_args_t& dev_args, + boost::shared_ptr<n230_resource_manager> resource_mgr, + boost::weak_ptr<property_tree> prop_tree +) : + _dev_args(dev_args), + _resource_mgr(resource_mgr), + _tree(prop_tree) +{ + _async_md_queue.reset(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES)); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr n230_stream_manager::get_rx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_stream_setup_mutex); + + stream_args_t args = args_; + + //setup defaults for unspecified values + if (args.otw_format.empty()) args.otw_format = "sc16"; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + boost::shared_ptr<sph::recv_packet_streamer> my_streamer; + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + const size_t chan = args.channels[stream_i]; + radio_resource_t& perif = _resource_mgr->get_radio(chan); + + //setup transport hints (default to a large recv buff) + //TODO: Propagate the device_args class into streamer in the future + device_addr_t device_addr = args.args; + if (not device_addr.has_key("recv_buff_size")) { + device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_buff_size()); + } + if (not device_addr.has_key("recv_frame_size")) { + device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_frame_size()); + } + if (not device_addr.has_key("num_recv_frames")) { + device_addr["num_recv_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_recv_frames()); + } + + transport::udp_zero_copy::buff_params buff_params_out; + sid_t sid; + zero_copy_if::sptr xport = _resource_mgr->create_transport( + RX_DATA, chan, device_addr, sid, buff_params_out); + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + //+ sizeof(vrt::if_packet_info_t().tlr) //no longer using trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = xport->get_recv_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + spp = std::min<size_t>(N230_TX_MAX_SPP, spp); //FPGA FIFO maximum for framing at full rate + + //make the new streamer given the samples per packet + if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + my_streamer->resize(args.channels.size()); + + //init some streamer stuff + my_streamer->set_vrt_unpacker(&n230_stream_manager::_cvita_hdr_unpack); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_be"; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.framer->clear(); + perif.framer->set_nsamps_per_packet(spp); + perif.framer->set_sid(sid.reversed().get()); + perif.framer->setup(args); + perif.ddc->setup(args); + + //Give the streamer a functor to get the recv_buffer + //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&zero_copy_if::get_recv_buff, xport, _1), + true /*flush*/ + ); + + my_streamer->set_overflow_handler(stream_i, boost::bind( + &n230_stream_manager::_handle_overflow, this, chan + )); + + my_streamer->set_issue_stream_cmd(stream_i, boost::bind( + &rx_vita_core_3000::issue_stream_command, perif.framer, _1 + )); + + const size_t fc_window = _get_rx_flow_control_window( + xport->get_recv_frame_size(), buff_params_out.recv_buff_size); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / N230_RX_FC_REQUEST_FREQ); + + perif.framer->configure_flow_control(fc_window); + + //Give the streamer a functor to send flow control messages + //handle_rx_flowctrl is static and has no lifetime issues + boost::shared_ptr<rx_fc_cache_t> fc_cache(new rx_fc_cache_t()); + my_streamer->set_xport_handle_flowctrl( + stream_i, boost::bind(&n230_stream_manager::_handle_rx_flowctrl, sid.get(), xport, fc_cache, _1), + fc_handle_window, + true/*init*/ + ); + + //Store a weak pointer to prevent a streamer->manager->streamer circular dependency + _rx_streamers[chan] = my_streamer; //store weak pointer + _rx_stream_cached_args[chan] = args; + + //Sets tick and samp rates on all streamer + update_tick_rate(_get_tick_rate()); + + //TODO: Find a way to remove this dependency + property_tree::sptr prop_tree = _tree.lock(); + if (prop_tree) { + //TODO: Update this to support multiple motherboards + const fs_path mb_path = "/mboards/0"; + prop_tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update(); + } + } + update_stream_states(); + + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr n230_stream_manager::get_tx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_stream_setup_mutex); + + uhd::stream_args_t args = args_; + + //setup defaults for unspecified values + if (not args.otw_format.empty() and args.otw_format != "sc16") { + throw uhd::value_error("n230_impl::get_tx_stream only supports otw_format sc16"); + } + args.otw_format = "sc16"; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //shared async queue for all channels in streamer + boost::shared_ptr<async_md_queue_t> async_md(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES)); + + boost::shared_ptr<sph::send_packet_streamer> my_streamer; + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + const size_t chan = args.channels[stream_i]; + radio_resource_t& perif = _resource_mgr->get_radio(chan); + + //setup transport hints (default to a large recv buff) + //TODO: Propagate the device_args class into streamer in the future + device_addr_t device_addr = args.args; + if (not device_addr.has_key("send_buff_size")) { + device_addr["send_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_buff_size()); + } + if (not device_addr.has_key("send_frame_size")) { + device_addr["send_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_frame_size()); + } + if (not device_addr.has_key("num_send_frames")) { + device_addr["num_send_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_send_frames()); + } + + transport::udp_zero_copy::buff_params buff_params_out; + sid_t sid; + zero_copy_if::sptr xport = _resource_mgr->create_transport( + TX_DATA, chan, device_addr, sid, buff_params_out); + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::num_vrl_words32*sizeof(boost::uint32_t) + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + //+ sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = xport->get_send_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + if (not my_streamer) my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_packer(&n230_stream_manager::_cvita_hdr_pack); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_be"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.deframer->clear(); + perif.deframer->setup(args); + perif.duc->setup(args); + + //flow control setup + size_t fc_window = _get_tx_flow_control_window( + bpp, device_addr.cast<size_t>("send_buff_size", _dev_args.get_send_buff_size())); + //In packets + const size_t fc_handle_window = (fc_window / N230_TX_FC_RESPONSE_FREQ); + + perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window); + boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t()); + fc_cache->stream_channel = stream_i; + fc_cache->device_channel = chan; + fc_cache->async_queue = async_md; + fc_cache->old_async_queue = _async_md_queue; + + tick_rate_retriever_t get_tick_rate_fn = boost::bind(&n230_stream_manager::_get_tick_rate, this); + task::sptr task = task::make( + boost::bind(&n230_stream_manager::_handle_tx_async_msgs, + fc_cache, xport, get_tick_rate_fn)); + + //Give the streamer a functor to get the send buffer + //get_tx_buff_with_flowctrl is static so bind has no lifetime issues + //xport.send (sptr) is required to add streamer->data-transport lifetime dependency + //task (sptr) is required to add a streamer->async-handler lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&n230_stream_manager::_get_tx_buff_with_flowctrl, task, fc_cache, xport, fc_window, _1) + ); + //Give the streamer a functor handled received async messages + my_streamer->set_async_receiver( + boost::bind(&async_md_queue_t::pop_with_timed_wait, async_md, _1, _2) + ); + my_streamer->set_xport_chan_sid(stream_i, true, sid.get()); + my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet + + //Store a weak pointer to prevent a streamer->manager->streamer circular dependency + _tx_streamers[chan] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); + _tx_stream_cached_args[chan] = args; + + //Sets tick and samp rates on all streamer + update_tick_rate(_get_tick_rate()); + + //TODO: Find a way to remove this dependency + property_tree::sptr prop_tree = _tree.lock(); + if (prop_tree) { + //TODO: Update this to support multiple motherboards + const fs_path mb_path = "/mboards/0"; + prop_tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update(); + } + } + update_stream_states(); + + return my_streamer; +} + +/*********************************************************************** + * Async Message Receiver + **********************************************************************/ +bool n230_stream_manager::recv_async_msg(async_metadata_t &async_metadata, double timeout) +{ + return _async_md_queue->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Sample Rate Updaters + **********************************************************************/ +void n230_stream_manager::update_rx_samp_rate(const size_t dspno, const double rate) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[dspno].lock()); + if (not my_streamer) return; + my_streamer->set_samp_rate(rate); + const double adj = _resource_mgr->get_radio(dspno).ddc->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void n230_stream_manager::update_tx_samp_rate(const size_t dspno, const double rate) +{ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[dspno].lock()); + if (not my_streamer) return; + my_streamer->set_samp_rate(rate); + const double adj = _resource_mgr->get_radio(dspno).duc->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +/*********************************************************************** + * Tick Rate Updater + **********************************************************************/ +void n230_stream_manager::update_tick_rate(const double rate) +{ + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + radio_resource_t& perif = _resource_mgr->get_radio(i); + + boost::shared_ptr<sph::recv_packet_streamer> my_rx_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); + if (my_rx_streamer) my_rx_streamer->set_tick_rate(rate); + perif.framer->set_tick_rate(rate); + + boost::shared_ptr<sph::send_packet_streamer> my_tx_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[i].lock()); + if (my_tx_streamer) my_tx_streamer->set_tick_rate(rate); + } +} + +/*********************************************************************** + * Stream State Updater + **********************************************************************/ +void n230_stream_manager::update_stream_states() +{ + //extract settings from state variables + const bool enb_tx0 = bool(_tx_streamers[0].lock()); + const bool enb_rx0 = bool(_rx_streamers[0].lock()); + const bool enb_tx1 = bool(_tx_streamers[1].lock()); + const bool enb_rx1 = bool(_rx_streamers[1].lock()); + + fe_state_t fe0_state = NONE_STREAMING; + if (enb_tx0 && enb_rx0) fe0_state = TXRX_STREAMING; + else if (enb_tx0) fe0_state = TX_STREAMING; + else if (enb_rx0) fe0_state = RX_STREAMING; + + fe_state_t fe1_state = NONE_STREAMING; + if (enb_tx1 && enb_rx1) fe1_state = TXRX_STREAMING; + else if (enb_tx1) fe1_state = TX_STREAMING; + else if (enb_rx1) fe1_state = RX_STREAMING; + + _resource_mgr->get_frontend_ctrl().set_stream_state(fe0_state, fe1_state); +} + +size_t n230_stream_manager::_get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size) +{ + double sw_buff_max = sw_buff_size * N230_RX_SW_BUFF_FULL_FACTOR; + size_t window_in_pkts = (static_cast<size_t>(sw_buff_max) / frame_size); + if (window_in_pkts == 0) { + throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); + } + return window_in_pkts; +} + +void n230_stream_manager::_handle_overflow(const size_t i) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); + if (my_streamer->get_num_channels() == 2) { + //MIMO + //find out if we were in continuous mode before stopping + const bool in_continuous_streaming_mode = _resource_mgr->get_radio(i).framer->in_continuous_streaming_mode(); + //stop streaming + my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + //restart streaming + if (in_continuous_streaming_mode) { + stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + stream_cmd.stream_now = false; + stream_cmd.time_spec = _resource_mgr->get_radio(i).time->get_time_now() + time_spec_t(0.01); + my_streamer->issue_stream_cmd(stream_cmd); + } + } else { + _resource_mgr->get_radio(i).framer->handle_overflow(); + } +} + +void n230_stream_manager::_handle_rx_flowctrl( + const sid_t& sid, + zero_copy_if::sptr xport, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq) +{ + static const size_t RXFC_PACKET_LEN_IN_WORDS = 2; + static const size_t RXFC_CMD_CODE_OFFSET = 0; + static const size_t RXFC_SEQ_NUM_OFFSET = 1; + + managed_send_buffer::sptr buff = xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + //recover seq32 + size_t& seq_sw = fc_cache->last_seq_in; + const size_t seq_hw = seq_sw & HW_SEQ_NUM_MASK; + if (last_seq < seq_hw) seq_sw += (HW_SEQ_NUM_MASK + 1); + seq_sw &= ~HW_SEQ_NUM_MASK; + seq_sw |= last_seq; + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; + packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = seq_sw; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = sid.get(); + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + //load header + _cvita_hdr_pack(pkt, packet_info); + + //load payload + pkt[packet_info.num_header_words32 + RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(N230_EVENT_CODE_FLOW_CTRL); + pkt[packet_info.num_header_words32 + RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq_sw); + + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + +void n230_stream_manager::_handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + tick_rate_retriever_t get_tick_rate) +{ + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff) return; + + //extract packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + //unpacking can fail + uint32_t (*endian_conv)(uint32_t) = uhd::ntohx; + try { + _cvita_hdr_unpack(packet_buff, if_packet_info); + endian_conv = uhd::ntohx; + } catch(const std::exception &ex) { + UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + return; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff( + endian_conv, metadata, if_packet_info, packet_buff, + get_tick_rate(), fc_cache->stream_channel); + + //The FC response and the burst ack are two indicators that the radio + //consumed packets. Use them to update the FC metadata + if (metadata.event_code == N230_EVENT_CODE_FLOW_CTRL or + metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK + ) { + const size_t seq = metadata.user_payload[0]; + fc_cache->seq_queue.push_with_pop_on_full(seq); + } + + //FC responses don't propagate up to the user so filter them here + if (metadata.event_code != N230_EVENT_CODE_FLOW_CTRL) { + fc_cache->async_queue->push_with_pop_on_full(metadata); + metadata.channel = fc_cache->device_channel; + fc_cache->old_async_queue->push_with_pop_on_full(metadata); + standard_async_msg_prints(metadata); + } +} + +managed_send_buffer::sptr n230_stream_manager::_get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + size_t fc_pkt_window, + const double timeout) +{ + while (true) + { + const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); + if ((delta & HW_SEQ_NUM_MASK) <= fc_pkt_window) break; + + const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); + if (not ok) return managed_send_buffer::sptr(); //timeout waiting for flow control + } + + managed_send_buffer::sptr buff = xport->get_send_buff(timeout); + if (buff) fc_cache->last_seq_out++; //update seq, this will actually be a send + return buff; +} + +size_t n230_stream_manager::_get_tx_flow_control_window( + size_t payload_size, + size_t hw_buff_size) +{ + size_t window_in_pkts = hw_buff_size / payload_size; + if (window_in_pkts == 0) { + throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); + } + return window_in_pkts; +} + +double n230_stream_manager::_get_tick_rate() +{ + return _resource_mgr->get_clk_pps_ctrl().get_tick_rate(); +} + +void n230_stream_manager::_cvita_hdr_unpack( + const boost::uint32_t *packet_buff, + vrt::if_packet_info_t &if_packet_info) +{ + if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + return vrt::if_hdr_unpack_be(packet_buff, if_packet_info); +} + +void n230_stream_manager::_cvita_hdr_pack( + boost::uint32_t *packet_buff, + vrt::if_packet_info_t &if_packet_info) +{ + if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + return vrt::if_hdr_pack_be(packet_buff, if_packet_info); +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_stream_manager.hpp b/host/lib/usrp/n230/n230_stream_manager.hpp new file mode 100644 index 000000000..7a496c4e9 --- /dev/null +++ b/host/lib/usrp/n230/n230_stream_manager.hpp @@ -0,0 +1,151 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_STREAM_MANAGER_HPP +#define INCLUDED_N230_STREAM_MANAGER_HPP + +#include "time_core_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include <uhd/types/sid.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/tasks.hpp> +#include <boost/smart_ptr.hpp> +#include "n230_device_args.hpp" +#include "n230_resource_manager.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_stream_manager : public boost::noncopyable +{ +public: //Methods + n230_stream_manager( + const n230_device_args_t& dev_args, + boost::shared_ptr<n230_resource_manager> resource_mgr, + boost::weak_ptr<property_tree> prop_tree); + virtual ~n230_stream_manager(); + + rx_streamer::sptr get_rx_stream( + const uhd::stream_args_t &args); + + tx_streamer::sptr get_tx_stream( + const uhd::stream_args_t &args_); + + bool recv_async_msg( + async_metadata_t &async_metadata, + double timeout); + + void update_stream_states(); + + void update_rx_samp_rate( + const size_t dspno, const double rate); + + void update_tx_samp_rate( + const size_t dspno, const double rate); + + void update_tick_rate( + const double rate); + +private: + typedef transport::bounded_buffer<async_metadata_t> async_md_queue_t; + + struct rx_fc_cache_t + { + rx_fc_cache_t(): + last_seq_in(0){} + size_t last_seq_in; + }; + + struct tx_fc_cache_t + { + tx_fc_cache_t(): + stream_channel(0), + device_channel(0), + last_seq_out(0), + last_seq_ack(0), + seq_queue(1){} + size_t stream_channel; + size_t device_channel; + size_t last_seq_out; + size_t last_seq_ack; + transport::bounded_buffer<size_t> seq_queue; + boost::shared_ptr<async_md_queue_t> async_queue; + boost::shared_ptr<async_md_queue_t> old_async_queue; + }; + + typedef boost::function<double(void)> tick_rate_retriever_t; + + void _handle_overflow(const size_t i); + + double _get_tick_rate(); + + static size_t _get_rx_flow_control_window( + size_t frame_size, size_t sw_buff_size); + + static void _handle_rx_flowctrl( + const sid_t& sid, + transport::zero_copy_if::sptr xport, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq); + + static void _handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> guts, + transport::zero_copy_if::sptr xport, + tick_rate_retriever_t get_tick_rate); + + static transport::managed_send_buffer::sptr _get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> guts, + transport::zero_copy_if::sptr xport, + size_t fc_pkt_window, + const double timeout); + + static size_t _get_tx_flow_control_window( + size_t payload_size, + size_t hw_buff_size); + + static void _cvita_hdr_unpack( + const boost::uint32_t *packet_buff, + transport::vrt::if_packet_info_t &if_packet_info); + + static void _cvita_hdr_pack( + boost::uint32_t *packet_buff, + transport::vrt::if_packet_info_t &if_packet_info); + + const n230_device_args_t _dev_args; + boost::shared_ptr<n230_resource_manager> _resource_mgr; + //TODO: Find a way to remove this dependency + boost::weak_ptr<property_tree> _tree; + + boost::mutex _stream_setup_mutex; + uhd::msg_task::sptr _async_task; + boost::shared_ptr<async_md_queue_t> _async_md_queue; + boost::weak_ptr<uhd::tx_streamer> _tx_streamers[fpga::NUM_RADIOS]; + boost::weak_ptr<uhd::rx_streamer> _rx_streamers[fpga::NUM_RADIOS]; + stream_args_t _tx_stream_cached_args[fpga::NUM_RADIOS]; + stream_args_t _rx_stream_cached_args[fpga::NUM_RADIOS]; + + static const boost::uint32_t HW_SEQ_NUM_MASK = 0xFFF; +}; + +}}} //namespace + +#endif //INCLUDED_N230_STREAM_MANAGER_HPP diff --git a/host/lib/usrp/n230/n230_uart.cpp b/host/lib/usrp/n230/n230_uart.cpp new file mode 100644 index 000000000..20936c303 --- /dev/null +++ b/host/lib/usrp/n230/n230_uart.cpp @@ -0,0 +1,131 @@ +// +// Copyright 2013 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_uart.hpp" + +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/exception.hpp> + +using namespace uhd; +using namespace uhd::transport; + +namespace uhd { namespace usrp { namespace n230 { + +struct n230_uart_impl : n230_uart +{ + n230_uart_impl(zero_copy_if::sptr xport, const boost::uint32_t sid): + _xport(xport), + _sid(sid), + _count(0), + _char_queue(4096) + { + //this default baud divider is over 9000 + this->set_baud_divider(9001); + + //create a task to handle incoming packets + _recv_task = uhd::task::make(boost::bind(&n230_uart_impl::handle_recv, this)); + } + + void send_char(const char ch) + { + managed_send_buffer::sptr buff = _xport->get_send_buff(); + UHD_ASSERT_THROW(bool(buff)); + + vrt::if_packet_info_t packet_info; + packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; + packet_info.num_payload_words32 = 2; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = _count++; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = _sid; + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + boost::uint32_t *packet_buff = buff->cast<boost::uint32_t *>(); + vrt::if_hdr_pack_le(packet_buff, packet_info); + packet_buff[packet_info.num_header_words32+0] = uhd::htonx(boost::uint32_t(_baud_div)); + packet_buff[packet_info.num_header_words32+1] = uhd::htonx(boost::uint32_t(ch)); + buff->commit(packet_info.num_packet_words32*sizeof(boost::uint32_t)); + } + + void write_uart(const std::string &buff) + { + static bool r_sent = false; + for (size_t i = 0; i < buff.size(); i++) + { + if (buff[i] == '\n' and not r_sent) this->send_char('\r'); + this->send_char(buff[i]); + r_sent = (buff[i] == '\r'); + } + } + + std::string read_uart(double timeout) + { + std::string line; + char ch = '\0'; + while (_char_queue.pop_with_timed_wait(ch, timeout)) + { + if (ch == '\r') continue; + line += std::string(&ch, 1); + if (ch == '\n') return line; + } + return line; + } + + void handle_recv(void) + { + managed_recv_buffer::sptr buff = _xport->get_recv_buff(); + if (not buff) + return; + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + vrt::if_packet_info_t packet_info; + packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + vrt::if_hdr_unpack_be(packet_buff, packet_info); + const char ch = char(uhd::ntohx(packet_buff[packet_info.num_header_words32+1])); + _char_queue.push_with_pop_on_full(ch); + } + + void set_baud_divider(const double baud_div) + { + _baud_div = size_t(baud_div + 0.5); + } + + const zero_copy_if::sptr _xport; + const boost::uint32_t _sid; + size_t _count; + size_t _baud_div; + bounded_buffer<char> _char_queue; + uhd::task::sptr _recv_task; +}; + + +n230_uart::sptr n230_uart::make(zero_copy_if::sptr xport, const boost::uint32_t sid) +{ + return n230_uart::sptr(new n230_uart_impl(xport, sid)); +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_uart.hpp b/host/lib/usrp/n230/n230_uart.hpp new file mode 100644 index 000000000..0bde12ab2 --- /dev/null +++ b/host/lib/usrp/n230/n230_uart.hpp @@ -0,0 +1,38 @@ +// +// Copyright 2013 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_UART_HPP +#define INCLUDED_N230_UART_HPP + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/serial.hpp> //uart iface +#include <uhd/utils/tasks.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +namespace uhd { namespace usrp { namespace n230 { + +class n230_uart: boost::noncopyable, public uhd::uart_iface +{ +public: + typedef boost::shared_ptr<n230_uart> sptr; + static sptr make(uhd::transport::zero_copy_if::sptr, const boost::uint32_t sid); + virtual void set_baud_divider(const double baud_div) = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_UART_HPP */ diff --git a/host/lib/usrp/usrp1/CMakeLists.txt b/host/lib/usrp/usrp1/CMakeLists.txt index 47344e841..6924ba3b0 100644 --- a/host/lib/usrp/usrp1/CMakeLists.txt +++ b/host/lib/usrp/usrp1/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP1 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_USRP1) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp index 4c3141d9e..5640e8dae 100644 --- a/host/lib/usrp/usrp1/dboard_iface.cpp +++ b/host/lib/usrp/usrp1/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-2012,2015,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -63,6 +63,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace boost::assign; static const dboard_id_t tvrx_id(0x0040); @@ -106,12 +107,23 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void _set_pin_ctrl(unit_t, boost::uint16_t); void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); void _set_gpio_ddr(unit_t, boost::uint16_t); void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + + void set_command_time(const uhd::time_spec_t& t); + uhd::time_spec_t get_command_time(void); void write_i2c(boost::uint16_t, const byte_vector_t &); byte_vector_t read_i2c(boost::uint16_t, size_t); @@ -131,6 +143,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn); private: usrp1_iface::sptr _iface; @@ -139,6 +152,8 @@ private: const usrp1_impl::dboard_slot_t _dboard_slot; const double &_master_clock_rate; const dboard_id_t _rx_dboard_id; + uhd::dict<unit_t, boost::uint16_t> _pin_ctrl, _gpio_out, _gpio_ddr; + uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > _atr_regs; }; /*********************************************************************** @@ -217,6 +232,65 @@ double usrp1_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ +template <typename T> +static T shadow_it(T &shadow, const T &value, const T &mask){ + shadow = (shadow & ~mask) | (value & mask); + return shadow; +} + +void usrp1_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_pin_ctrl(unit, shadow_it(_pin_ctrl[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_pin_ctrl(unit_t unit){ + return _pin_ctrl[unit]; +} + +void usrp1_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _set_atr_reg(unit, reg, shadow_it(_atr_regs[unit][reg], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return _atr_regs[unit][reg]; +} + +void usrp1_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_gpio_ddr(unit, shadow_it(_gpio_ddr[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_gpio_ddr(unit_t unit){ + return _gpio_ddr[unit]; +} + +void usrp1_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_gpio_out(unit, shadow_it(_gpio_out[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_gpio_out(unit_t unit){ + return _gpio_out[unit]; +} + +boost::uint32_t usrp1_dboard_iface::read_gpio(unit_t unit) +{ + boost::uint32_t out_value; + + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + out_value = _iface->peek32(1); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + out_value = _iface->peek32(2); + else + UHD_THROW_INVALID_CODE_PATH(); + + switch(unit) { + case UNIT_RX: + return (boost::uint32_t)((out_value >> 16) & 0x0000ffff); + case UNIT_TX: + return (boost::uint32_t)((out_value >> 0) & 0x0000ffff); + default: UHD_THROW_INVALID_CODE_PATH(); + } + UHD_ASSERT_THROW(false); +} + void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) { switch(unit) { @@ -232,6 +306,7 @@ void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_MASK_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } @@ -250,6 +325,7 @@ void usrp1_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_OE_2, 0xffff0000 | value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } @@ -268,34 +344,10 @@ void usrp1_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_IO_2, 0xffff0000 | value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } -void usrp1_dboard_iface::set_gpio_debug(unit_t, int) -{ - /* NOP */ -} - -boost::uint16_t usrp1_dboard_iface::read_gpio(unit_t unit) -{ - boost::uint32_t out_value; - - if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) - out_value = _iface->peek32(1); - else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) - out_value = _iface->peek32(2); - else - UHD_THROW_INVALID_CODE_PATH(); - - switch(unit) { - case UNIT_RX: - return (boost::uint16_t)((out_value >> 16) & 0x0000ffff); - case UNIT_TX: - return (boost::uint16_t)((out_value >> 0) & 0x0000ffff); - } - UHD_ASSERT_THROW(false); -} - void usrp1_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value) { @@ -316,6 +368,7 @@ void usrp1_dboard_iface::_set_atr_reg(unit_t unit, else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_RXVAL_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } else if (atr == ATR_REG_FULL_DUPLEX) { switch(unit) { @@ -331,6 +384,7 @@ void usrp1_dboard_iface::_set_atr_reg(unit_t unit, else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_TXVAL_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } } @@ -361,6 +415,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit, return SPI_ENABLE_RX_B; else break; + default: + break; } UHD_THROW_INVALID_CODE_PATH(); } @@ -429,3 +485,23 @@ double usrp1_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); } + +/*********************************************************************** + * Unsupported + **********************************************************************/ + +void usrp1_dboard_iface::set_command_time(const uhd::time_spec_t&) +{ + throw uhd::not_implemented_error("timed command support not implemented"); +} + +uhd::time_spec_t usrp1_dboard_iface::get_command_time() +{ + throw uhd::not_implemented_error("timed command support not implemented"); +} + +void usrp1_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} + diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp index dbd5408e8..5e1a70a8f 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.cpp +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -209,13 +209,13 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ const fs_path mb_path = "/mboards/0"; _tree->create<std::string>(mb_path / "name").set("USRP1"); _tree->create<std::string>(mb_path / "load_eeprom") - .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create user-defined control objects //////////////////////////////////////////////////////////////////// _tree->create<std::pair<boost::uint8_t, boost::uint32_t> >(mb_path / "user" / "regs") - .subscribe(boost::bind(&usrp1_impl::set_reg, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_reg, this, _1)); //////////////////////////////////////////////////////////////////// // setup the mboard eeprom @@ -223,7 +223,7 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, USRP1_EEPROM_MAP_KEY); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects @@ -247,7 +247,7 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ } UHD_MSG(status) << boost::format("Using FPGA clock rate of %fMHz...") % (_master_clock_rate/1e6) << std::endl; _tree->create<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&usrp1_impl::update_tick_rate, this, _1)) + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_tick_rate, this, _1)) .set(_master_clock_rate); //////////////////////////////////////////////////////////////////// @@ -260,13 +260,13 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1)) + .set_coercer(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1)) .set(0.0); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1)) - .publish(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec)) + .add_coerced_subscriber(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1)) + .set_publisher(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec)) .set(0.0); } @@ -281,18 +281,18 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1)); BOOST_FOREACH(const std::string &db, _dbc.keys()){ const fs_path rx_fe_path = mb_path / "rx_frontends" / db; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1)) + .set_coercer(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1)) + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1)) .set(true); } @@ -303,19 +303,19 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ for (size_t dspno = 0; dspno < get_num_ddcs(); dspno++){ fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this)); + .set_publisher(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this)); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default rate - .coerce(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this)); + .set_publisher(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this)); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd"); if (dspno == 0){ - //only subscribe the callback for dspno 0 since it will stream all dsps + //only add_coerced_subscriber the callback for dspno 0 since it will stream all dsps _tree->access<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1)); } } @@ -326,22 +326,22 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ for (size_t dspno = 0; dspno < get_num_ducs(); dspno++){ fs_path tx_dsp_path = mb_path / str(boost::format("tx_dsps/%u") % dspno); _tree->create<meta_range_t>(tx_dsp_path / "rate/range") - .publish(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this)); + .set_publisher(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this)); _tree->create<double>(tx_dsp_path / "rate/value") .set(1e6) //some default rate - .coerce(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1)); _tree->create<double>(tx_dsp_path / "freq/value") - .coerce(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1)); _tree->create<meta_range_t>(tx_dsp_path / "freq/range") - .publish(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this)); + .set_publisher(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this)); } //////////////////////////////////////////////////////////////////// // create time control objects //////////////////////////////////////////////////////////////////// _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl)) - .subscribe(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1)); + .set_publisher(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl)) + .add_coerced_subscriber(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1)); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(std::vector<std::string>(1, "internal")); _tree->create<std::vector<std::string> >(mb_path / "time_source/options").set(std::vector<std::string>(1, "none")); @@ -365,24 +365,23 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1)); //create a new dboard interface and manager - _dbc[db].dboard_iface = make_dboard_iface( + dboard_iface::sptr dboard_iface = make_dboard_iface( _iface, _dbc[db].codec, (db == "A")? DBOARD_SLOT_A : DBOARD_SLOT_B, _master_clock_rate, rx_db_eeprom.id ); - _tree->create<dboard_iface::sptr>(mb_path / "dboards" / db/ "iface").set(_dbc[db].dboard_iface); _dbc[db].dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dbc[db].dboard_iface, _tree->subtree(mb_path / "dboards" / db) + dboard_iface, _tree->subtree(mb_path / "dboards" / db) ); //init the subdev specs if we have a dboard (wont leave this loop empty) diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp index 012bc0794..da901bd6c 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.hpp +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -92,7 +92,6 @@ private: uhd::transport::usb_zero_copy::sptr _data_transport; struct db_container_type{ usrp1_codec_ctrl::sptr codec; - uhd::usrp::dboard_iface::sptr dboard_iface; uhd::usrp::dboard_manager::sptr dboard_manager; }; uhd::dict<std::string, db_container_type> _dbc; diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt index d9894adaf..edf77a654 100644 --- a/host/lib/usrp/usrp2/CMakeLists.txt +++ b/host/lib/usrp/usrp2/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP2 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_USRP2) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp index 7bb69c7b7..a6ba1e0b7 100644 --- a/host/lib/usrp/usrp2/dboard_iface.cpp +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012,2015 Ettus Research LLC +// Copyright 2010-2012,2015,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -54,12 +54,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -71,6 +75,7 @@ public: std::vector<double> get_clock_rates(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn); void write_spi( unit_t unit, @@ -149,18 +154,22 @@ usrp2_dboard_iface::~usrp2_dboard_iface(void){ * Clocks **********************************************************************/ void usrp2_dboard_iface::set_clock_rate(unit_t unit, double rate){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _clock_rates[unit] = rate; //set to shadow switch(unit){ case UNIT_RX: _clock_ctrl->set_rate_rx_dboard_clock(rate); return; case UNIT_TX: _clock_ctrl->set_rate_tx_dboard_clock(rate); return; + default: UHD_THROW_INVALID_CODE_PATH(); } } double usrp2_dboard_iface::get_clock_rate(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_rates[unit]; //get from shadow } std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit){ case UNIT_RX: return _clock_ctrl->get_rates_rx_dboard_clock(); case UNIT_TX: return _clock_ctrl->get_rates_tx_dboard_clock(); @@ -169,40 +178,56 @@ std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ } void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit){ - case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; - case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; + case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; + case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } -double usrp2_dboard_iface::get_codec_rate(unit_t){ +double usrp2_dboard_iface::get_codec_rate(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_ctrl->get_master_clock_rate(); } + /*********************************************************************** * GPIO **********************************************************************/ -void usrp2_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void usrp2_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void usrp2_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t usrp2_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void usrp2_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void usrp2_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t usrp2_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void usrp2_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t usrp2_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void usrp2_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void usrp2_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void usrp2_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t usrp2_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t usrp2_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -219,6 +244,7 @@ void usrp2_dboard_iface::write_spi( boost::uint32_t data, size_t num_bits ){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _spi_iface->write_spi(unit_to_spi_dev[unit], config, data, num_bits); } @@ -228,6 +254,7 @@ boost::uint32_t usrp2_dboard_iface::read_write_spi( boost::uint32_t data, size_t num_bits ){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _spi_iface->read_spi(unit_to_spi_dev[unit], config, data, num_bits); } @@ -250,6 +277,7 @@ void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ (UNIT_RX, SPI_SS_RX_DAC) (UNIT_TX, SPI_SS_TX_DAC) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _spi_iface->write_spi( unit_to_spi_dac[unit], spi_config_t::EDGE_FALL, _dac_regs[unit].get_reg(), 24 @@ -257,6 +285,8 @@ void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ } void usrp2_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + _dac_regs[unit].data = boost::math::iround(4095*value/3.3); _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; @@ -285,6 +315,8 @@ double usrp2_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which){ (UNIT_TX, SPI_SS_TX_ADC) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + //setup spi config args spi_config_t config; config.mosi_edge = spi_config_t::EDGE_FALL; @@ -320,3 +352,8 @@ void usrp2_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _wb_iface->set_time(t); } + +void usrp2_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 7b59dfaf1..b0c29392c 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -474,15 +474,15 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : //////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(_mbc[mb].iface->mb_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); //////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////// _mbc[mb].clock = usrp2_clock_ctrl::make(_mbc[mb].iface, _mbc[mb].spiface); _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) - .subscribe(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); //////////////////////////////////////////////////////////////// // create codec control objects @@ -500,10 +500,10 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : _tree->create<std::string>(rx_codec_path / "name").set("ads62p44"); _tree->create<meta_range_t>(rx_codec_path / "gains/digital/range").set(meta_range_t(0, 6.0, 0.5)); _tree->create<double>(rx_codec_path / "gains/digital/value") - .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); + .add_coerced_subscriber(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); _tree->create<meta_range_t>(rx_codec_path / "gains/fine/range").set(meta_range_t(0, 0.5, 0.05)); _tree->create<double>(rx_codec_path / "gains/fine/value") - .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); + .add_coerced_subscriber(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); }break; case usrp2_iface::USRP2_REV3: @@ -550,7 +550,7 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : BOOST_FOREACH(const std::string &name, _mbc[mb].gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); } } else @@ -563,9 +563,9 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : // and do the misc mboard sensors //////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/mimo_locked") - .publish(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); + .set_publisher(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); + .set_publisher(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); //////////////////////////////////////////////////////////////// // create frontend control objects @@ -578,27 +578,27 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : ); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////// @@ -613,20 +613,20 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : for (size_t dspno = 0; dspno < _mbc[mb].rx_dsps.size(); dspno++){ _mbc[mb].rx_dsps[dspno]->set_link_rate(USRP2_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) - .subscribe(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////// @@ -637,17 +637,17 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : ); _mbc[mb].tx_dsp->set_link_rate(USRP2_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) - .subscribe(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _mbc[mb].tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _mbc[mb].tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _mbc[mb].tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _mbc[mb].tx_dsp)); //setup dsp flow control const double ups_per_sec = device_args_i.cast<double>("ups_per_sec", 20); @@ -670,22 +670,22 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases, mimo_clock_sync_delay_cycles ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)) .set("none"); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)) .set("internal"); std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("mimo"); if (_mbc[mb].gps and _mbc[mb].gps->gps_detected()) clock_sources.push_back("gpsdo"); @@ -697,18 +697,18 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : case usrp2_iface::USRP_N200_R4: case usrp2_iface::USRP_N210_R4: _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1)); default: break; //otherwise, do not register } _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create user-defined control objects //////////////////////////////////////////////////////////////////// _mbc[mb].user = user_settings_core_200::make(_mbc[mb].wbiface, U2_REG_SR_ADDR(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1)); //////////////////////////////////////////////////////////////// // create dboard control objects @@ -726,32 +726,31 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); //create a new dboard interface and manager - _mbc[mb].dboard_iface = make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_mbc[mb].dboard_iface); _mbc[mb].dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _mbc[mb].dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1)); } } diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp index 07cd98b4c..47fcec657 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.hpp +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -102,7 +102,6 @@ private: uhd::transport::zero_copy_if::sptr tx_dsp_xport; uhd::transport::zero_copy_if::sptr fifo_ctrl_xport; uhd::usrp::dboard_manager::sptr dboard_manager; - uhd::usrp::dboard_iface::sptr dboard_iface; size_t rx_chan_occ, tx_chan_occ; mb_container_type(void): rx_chan_occ(0), tx_chan_occ(0){} }; diff --git a/host/lib/usrp/usrp_c.cpp b/host/lib/usrp/usrp_c.cpp index 69f2bd5e5..943f96db0 100644 --- a/host/lib/usrp/usrp_c.cpp +++ b/host/lib/usrp/usrp_c.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015 Ettus Research LLC + * Copyright 2015-2016 Ettus Research LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -837,6 +837,95 @@ uhd_error uhd_usrp_get_fe_rx_freq_range( ) } +UHD_API uhd_error uhd_usrp_get_rx_lo_names( + uhd_usrp_handle h, + size_t chan, + uhd_string_vector_handle rx_lo_names_out +){ + UHD_SAFE_C_SAVE_ERROR(h, + rx_lo_names_out->string_vector_cpp = USRP(h)->get_rx_lo_names(chan); + ) +} + +UHD_API uhd_error uhd_usrp_set_rx_lo_source( + uhd_usrp_handle h, + const char* src, + const char* name, + size_t chan +){ + UHD_SAFE_C_SAVE_ERROR(h, + USRP(h)->set_rx_lo_source(src, name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_get_rx_lo_source( + uhd_usrp_handle h, + const char* name, + size_t chan, + char* rx_lo_source_out, + size_t strbuffer_len +){ + UHD_SAFE_C_SAVE_ERROR(h, + strncpy(rx_lo_source_out, USRP(h)->get_rx_lo_source(name, chan).c_str(), strbuffer_len); + ) +} + +UHD_API uhd_error uhd_usrp_get_rx_lo_sources( + uhd_usrp_handle h, + const char* name, + size_t chan, + uhd_string_vector_handle rx_lo_sources_out +){ + UHD_SAFE_C_SAVE_ERROR(h, + rx_lo_sources_out->string_vector_cpp = USRP(h)->get_rx_lo_sources(name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_set_rx_lo_export_enabled( + uhd_usrp_handle h, + bool enabled, + const char* name, + size_t chan +){ + UHD_SAFE_C_SAVE_ERROR(h, + USRP(h)->set_rx_lo_export_enabled(enabled, name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_get_rx_lo_export_enabled( + uhd_usrp_handle h, + const char* name, + size_t chan, + bool* result_out +) { + UHD_SAFE_C_SAVE_ERROR(h, + *result_out = USRP(h)->get_rx_lo_export_enabled(name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_set_rx_lo_freq( + uhd_usrp_handle h, + double freq, + const char* name, + size_t chan, + double* coerced_freq_out +){ + UHD_SAFE_C_SAVE_ERROR(h, + *coerced_freq_out = USRP(h)->set_rx_lo_freq(freq, name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_get_rx_lo_freq( + uhd_usrp_handle h, + const char* name, + size_t chan, + double* rx_lo_freq_out +){ + UHD_SAFE_C_SAVE_ERROR(h, + *rx_lo_freq_out = USRP(h)->get_rx_lo_freq(name, chan); + ) +} + uhd_error uhd_usrp_set_rx_gain( uhd_usrp_handle h, double gain, diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt index 3d6348eec..ea237b008 100644 --- a/host/lib/usrp/x300/CMakeLists.txt +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -22,10 +22,9 @@ ######################################################################## # Conditionally configure the X300 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_X300) LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/x300_radio_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_uart.cpp @@ -35,7 +34,6 @@ IF(ENABLE_X300) ${CMAKE_CURRENT_SOURCE_DIR}/x300_dboard_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_clock_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_image_loader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/x300_adc_dac_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cdecode.c ) ENDIF(ENABLE_X300) diff --git a/host/lib/usrp/x300/x300_dac_ctrl.cpp b/host/lib/usrp/x300/x300_dac_ctrl.cpp index d49fba383..6ffd1ede4 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.cpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/host/lib/usrp/x300/x300_dac_ctrl.hpp b/host/lib/usrp/x300/x300_dac_ctrl.hpp index f2a407971..ca47a90e7 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.hpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/host/lib/usrp/x300/x300_dboard_iface.cpp b/host/lib/usrp/x300/x300_dboard_iface.cpp index 502630109..b28768f90 100644 --- a/host/lib/usrp/x300/x300_dboard_iface.cpp +++ b/host/lib/usrp/x300/x300_dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2013,2015 Ettus Research LLC +// Copyright 2013,2015,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -15,85 +15,16 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // -#include "x300_impl.hpp" +#include "x300_dboard_iface.hpp" #include "x300_regs.hpp" -#include <uhd/usrp/dboard_iface.hpp> #include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include "ad7922_regs.hpp" //aux adc -#include "ad5623_regs.hpp" //aux dac using namespace uhd; using namespace uhd::usrp; using namespace boost::assign; -class x300_dboard_iface : public dboard_iface -{ -public: - x300_dboard_iface(const x300_dboard_iface_config_t &config); - ~x300_dboard_iface(void); - - special_props_t get_special_props(void) - { - special_props_t props; - props.soft_clock_divider = false; - props.mangle_i2c_addrs = (_config.dboard_slot == 1); - return props; - } - - void write_aux_dac(unit_t, aux_dac_t, double); - double read_aux_adc(unit_t, aux_adc_t); - - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - - void set_command_time(const uhd::time_spec_t& t); - uhd::time_spec_t get_command_time(void); - - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); - - void write_i2c(boost::uint16_t, const byte_vector_t &); - byte_vector_t read_i2c(boost::uint16_t, size_t); - - void set_clock_rate(unit_t, double); - double get_clock_rate(unit_t); - std::vector<double> get_clock_rates(unit_t); - void set_clock_enabled(unit_t, bool); - double get_codec_rate(unit_t); - - void write_spi( - unit_t unit, - const spi_config_t &config, - boost::uint32_t data, - size_t num_bits - ); - - boost::uint32_t read_write_spi( - unit_t unit, - const spi_config_t &config, - boost::uint32_t data, - size_t num_bits - ); - - const x300_dboard_iface_config_t _config; - uhd::dict<unit_t, ad5623_regs_t> _dac_regs; - uhd::dict<unit_t, double> _clock_rates; - void _write_aux_dac(unit_t); - -}; - -/*********************************************************************** - * Make Function - **********************************************************************/ -dboard_iface::sptr x300_make_dboard_iface(const x300_dboard_iface_config_t &config) -{ - return dboard_iface::sptr(new x300_dboard_iface(config)); -} - /*********************************************************************** * Structors **********************************************************************/ @@ -116,27 +47,6 @@ x300_dboard_iface::x300_dboard_iface(const x300_dboard_iface_config_t &config): this->set_clock_enabled(UNIT_RX, false); this->set_clock_enabled(UNIT_TX, false); - - - //some test code - /* - { - - this->write_aux_dac(UNIT_TX, AUX_DAC_A, .1); - this->write_aux_dac(UNIT_TX, AUX_DAC_B, 1); - this->write_aux_dac(UNIT_RX, AUX_DAC_A, 2); - this->write_aux_dac(UNIT_RX, AUX_DAC_B, 3); - while (1) - { - UHD_VAR(this->read_aux_adc(UNIT_TX, AUX_ADC_A)); - UHD_VAR(this->read_aux_adc(UNIT_TX, AUX_ADC_B)); - UHD_VAR(this->read_aux_adc(UNIT_RX, AUX_ADC_A)); - UHD_VAR(this->read_aux_adc(UNIT_RX, AUX_ADC_B)); - sleep(1); - } - } - */ - } x300_dboard_iface::~x300_dboard_iface(void) @@ -153,6 +63,8 @@ x300_dboard_iface::~x300_dboard_iface(void) **********************************************************************/ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + // Just return if the requested rate is already set if (std::fabs(_clock_rates[unit] - rate) < std::numeric_limits<double>::epsilon()) return; @@ -165,17 +77,21 @@ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate) case UNIT_TX: _config.clock->set_dboard_rate(_config.which_tx_clk, rate); break; + default: + UHD_THROW_INVALID_CODE_PATH(); } _clock_rates[unit] = rate; //set to shadow } double x300_dboard_iface::get_clock_rate(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_rates[unit]; //get from shadow } std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit) { case UNIT_RX: @@ -189,6 +105,7 @@ std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit) void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit) { case UNIT_RX: @@ -200,57 +117,74 @@ void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb) } } -double x300_dboard_iface::get_codec_rate(unit_t) +double x300_dboard_iface::get_codec_rate(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _config.clock->get_master_clock_rate(); } /*********************************************************************** * GPIO **********************************************************************/ -void x300_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) +void x300_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_pin_ctrl(unit, value); + _config.gpio->set_pin_ctrl(unit, value, mask); } -void x300_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) +boost::uint32_t x300_dboard_iface::get_pin_ctrl(unit_t unit) { - return _config.gpio->set_gpio_ddr(unit, value); + return _config.gpio->get_pin_ctrl(unit); } -void x300_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) +void x300_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_gpio_out(unit, value); + _config.gpio->set_atr_reg(unit, reg, value, mask); } -boost::uint16_t x300_dboard_iface::read_gpio(unit_t unit) +boost::uint32_t x300_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg) { - return _config.gpio->read_gpio(unit); + return _config.gpio->get_atr_reg(unit, reg); +} + +void x300_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask) +{ + _config.gpio->set_gpio_ddr(unit, value, mask); +} + +boost::uint32_t x300_dboard_iface::get_gpio_ddr(unit_t unit) +{ + return _config.gpio->get_gpio_ddr(unit); } -void x300_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value) +void x300_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_atr_reg(unit, atr, value); + _config.gpio->set_gpio_out(unit, value, mask); } -void x300_dboard_iface::set_gpio_debug(unit_t, int) +boost::uint32_t x300_dboard_iface::get_gpio_out(unit_t unit) { - throw uhd::not_implemented_error("no set_gpio_debug implemented"); + return _config.gpio->get_gpio_out(unit); +} + +boost::uint32_t x300_dboard_iface::read_gpio(unit_t unit) +{ + return _config.gpio->read_gpio(unit); } /*********************************************************************** * SPI **********************************************************************/ -#define toslaveno(unit) \ - (((unit) == dboard_iface::UNIT_TX)? _config.tx_spi_slaveno : _config.rx_spi_slaveno) - void x300_dboard_iface::write_spi( unit_t unit, const spi_config_t &config, boost::uint32_t data, size_t num_bits ){ - _config.spi->write_spi(toslaveno(unit), config, data, num_bits); + boost::uint32_t slave = 0; + if (unit == UNIT_TX) slave |= _config.tx_spi_slaveno; + if (unit == UNIT_RX) slave |= _config.rx_spi_slaveno; + + _config.spi->write_spi(int(slave), config, data, num_bits); } boost::uint32_t x300_dboard_iface::read_write_spi( @@ -259,7 +193,10 @@ boost::uint32_t x300_dboard_iface::read_write_spi( boost::uint32_t data, size_t num_bits ){ - return _config.spi->read_spi(toslaveno(unit), config, data, num_bits); + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + return _config.spi->read_spi( + (unit==dboard_iface::UNIT_TX)?_config.tx_spi_slaveno:_config.rx_spi_slaveno, + config, data, num_bits); } /*********************************************************************** @@ -284,6 +221,7 @@ void x300_dboard_iface::_write_aux_dac(unit_t unit) (UNIT_RX, DB_RX_LSDAC_SEN) (UNIT_TX, DB_TX_LSDAC_SEN) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _config.spi->write_spi( unit_to_spi_dac[unit], spi_config_t::EDGE_FALL, _dac_regs[unit].get_reg(), 24 @@ -292,6 +230,8 @@ void x300_dboard_iface::_write_aux_dac(unit_t unit) void x300_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + _dac_regs[unit].data = boost::math::iround(4095*value/3.3); _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; @@ -321,6 +261,8 @@ double x300_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which) (UNIT_TX, DB_TX_LSADC_SEN) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + //setup spi config args spi_config_t config; config.mosi_edge = spi_config_t::EDGE_FALL; @@ -356,3 +298,25 @@ void x300_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _config.cmd_time_ctrl->set_time(t); } + +void x300_dboard_iface::add_rx_fe( + const std::string& fe_name, + rx_frontend_core_3000::sptr fe_core) +{ + _rx_fes[fe_name] = fe_core; +} + +void x300_dboard_iface::set_fe_connection( + unit_t unit, const std::string& fe_name, + const fe_connection_t& fe_conn) +{ + if (unit == UNIT_RX) { + if (_rx_fes.has_key(fe_name)) { + _rx_fes[fe_name]->set_fe_connection(fe_conn); + } else { + throw uhd::assertion_error("front-end name was not registered: " + fe_name); + } + } else { + throw uhd::not_implemented_error("frontend connection not configurable for TX"); + } +} diff --git a/host/lib/usrp/x300/x300_dboard_iface.hpp b/host/lib/usrp/x300/x300_dboard_iface.hpp new file mode 100644 index 000000000..124d768e8 --- /dev/null +++ b/host/lib/usrp/x300/x300_dboard_iface.hpp @@ -0,0 +1,115 @@ +// +// Copyright 2010-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_X300_DBOARD_IFACE_HPP +#define INCLUDED_X300_DBOARD_IFACE_HPP + +#include "x300_clock_ctrl.hpp" +#include "spi_core_3000.hpp" +#include "i2c_core_100_wb32.hpp" +#include "gpio_atr_3000.hpp" +#include "rx_frontend_core_3000.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include "ad7922_regs.hpp" //aux adc +#include "ad5623_regs.hpp" //aux dac +#include <uhd/types/dict.hpp> + +struct x300_dboard_iface_config_t +{ + uhd::usrp::gpio_atr::db_gpio_atr_3000::sptr gpio; + spi_core_3000::sptr spi; + size_t rx_spi_slaveno; + size_t tx_spi_slaveno; + uhd::i2c_iface::sptr i2c; + x300_clock_ctrl::sptr clock; + x300_clock_which_t which_rx_clk; + x300_clock_which_t which_tx_clk; + boost::uint8_t dboard_slot; + uhd::timed_wb_iface::sptr cmd_time_ctrl; +}; + +class x300_dboard_iface : public uhd::usrp::dboard_iface +{ +public: + x300_dboard_iface(const x300_dboard_iface_config_t &config); + ~x300_dboard_iface(void); + + inline special_props_t get_special_props(void) + { + special_props_t props; + props.soft_clock_divider = false; + props.mangle_i2c_addrs = (_config.dboard_slot == 1); + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, double); + double read_aux_adc(unit_t, aux_adc_t); + + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + + void set_command_time(const uhd::time_spec_t& t); + uhd::time_spec_t get_command_time(void); + + void write_i2c(boost::uint16_t, const uhd::byte_vector_t &); + uhd::byte_vector_t read_i2c(boost::uint16_t, size_t); + + void set_clock_rate(unit_t, double); + double get_clock_rate(unit_t); + std::vector<double> get_clock_rates(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + + void write_spi( + unit_t unit, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + boost::uint32_t read_write_spi( + unit_t unit, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + void set_fe_connection( + unit_t unit, const std::string& name, + const uhd::usrp::fe_connection_t& fe_conn); + + void add_rx_fe( + const std::string& fe_name, + rx_frontend_core_3000::sptr fe_core); + +private: + const x300_dboard_iface_config_t _config; + uhd::dict<unit_t, ad5623_regs_t> _dac_regs; + uhd::dict<unit_t, double> _clock_rates; + uhd::dict<std::string, rx_frontend_core_3000::sptr> _rx_fes; + void _write_aux_dac(unit_t); +}; + + + +#endif /* INCLUDED_X300_DBOARD_IFACE_HPP */ diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h index 549fc9dfa..e05cd9ec7 100644 --- a/host/lib/usrp/x300/x300_fw_common.h +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -33,7 +33,7 @@ extern "C" { #define X300_REVISION_MIN 2 #define X300_FW_COMPAT_MAJOR 4 #define X300_FW_COMPAT_MINOR 0 -#define X300_FPGA_COMPAT_MAJOR 19 +#define X300_FPGA_COMPAT_MAJOR 0x20 //shared memory sections - in between the stack and the program space #define X300_FW_SHMEM_BASE 0x6000 diff --git a/host/lib/usrp/x300/x300_fw_ctrl.cpp b/host/lib/usrp/x300/x300_fw_ctrl.cpp index 3a8d984fb..25960ede0 100644 --- a/host/lib/usrp/x300/x300_fw_ctrl.cpp +++ b/host/lib/usrp/x300/x300_fw_ctrl.cpp @@ -37,6 +37,11 @@ class x300_ctrl_iface : public wb_iface public: enum {num_retries = 3}; + x300_ctrl_iface(bool enable_errors = true) : errors(enable_errors) + { + /* NOP */ + } + void flush(void) { boost::mutex::scoped_lock lock(reg_access); @@ -52,11 +57,11 @@ public: { return this->__poke32(addr, data); } - catch(const std::exception &ex) + catch(const uhd::io_error &ex) { - const std::string error_msg = str(boost::format( + std::string error_msg = str(boost::format( "x300 fw communication failure #%u\n%s") % i % ex.what()); - UHD_MSG(error) << error_msg << std::endl; + if (errors) UHD_MSG(error) << error_msg << std::endl; if (i == num_retries) throw uhd::io_error(error_msg); } } @@ -72,11 +77,11 @@ public: boost::uint32_t data = this->__peek32(addr); return data; } - catch(const std::exception &ex) + catch(const uhd::io_error &ex) { - const std::string error_msg = str(boost::format( + std::string error_msg = str(boost::format( "x300 fw communication failure #%u\n%s") % i % ex.what()); - UHD_MSG(error) << error_msg << std::endl; + if (errors) UHD_MSG(error) << error_msg << std::endl; if (i == num_retries) throw uhd::io_error(error_msg); } } @@ -84,6 +89,8 @@ public: } protected: + bool errors; + virtual void __poke32(const wb_addr_type addr, const boost::uint32_t data) = 0; virtual boost::uint32_t __peek32(const wb_addr_type addr) = 0; virtual void __flush() = 0; @@ -98,8 +105,8 @@ protected: class x300_ctrl_iface_enet : public x300_ctrl_iface { public: - x300_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp): - udp(udp), seq(0) + x300_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true): + x300_ctrl_iface(enable_errors), udp(udp), seq(0) { try { @@ -187,8 +194,8 @@ private: class x300_ctrl_iface_pcie : public x300_ctrl_iface { public: - x300_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy): - _drv_proxy(drv_proxy) + x300_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy, bool enable_errors = true): + x300_ctrl_iface(enable_errors), _drv_proxy(drv_proxy) { nirio_status status = 0; nirio_status_chain(_drv_proxy->set_attribute(RIO_ADDRESS_SPACE, BUS_INTERFACE), status); @@ -289,12 +296,12 @@ private: static const boost::uint32_t INIT_TIMEOUT_IN_MS = 5000; }; -wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp) +wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true) { - return wb_iface::sptr(new x300_ctrl_iface_enet(udp)); + return wb_iface::sptr(new x300_ctrl_iface_enet(udp, enable_errors)); } -wb_iface::sptr x300_make_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy) +wb_iface::sptr x300_make_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy, bool enable_errors = true) { - return wb_iface::sptr(new x300_ctrl_iface_pcie(drv_proxy)); + return wb_iface::sptr(new x300_ctrl_iface_pcie(drv_proxy, enable_errors)); } diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index ce81d5f1f..43ccd26f5 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2013-2015 Ettus Research LLC +// Copyright 2013-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -18,9 +18,9 @@ #include "x300_impl.hpp" #include "x300_lvbitx.hpp" #include "x310_lvbitx.hpp" +#include "apply_corrections.hpp" #include <boost/algorithm/string.hpp> #include <boost/asio.hpp> -#include "apply_corrections.hpp" #include <uhd/utils/static.hpp> #include <uhd/utils/msg.hpp> #include <uhd/utils/paths.hpp> @@ -32,12 +32,14 @@ #include <boost/make_shared.hpp> #include <boost/functional/hash.hpp> #include <boost/assign/list_of.hpp> -#include <fstream> #include <uhd/transport/udp_zero_copy.hpp> #include <uhd/transport/udp_constants.hpp> +#include <uhd/transport/zero_copy_recv_offload.hpp> #include <uhd/transport/nirio_zero_copy.hpp> #include <uhd/transport/nirio/niusrprio_session.h> #include <uhd/utils/platform.hpp> +#include <uhd/types/sid.hpp> +#include <fstream> #define NIUSRPRIO_DEFAULT_RPC_PORT "5444" @@ -45,24 +47,41 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::rfnoc; using namespace uhd::transport; using namespace uhd::niusrprio; +using namespace uhd::usrp::gpio_atr; using namespace uhd::usrp::x300; namespace asio = boost::asio; +static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { + //Possible options: + //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM + //HA = {0:1G, 1:Aurora} w/ DRAM, XA = {0:10G, 1:Aurora} w/ DRAM + + std::string option; + uint32_t sfp0_type = zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_SFP0_TYPE)); + uint32_t sfp1_type = zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_SFP1_TYPE)); + + if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_1G_ETH) { + option = "1G"; + } else if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_10G_ETH) { + option = "HG"; + } else if (sfp0_type == RB_SFP_10G_ETH and sfp1_type == RB_SFP_10G_ETH) { + option = "XG"; + } else if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_AURORA) { + option = "HA"; + } else if (sfp0_type == RB_SFP_10G_ETH and sfp1_type == RB_SFP_AURORA) { + option = "XA"; + } else { + option = "HG"; //Default + } + return option; +} + /*********************************************************************** * Discovery over the udp and pcie transport **********************************************************************/ -static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { - //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM - //HGS = {0:1G, 1:10G} w/ SRAM, XGS = {0:10G, 1:10G} w/ SRAM - - //In the default configuration, UHD does not support the HG and XG images so - //they are never autodetected. - bool eth0XG = (zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_ETH_TYPE0)) == 0x1); - bool eth1XG = (zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_ETH_TYPE1)) == 0x1); - return (eth0XG && eth1XG) ? "XGS" : (eth1XG ? "HGS" : "1G"); -} //@TODO: Refactor the find functions to collapse common code for ethernet and PCIe static device_addrs_t x300_find_with_addr(const device_addr_t &hint) @@ -96,7 +115,12 @@ static device_addrs_t x300_find_with_addr(const device_addr_t &hint) //This operation can throw due to compatibility mismatch. try { - wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected(new_addr["addr"], BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); + wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet( + udp_simple::make_connected(new_addr["addr"], + BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)), + false /* Suppress timeout errors */ + ); + if (x300_impl::is_claimed(zpu_ctrl)) continue; //claimed by another process new_addr["fpga"] = get_fpga_option(zpu_ctrl); @@ -193,7 +217,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu if (get_pcie_zpu_iface_registry().has_key(resource_d)) { zpu_ctrl = get_pcie_zpu_iface_registry()[resource_d].lock(); } else { - zpu_ctrl = x300_make_ctrl_iface_pcie(kernel_proxy); + zpu_ctrl = x300_make_ctrl_iface_pcie(kernel_proxy, false /* suppress timeout errors */); //We don't put this zpu_ctrl in the registry because we need //a persistent niriok_proxy associated with the object } @@ -215,7 +239,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu //set these values as empty string so the device may still be found //and the filter's below can still operate on the discovered device if (not hint.has_key("fpga")) { - new_addr["fpga"] = "HGS"; + new_addr["fpga"] = "HG"; } new_addr["name"] = ""; new_addr["serial"] = ""; @@ -348,18 +372,18 @@ static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_nam if ((i & 0x1fff) == 0) UHD_MSG(status) << "." << std::flush; } + //Wait for fimrware to reboot. 3s is an upper bound + boost::this_thread::sleep(boost::posix_time::milliseconds(3000)); UHD_MSG(status) << " done!" << std::endl; } -x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) +x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) + : device3_impl() + , _sid_framer(0) { UHD_MSG(status) << "X300 initialization sequence..." << std::endl; - _type = device::USRP; _ignore_cal_file = dev_addr.has_key("ignore-cal-file"); - _async_md.reset(new async_md_type(1000/*messages deep*/)); - _tree = uhd::property_tree::make(); _tree->create<std::string>("/name").set("X-Series Device"); - _sid_framer = 0; const device_addrs_t device_args = separate_device_addr(dev_addr); _mb.resize(device_args.size()); @@ -369,13 +393,134 @@ x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) } } +void x300_impl::mboard_members_t::discover_eth( + const mboard_eeprom_t mb_eeprom, + const std::vector<std::string> &ip_addrs) +{ + // Clear any previous addresses added + eth_conns.clear(); + + // Index the MB EEPROM addresses + std::vector<std::string> mb_eeprom_addrs; + const size_t num_mb_eeprom_addrs = 4; + for (size_t i = 0; i < num_mb_eeprom_addrs; i++) { + const std::string key = "ip-addr" + boost::to_string(i); + + // Show a warning if there exists duplicate addresses in the mboard eeprom + if (std::find(mb_eeprom_addrs.begin(), mb_eeprom_addrs.end(), mb_eeprom[key]) != mb_eeprom_addrs.end()) { + UHD_MSG(warning) << str(boost::format( + "Duplicate IP address %s found in mboard EEPROM. " + "Device may not function properly.\nView and reprogram the values " + "using the usrp_burn_mb_eeprom utility.\n") % mb_eeprom[key]); + } + mb_eeprom_addrs.push_back(mb_eeprom[key]); + } + + BOOST_FOREACH(const std::string& addr, ip_addrs) { + x300_eth_conn_t conn_iface; + conn_iface.addr = addr; + conn_iface.type = X300_IFACE_NONE; + + // Decide from the mboard eeprom what IP corresponds + // to an interface + for (size_t i = 0; i < mb_eeprom_addrs.size(); i++) { + if (addr == mb_eeprom_addrs[i]) { + // Choose the interface based on the index parity + if (i % 2 == 0) { + conn_iface.type = X300_IFACE_ETH0; + } else { + conn_iface.type = X300_IFACE_ETH1; + } + break; + } + } + + // Check default IP addresses if we couldn't + // determine the IP from the mboard eeprom + if (conn_iface.type == X300_IFACE_NONE) { + UHD_MSG(warning) << str(boost::format( + "Address %s not found in mboard EEPROM. Address may be wrong or " + "the EEPROM may be corrupt.\n Attempting to continue with default " + "IP addresses.\n") % conn_iface.addr + ); + + if (addr == boost::asio::ip::address_v4( + boost::uint32_t(X300_DEFAULT_IP_ETH0_1G)).to_string()) { + conn_iface.type = X300_IFACE_ETH0; + } else if (addr == boost::asio::ip::address_v4( + boost::uint32_t(X300_DEFAULT_IP_ETH1_1G)).to_string()) { + conn_iface.type = X300_IFACE_ETH1; + } else if (addr == boost::asio::ip::address_v4( + boost::uint32_t(X300_DEFAULT_IP_ETH0_10G)).to_string()) { + conn_iface.type = X300_IFACE_ETH0; + } else if (addr == boost::asio::ip::address_v4( + boost::uint32_t(X300_DEFAULT_IP_ETH1_10G)).to_string()) { + conn_iface.type = X300_IFACE_ETH1; + } else { + throw uhd::assertion_error(str(boost::format( + "X300 Initialization Error: Failed to match address %s with " + "any addresses for the device. Please check the address.") + % conn_iface.addr + )); + } + } + + // Save to a vector of connections + if (conn_iface.type != X300_IFACE_NONE) { + // Check the address before we add it + try + { + wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet( + udp_simple::make_connected(conn_iface.addr, + BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)), + false /* Suppress timeout errors */ + ); + + // Peek the ZPU ctrl to make sure this connection works + zpu_ctrl->peek32(0); + } + + // If the address does not work, throw an error + catch(std::exception &) + { + throw uhd::io_error(str(boost::format( + "X300 Initialization Error: Invalid address %s") + % conn_iface.addr)); + } + eth_conns.push_back(conn_iface); + } + } + + if (eth_conns.size() == 0) + throw uhd::assertion_error("X300 Initialization Error: No ethernet interfaces specified."); +} + void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) { const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i); mboard_members_t &mb = _mb[mb_i]; mb.initialization_done = false; - mb.addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"]; + std::vector<std::string> eth_addrs; + // Not choosing eth0 based on resource might cause user issues + std::string eth0_addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"]; + eth_addrs.push_back(eth0_addr); + + mb.next_src_addr = 0; //Host source address for blocks + if (dev_addr.has_key("second_addr")) { + std::string eth1_addr = dev_addr["second_addr"]; + + // Ensure we do not have duplicate addresses + if (eth1_addr != eth0_addr) + eth_addrs.push_back(eth1_addr); + } + + // Initially store the first address provided to setup communication + // Once we read the eeprom, we use it to map IP to its interface + x300_eth_conn_t init; + init.addr = eth_addrs[0]; + mb.eth_conns.push_back(init); + mb.xport_path = dev_addr.has_key("resource") ? "nirio" : "eth"; mb.if_pkt_is_big_endian = mb.xport_path != "nirio"; @@ -413,6 +558,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) const boost::uint32_t tx_data_fifos[2] = {X300_RADIO_DEST_PREFIX_TX, X300_RADIO_DEST_PREFIX_TX + 3}; mb.rio_fpga_interface->get_kernel_proxy()->get_rio_quirks().register_tx_streams(tx_data_fifos, 2); + _tree->create<size_t>(mb_path / "mtu/recv").set(X300_PCIE_RX_DATA_FRAME_SIZE); + _tree->create<size_t>(mb_path / "mtu/send").set(X300_PCIE_TX_DATA_FRAME_SIZE); _tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_PCIE); } @@ -452,7 +599,28 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) // Detect the frame size on the path to the USRP try { - _max_frame_sizes = determine_max_frame_size(mb.addr, req_max_frame_size); + frame_size_t pri_frame_sizes = determine_max_frame_size( + eth_addrs.at(0), req_max_frame_size + ); + + _max_frame_sizes = pri_frame_sizes; + if (eth_addrs.size() > 1) { + frame_size_t sec_frame_sizes = determine_max_frame_size( + eth_addrs.at(1), req_max_frame_size + ); + + // Choose the minimum of the max frame sizes + // to ensure we don't exceed any one of the links' MTU + _max_frame_sizes.recv_frame_size = std::min( + pri_frame_sizes.recv_frame_size, + sec_frame_sizes.recv_frame_size + ); + + _max_frame_sizes.send_frame_size = std::min( + pri_frame_sizes.send_frame_size, + sec_frame_sizes.send_frame_size + ); + } } catch(std::exception &e) { UHD_MSG(error) << e.what() << std::endl; } @@ -479,6 +647,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) << std::endl; } + _tree->create<size_t>(mb_path / "mtu/recv").set(_max_frame_sizes.recv_frame_size); + _tree->create<size_t>(mb_path / "mtu/send").set(std::min(_max_frame_sizes.send_frame_size, X300_1GE_DATA_FRAME_MAX_SIZE)); _tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_10GIGE); } @@ -486,15 +656,15 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) UHD_MSG(status) << "Setup basic communication..." << std::endl; if (mb.xport_path == "nirio") { boost::mutex::scoped_lock(pcie_zpu_iface_registry_mutex); - if (get_pcie_zpu_iface_registry().has_key(mb.addr)) { + if (get_pcie_zpu_iface_registry().has_key(mb.get_pri_eth().addr)) { throw uhd::assertion_error("Someone else has a ZPU transport to the device open. Internal error!"); } else { mb.zpu_ctrl = x300_make_ctrl_iface_pcie(mb.rio_fpga_interface->get_kernel_proxy()); - get_pcie_zpu_iface_registry()[mb.addr] = boost::weak_ptr<wb_iface>(mb.zpu_ctrl); + get_pcie_zpu_iface_registry()[mb.get_pri_eth().addr] = boost::weak_ptr<wb_iface>(mb.zpu_ctrl); } } else { - mb.zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected(mb.addr, - BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); + mb.zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected( + mb.get_pri_eth().addr, BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); } mb.claimer_task = uhd::task::make(boost::bind(&x300_impl::claimer_loop, this, mb.zpu_ctrl)); @@ -561,7 +731,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) const mboard_eeprom_t mb_eeprom(*eeprom16, "X300"); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1)); bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom"); if (recover_mb_eeprom) { @@ -593,27 +763,9 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //////////////////////////////////////////////////////////////////// // determine routing based on address match //////////////////////////////////////////////////////////////////// - mb.router_dst_here = X300_XB_DST_E0; //some default if eeprom not match - if (mb.xport_path == "nirio") { - mb.router_dst_here = X300_XB_DST_PCI; - } else { - if (mb.addr == mb_eeprom["ip-addr0"]) mb.router_dst_here = X300_XB_DST_E0; - else if (mb.addr == mb_eeprom["ip-addr1"]) mb.router_dst_here = X300_XB_DST_E1; - else if (mb.addr == mb_eeprom["ip-addr2"]) mb.router_dst_here = X300_XB_DST_E0; - else if (mb.addr == mb_eeprom["ip-addr3"]) mb.router_dst_here = X300_XB_DST_E1; - else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH0_1G)).to_string()) mb.router_dst_here = X300_XB_DST_E0; - else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH1_1G)).to_string()) mb.router_dst_here = X300_XB_DST_E1; - else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH0_10G)).to_string()) mb.router_dst_here = X300_XB_DST_E0; - else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH1_10G)).to_string()) mb.router_dst_here = X300_XB_DST_E1; - } - - //////////////////////////////////////////////////////////////////// - // read dboard eeproms - //////////////////////////////////////////////////////////////////// - for (size_t i = 0; i < 8; i++) - { - if (i == 0 or i == 2) continue; //not used - mb.db_eeproms[i].load(*mb.zpu_i2c, 0x50 | i); + if (mb.xport_path != "nirio") { + // Discover ethernet interfaces + mb.discover_eth(mb_eeprom, eth_addrs); } //////////////////////////////////////////////////////////////////// @@ -678,15 +830,14 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //Initialize clock source to use internal reference and generate //a valid radio clock. This may change after configuration is done. //This will configure the LMK and wait for lock - update_clock_source(mb, "internal"); + update_clock_source(mb, X300_DEFAULT_CLOCK_SOURCE); //////////////////////////////////////////////////////////////////// // create clock properties //////////////////////////////////////////////////////////////////// - _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)); - - _tree->create<time_spec_t>(mb_path / "time" / "cmd"); + _tree->create<double>(mb_path / "master_clock_rate") + .set_publisher(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)) + ; UHD_MSG(status) << "Radio 1x clock:" << (mb.clock->get_master_clock_rate()/1e6) << std::endl; @@ -713,7 +864,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) BOOST_FOREACH(const std::string &name, mb.gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, mb.gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, mb.gps, name)); } } else @@ -729,69 +880,27 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, i), 0); } - //////////////////////////////////////////////////////////////////// - // setup radios - //////////////////////////////////////////////////////////////////// - this->setup_radio(mb_i, "A", dev_addr); - this->setup_radio(mb_i, "B", dev_addr); - - //////////////////////////////////////////////////////////////////// - // ADC test and cal - //////////////////////////////////////////////////////////////////// - if (dev_addr.has_key("self_cal_adc_delay")) { - self_cal_adc_xfer_delay(mb, true /* Apply ADC delay */); - } - if (dev_addr.has_key("ext_adc_self_test")) { - extended_adc_test(mb, dev_addr.cast<double>("ext_adc_self_test", 30)); - } else if ( ! dev_addr.has_key("disable_adc_self_test") ) { - self_test_adcs(mb); - } - - //////////////////////////////////////////////////////////////////// - // front panel gpio - //////////////////////////////////////////////////////////////////// - mb.fp_gpio = gpio_core_200::make(mb.radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); - BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) - { - _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr.second) - .set(0) - .subscribe(boost::bind(&x300_impl::set_fp_gpio, this, mb.fp_gpio, attr.first, _1)); - } - _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") - .publish(boost::bind(&x300_impl::get_fp_gpio, this, mb.fp_gpio)); - - //////////////////////////////////////////////////////////////////// - // register the time keepers - only one can be the highlander - //////////////////////////////////////////////////////////////////// - _tree->create<time_spec_t>(mb_path / "time" / "now") - .publish(boost::bind(&time_core_3000::get_time_now, mb.radio_perifs[0].time64)) - .subscribe(boost::bind(&x300_impl::sync_times, this, mb, _1)) - .set(0.0); - _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, mb.radio_perifs[0].time64)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[0].time64, _1)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[1].time64, _1)); //////////////////////////////////////////////////////////////////// // setup time sources and properties //////////////////////////////////////////////////////////////////// _tree->create<std::string>(mb_path / "time_source" / "value") .set("internal") - .subscribe(boost::bind(&x300_impl::update_time_source, this, boost::ref(mb), _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::update_time_source, this, boost::ref(mb), _1)); static const std::vector<std::string> time_sources = boost::assign::list_of("internal")("external")("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources); //setup the time output, default to ON _tree->create<bool>(mb_path / "time_source" / "output") - .subscribe(boost::bind(&x300_impl::set_time_source_out, this, boost::ref(mb), _1)) + .add_coerced_subscriber(boost::bind(&x300_impl::set_time_source_out, this, boost::ref(mb), _1)) .set(true); //////////////////////////////////////////////////////////////////// // setup clock sources and properties //////////////////////////////////////////////////////////////////// _tree->create<std::string>(mb_path / "clock_source" / "value") - .set("internal") - .subscribe(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1)); + .set(X300_DEFAULT_CLOCK_SOURCE) + .add_coerced_subscriber(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1)); static const std::vector<std::string> clock_source_options = boost::assign::list_of("internal")("external")("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_source_options); @@ -807,54 +916,70 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //setup the clock output, default to ON _tree->create<bool>(mb_path / "clock_source" / "output") - .subscribe(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1)); + .add_coerced_subscriber(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1)); //initialize tick rate (must be done before setting time) - _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&x300_impl::set_tick_rate, this, boost::ref(mb), _1)) - .subscribe(boost::bind(&x300_impl::update_tick_rate, this, boost::ref(mb), _1)) - .set(mb.clock->get_master_clock_rate()); - - //////////////////////////////////////////////////////////////////// - // create frontend mapping - //////////////////////////////////////////////////////////////////// - std::vector<size_t> default_map(2, 0); default_map[1] = 1; - _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); - _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); - _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&x300_impl::update_subdev_spec, this, "rx", mb_i, _1)); - _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&x300_impl::update_subdev_spec, this, "tx", mb_i, _1)); + _tree->create<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&device3_impl::update_tx_streamers, this, _1)) + .add_coerced_subscriber(boost::bind(&device3_impl::update_rx_streamers, this, _1)) + .set(mb.clock->get_master_clock_rate()) + ; //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") - .publish(boost::bind(&x300_impl::get_ref_locked, this, mb)); + .set_publisher(boost::bind(&x300_impl::get_ref_locked, this, mb)); + + //////////////// RFNOC ///////////////// + const size_t n_rfnoc_blocks = mb.zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_NUM_CE)); + enumerate_rfnoc_blocks( + mb_i, + n_rfnoc_blocks, + X300_XB_DST_PCI + 1, /* base port */ + uhd::sid_t(X300_SRC_ADDR0, 0, X300_DST_ADDR + mb_i, 0), + dev_addr, + mb.if_pkt_is_big_endian ? ENDIANNESS_BIG : ENDIANNESS_LITTLE + ); + //////////////// RFNOC ///////////////// + + // If we have a radio, we must configure its codec control: + const std::string radio_blockid_hint = str(boost::format("%d/Radio") % mb_i); + std::vector<rfnoc::block_id_t> radio_ids = + find_blocks<rfnoc::x300_radio_ctrl_impl>(radio_blockid_hint); + if (not radio_ids.empty()) { + if (radio_ids.size() > 2) { + UHD_MSG(warning) << "Too many Radio Blocks found. Using only the first two." << std::endl; + radio_ids.resize(2); + } - //////////////////////////////////////////////////////////////////// - // do some post-init tasks - //////////////////////////////////////////////////////////////////// - subdev_spec_t rx_fe_spec, tx_fe_spec; - rx_fe_spec.push_back(subdev_spec_pair_t("A", - _tree->list(mb_path / "dboards" / "A" / "rx_frontends").at(0))); - rx_fe_spec.push_back(subdev_spec_pair_t("B", - _tree->list(mb_path / "dboards" / "B" / "rx_frontends").at(0))); - tx_fe_spec.push_back(subdev_spec_pair_t("A", - _tree->list(mb_path / "dboards" / "A" / "tx_frontends").at(0))); - tx_fe_spec.push_back(subdev_spec_pair_t("B", - _tree->list(mb_path / "dboards" / "B" / "tx_frontends").at(0))); - - _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_fe_spec); - _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_fe_spec); - - mb.regmap_db = boost::make_shared<uhd::soft_regmap_db_t>(); - mb.regmap_db->add(*mb.fw_regmap); - mb.regmap_db->add(*mb.radio_perifs[0].regmap); - mb.regmap_db->add(*mb.radio_perifs[1].regmap); - - _tree->create<uhd::soft_regmap_accessor_t::sptr>(mb_path / "registers") - .set(mb.regmap_db); + BOOST_FOREACH(const rfnoc::block_id_t &id, radio_ids) { + rfnoc::x300_radio_ctrl_impl::sptr radio(get_block_ctrl<rfnoc::x300_radio_ctrl_impl>(id)); + mb.radios.push_back(radio); + radio->setup_radio(mb.zpu_i2c, mb.clock, dev_addr.has_key("self_cal_adc_delay")); + } + + //////////////////////////////////////////////////////////////////// + // ADC test and cal + //////////////////////////////////////////////////////////////////// + if (dev_addr.has_key("self_cal_adc_delay")) { + rfnoc::x300_radio_ctrl_impl::self_cal_adc_xfer_delay( + mb.radios, mb.clock, + boost::bind(&x300_impl::wait_for_clk_locked, this, mb, fw_regmap_t::clk_status_reg_t::LMK_LOCK, _1), + true /* Apply ADC delay */); + } + if (dev_addr.has_key("ext_adc_self_test")) { + rfnoc::x300_radio_ctrl_impl::extended_adc_test( + mb.radios, + dev_addr.cast<double>("ext_adc_self_test", 30)); + } else if (not dev_addr.has_key("recover_mb_eeprom")){ + for (size_t i = 0; i < mb.radios.size(); i++) { + mb.radios.at(i)->self_test_adc(); + } + } + } else { + UHD_MSG(status) << "No Radio Block found. Assuming radio-less operation." << std::endl; + } mb.initialization_done = true; } @@ -865,14 +990,6 @@ x300_impl::~x300_impl(void) { BOOST_FOREACH(mboard_members_t &mb, _mb) { - //Disable/reset ADC/DAC - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); - mb.radio_perifs[0].regmap->misc_outs_reg.flush(); - mb.radio_perifs[1].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); - mb.radio_perifs[1].regmap->misc_outs_reg.flush(); - //kill the claimer task and unclaim the device mb.claimer_task.reset(); { //Critical section @@ -881,7 +998,7 @@ x300_impl::~x300_impl(void) mb.zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_SRC), 0); //If the process is killed, the entire registry will disappear so we //don't need to worry about unclean shutdowns here. - get_pcie_zpu_iface_registry().pop(mb.addr); + get_pcie_zpu_iface_registry().pop(mb.get_pri_eth().addr); } } } @@ -891,270 +1008,144 @@ x300_impl::~x300_impl(void) } } -void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, const uhd::device_addr_t &dev_addr) +uint32_t x300_impl::allocate_pcie_dma_chan(const uhd::sid_t &tx_sid, const xport_type_t xport_type) { - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i); - UHD_ASSERT_THROW(mb_i < _mb.size()); - mboard_members_t &mb = _mb[mb_i]; - const size_t radio_index = mb.get_radio_index(slot_name); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - - UHD_MSG(status) << boost::format("Initialize Radio%d control...") % radio_index << std::endl; - - //////////////////////////////////////////////////////////////////// - // radio control - //////////////////////////////////////////////////////////////////// - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t ctrl_sid; - both_xports_t xport = this->make_transport(mb_i, dest, X300_RADIO_DEST_PREFIX_CTRL, device_addr_t(), ctrl_sid); - perif.ctrl = radio_ctrl_core_3000::make(mb.if_pkt_is_big_endian, xport.recv, xport.send, ctrl_sid, slot_name); - - perif.regmap = boost::make_shared<radio_regmap_t>(radio_index); - perif.regmap->initialize(*perif.ctrl, true); - - //Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1 - if (radio_index == 0) { - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - perif.regmap->misc_outs_reg.flush(); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); - perif.regmap->misc_outs_reg.flush(); - } - perif.regmap->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); - - this->register_loopback_self_test(perif.ctrl); - - //////////////////////////////////////////////////////////////// - // Setup peripherals - //////////////////////////////////////////////////////////////// - perif.spi = spi_core_3000::make(perif.ctrl, radio::sr_addr(radio::SPI), radio::RB32_SPI); - perif.adc = x300_adc_ctrl::make(perif.spi, DB_ADC_SEN); - perif.dac = x300_dac_ctrl::make(perif.spi, DB_DAC_SEN, mb.clock->get_master_clock_rate()); - perif.leds = gpio_core_200_32wo::make(perif.ctrl, radio::sr_addr(radio::LEDS)); - perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::RX_FRONT)); - perif.rx_fe->set_dc_offset(rx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - perif.rx_fe->set_dc_offset_auto(rx_frontend_core_200::DEFAULT_DC_OFFSET_ENABLE); - perif.tx_fe = tx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::TX_FRONT)); - perif.tx_fe->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - perif.tx_fe->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); - perif.framer = rx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_CTRL)); - perif.ddc = rx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_DSP)); - perif.ddc->set_link_rate(10e9/8); //whatever - perif.deframer = tx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_CTRL)); - perif.duc = tx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_DSP)); - perif.duc->set_link_rate(10e9/8); //whatever - - //////////////////////////////////////////////////////////////////// - // create time control objects - //////////////////////////////////////////////////////////////////// - time_core_3000::readback_bases_type time64_rb_bases; - time64_rb_bases.rb_now = radio::RB64_TIME_NOW; - time64_rb_bases.rb_pps = radio::RB64_TIME_PPS; - perif.time64 = time_core_3000::make(perif.ctrl, radio::sr_addr(radio::TIME), time64_rb_bases); - - //Capture delays are calibrated every time. The status is only printed is the user - //asks to run the xfer self cal using "self_cal_adc_delay" - self_cal_adc_capture_delay(mb, radio_index, dev_addr.has_key("self_cal_adc_delay")); - - _tree->access<time_spec_t>(mb_path / "time" / "cmd") - .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); - - //////////////////////////////////////////////////////////////// - // create codec control objects - //////////////////////////////////////////////////////////////// - _tree->create<int>(mb_path / "rx_codecs" / slot_name / "gains"); //phony property so this dir exists - _tree->create<int>(mb_path / "tx_codecs" / slot_name / "gains"); //phony property so this dir exists - _tree->create<std::string>(mb_path / "rx_codecs" / slot_name / "name").set("ads62p48"); - _tree->create<std::string>(mb_path / "tx_codecs" / slot_name / "name").set("ad9146"); - - _tree->create<meta_range_t>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); - _tree->create<double>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "value") - .subscribe(boost::bind(&x300_adc_ctrl::set_gain, perif.adc, _1)).set(0); - - //////////////////////////////////////////////////////////////////// - // front end corrections - //////////////////////////////////////////////////////////////////// - perif.rx_fe->populate_subtree(_tree->subtree(mb_path / "rx_frontends" / slot_name)); - perif.tx_fe->populate_subtree(_tree->subtree(mb_path / "tx_frontends" / slot_name)); - - //////////////////////////////////////////////////////////////////// - // connect rx dsp control objects - //////////////////////////////////////////////////////////////////// - const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % radio_index); - perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); - _tree->access<double>(rx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&x300_impl::update_rx_samp_rate, this, boost::ref(mb), radio_index, _1)) - ; - _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); - - //////////////////////////////////////////////////////////////////// - // connect tx dsp control objects - //////////////////////////////////////////////////////////////////// - const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % radio_index); - perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); - _tree->access<double>(tx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&x300_impl::update_tx_samp_rate, this, boost::ref(mb), radio_index, _1)) - ; - - //////////////////////////////////////////////////////////////////// - // create RF frontend interfacing - //////////////////////////////////////////////////////////////////// - const fs_path db_path = (mb_path / "dboards" / slot_name); - const size_t j = (slot_name == "B")? 0x2 : 0x0; - _tree->create<dboard_eeprom_t>(db_path / "rx_eeprom") - .set(mb.db_eeproms[X300_DB0_RX_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_RX_EEPROM | j), _1)); - _tree->create<dboard_eeprom_t>(db_path / "tx_eeprom") - .set(mb.db_eeproms[X300_DB0_TX_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_TX_EEPROM | j), _1)); - _tree->create<dboard_eeprom_t>(db_path / "gdb_eeprom") - .set(mb.db_eeproms[X300_DB0_GDB_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_GDB_EEPROM | j), _1)); - - //create a new dboard interface - x300_dboard_iface_config_t db_config; - db_config.gpio = gpio_core_200::make(perif.ctrl, radio::sr_addr(radio::GPIO), radio::RB32_GPIO); - db_config.spi = perif.spi; - db_config.rx_spi_slaveno = DB_RX_SEN; - db_config.tx_spi_slaveno = DB_TX_SEN; - db_config.i2c = mb.zpu_i2c; - db_config.clock = mb.clock; - db_config.which_rx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; - db_config.which_tx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; - db_config.dboard_slot = (slot_name == "A")? 0 : 1; - db_config.cmd_time_ctrl = perif.ctrl; - _dboard_ifaces[db_path] = x300_make_dboard_iface(db_config); - - //create a new dboard manager - _tree->create<dboard_iface::sptr>(db_path / "iface").set(_dboard_ifaces[db_path]); - _dboard_managers[db_path] = dboard_manager::make( - mb.db_eeproms[X300_DB0_RX_EEPROM | j].id, - mb.db_eeproms[X300_DB0_TX_EEPROM | j].id, - mb.db_eeproms[X300_DB0_GDB_EEPROM | j].id, - _dboard_ifaces[db_path], - _tree->subtree(db_path) - ); - - //now that dboard is created -- register into rx antenna event - const std::string fe_name = _tree->list(db_path / "rx_frontends").front(); - _tree->access<std::string>(db_path / "rx_frontends" / fe_name / "antenna" / "value") - .subscribe(boost::bind(&x300_impl::update_atr_leds, this, mb.radio_perifs[radio_index].leds, _1)); - this->update_atr_leds(mb.radio_perifs[radio_index].leds, ""); //init anyway, even if never called - - //bind frontend corrections to the dboard freq props - const fs_path db_tx_fe_path = db_path / "tx_frontends"; - BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { - _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&x300_impl::set_tx_fe_corrections, this, mb_path, slot_name, _1)); - } - const fs_path db_rx_fe_path = db_path / "rx_frontends"; - BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { - _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&x300_impl::set_rx_fe_corrections, this, mb_path, slot_name, _1)); - } -} + static const uint32_t CTRL_CHANNEL = 0; + static const uint32_t FIRST_DATA_CHANNEL = 1; + if (xport_type == CTRL) { + return CTRL_CHANNEL; + } else { + // sid_t has no comparison defined + uint32_t raw_sid = tx_sid.get(); -void x300_impl::set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) -{ - if(not _ignore_cal_file){ - apply_rx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); - } -} + if (_dma_chan_pool.count(raw_sid) == 0) { + _dma_chan_pool[raw_sid] = _dma_chan_pool.size() + FIRST_DATA_CHANNEL; + UHD_MSG(status) << "[X300] Assigning PCIe DMA channel " << _dma_chan_pool[raw_sid] + << " to SID " << tx_sid.to_pp_string_hex() << std::endl; + } -void x300_impl::set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) -{ - if(not _ignore_cal_file){ - apply_tx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); + if (_dma_chan_pool.size() + FIRST_DATA_CHANNEL > X300_PCIE_MAX_CHANNELS) { + throw uhd::runtime_error("Trying to allocate more DMA channels than are available"); + } + return _dma_chan_pool[raw_sid]; } } -boost::uint32_t get_pcie_dma_channel(boost::uint8_t destination, boost::uint8_t prefix) -{ - static const boost::uint32_t RADIO_GRP_SIZE = 3; - static const boost::uint32_t RADIO0_GRP = 0; - static const boost::uint32_t RADIO1_GRP = 1; - - boost::uint32_t radio_grp = (destination == X300_XB_DST_R0) ? RADIO0_GRP : RADIO1_GRP; - return ((radio_grp * RADIO_GRP_SIZE) + prefix); +static boost::uint32_t extract_sid_from_pkt(void* pkt, size_t) { + return uhd::sid_t(uhd::wtohx(static_cast<const boost::uint32_t*>(pkt)[1])).get_dst(); } - -x300_impl::both_xports_t x300_impl::make_transport( - const size_t mb_index, - const boost::uint8_t& destination, - const boost::uint8_t& prefix, - const uhd::device_addr_t& args, - boost::uint32_t& sid) -{ +uhd::both_xports_t x300_impl::make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args +) { + const size_t mb_index = address.get_dst_addr() - X300_DST_ADDR; mboard_members_t &mb = _mb[mb_index]; - both_xports_t xports; - - sid_config_t config; - config.router_addr_there = X300_DEVICE_THERE; - config.dst_prefix = prefix; - config.router_dst_there = destination; - config.router_dst_here = mb.router_dst_here; - sid = this->allocate_sid(mb, config); - - static const uhd::device_addr_t DEFAULT_XPORT_ARGS; - - const uhd::device_addr_t& xport_args = - (prefix != X300_RADIO_DEST_PREFIX_CTRL) ? args : DEFAULT_XPORT_ARGS; - + const uhd::device_addr_t& xport_args = (xport_type == CTRL) ? uhd::device_addr_t() : args; zero_copy_xport_params default_buff_args; + both_xports_t xports; if (mb.xport_path == "nirio") { - default_buff_args.send_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_TX) - ? X300_PCIE_TX_DATA_FRAME_SIZE - : X300_PCIE_MSG_FRAME_SIZE; - - default_buff_args.recv_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_RX) - ? X300_PCIE_RX_DATA_FRAME_SIZE - : X300_PCIE_MSG_FRAME_SIZE; - - default_buff_args.num_send_frames = - (prefix == X300_RADIO_DEST_PREFIX_TX) - ? X300_PCIE_DATA_NUM_FRAMES - : X300_PCIE_MSG_NUM_FRAMES; - - default_buff_args.num_recv_frames = - (prefix == X300_RADIO_DEST_PREFIX_RX) - ? X300_PCIE_DATA_NUM_FRAMES - : X300_PCIE_MSG_NUM_FRAMES; - - xports.recv = nirio_zero_copy::make( - mb.rio_fpga_interface, - get_pcie_dma_channel(destination, prefix), - default_buff_args, - xport_args); + xports.send_sid = this->allocate_sid(mb, address, X300_SRC_ADDR0, X300_XB_DST_PCI); + xports.recv_sid = xports.send_sid.reversed(); + + uint32_t dma_channel_num = allocate_pcie_dma_chan(xports.send_sid, xport_type); + if (xport_type == CTRL) { + //Transport for control stream + if (_ctrl_dma_xport.get() == NULL) { + //One underlying DMA channel will handle + //all control traffic + zero_copy_xport_params ctrl_buff_args; + ctrl_buff_args.send_frame_size = X300_PCIE_MSG_FRAME_SIZE; + ctrl_buff_args.recv_frame_size = X300_PCIE_MSG_FRAME_SIZE; + ctrl_buff_args.num_send_frames = X300_PCIE_MSG_NUM_FRAMES * X300_PCIE_MAX_MUXED_XPORTS; + ctrl_buff_args.num_recv_frames = X300_PCIE_MSG_NUM_FRAMES * X300_PCIE_MAX_MUXED_XPORTS; + + zero_copy_if::sptr base_xport = nirio_zero_copy::make( + mb.rio_fpga_interface, dma_channel_num, + ctrl_buff_args, uhd::device_addr_t()); + _ctrl_dma_xport = muxed_zero_copy_if::make(base_xport, extract_sid_from_pkt, X300_PCIE_MAX_MUXED_XPORTS); + } + //Create a virtual control transport + xports.recv = _ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); + } else { + //Transport for data stream + default_buff_args.send_frame_size = + (xport_type == TX_DATA) + ? X300_PCIE_TX_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.recv_frame_size = + (xport_type == RX_DATA) + ? X300_PCIE_RX_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.num_send_frames = + (xport_type == TX_DATA) + ? X300_PCIE_DATA_NUM_FRAMES + : X300_PCIE_MSG_NUM_FRAMES; + + default_buff_args.num_recv_frames = + (xport_type == RX_DATA) + ? X300_PCIE_DATA_NUM_FRAMES + : X300_PCIE_MSG_NUM_FRAMES; + + xports.recv = nirio_zero_copy::make( + mb.rio_fpga_interface, dma_channel_num, + default_buff_args, xport_args); + } xports.send = xports.recv; + // Router config word is: + // - Upper 16 bits: Destination address (e.g. 0.0) + // - Lower 16 bits: DMA channel + uint32_t router_config_word = (xports.recv_sid.get_dst() << 16) | dma_channel_num; + mb.rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); + //For the nirio transport, buffer size is depends on the frame size and num frames xports.recv_buff_size = xports.recv->get_num_recv_frames() * xports.recv->get_recv_frame_size(); xports.send_buff_size = xports.send->get_num_send_frames() * xports.send->get_send_frame_size(); } else if (mb.xport_path == "eth") { + // Decide on the IP/Interface pair based on the endpoint index + std::string interface_addr = mb.eth_conns[mb.next_src_addr].addr; + const uint32_t xbar_src_addr = + mb.next_src_addr==0 ? X300_SRC_ADDR0 : X300_SRC_ADDR1; + const uint32_t xbar_src_dst = + mb.eth_conns[mb.next_src_addr].type==X300_IFACE_ETH0 ? X300_XB_DST_E0 : X300_XB_DST_E1; + mb.next_src_addr = (mb.next_src_addr + 1) % mb.eth_conns.size(); + + xports.send_sid = this->allocate_sid(mb, address, xbar_src_addr, xbar_src_dst); + xports.recv_sid = xports.send_sid.reversed(); /* Determine what the recommended frame size is for this * connection type.*/ size_t eth_data_rec_frame_size = 0; - if (mb.loaded_fpga_image == "HGS") { - if (mb.router_dst_here == X300_XB_DST_E0) { + fs_path mboard_path = fs_path("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate"); + + if (mb.loaded_fpga_image == "HG") { + size_t max_link_rate = 0; + if (xbar_src_dst == X300_XB_DST_E0) { eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; - _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_1GIGE); - } else if (mb.router_dst_here == X300_XB_DST_E1) { + max_link_rate += X300_MAX_RATE_1GIGE; + } else if (xbar_src_dst == X300_XB_DST_E1) { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; - _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_10GIGE); + max_link_rate += X300_MAX_RATE_10GIGE; } - } else if (mb.loaded_fpga_image == "XGS") { + _tree->access<double>(mboard_path).set(max_link_rate); + } else if (mb.loaded_fpga_image == "XG" or mb.loaded_fpga_image == "XA") { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; - _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_10GIGE); + size_t max_link_rate = X300_MAX_RATE_10GIGE; + max_link_rate *= mb.eth_conns.size(); + _tree->access<double>(mboard_path).set(max_link_rate); + } else if (mb.loaded_fpga_image == "HA") { + eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; + size_t max_link_rate = X300_MAX_RATE_1GIGE; + max_link_rate *= mb.eth_conns.size(); + _tree->access<double>(mboard_path).set(max_link_rate); } if (eth_data_rec_frame_size == 0) { @@ -1188,33 +1179,43 @@ x300_impl::both_xports_t x300_impl::make_transport( // Make sure frame sizes do not exceed the max available value supported by UHD default_buff_args.send_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_TX) + (xport_type == TX_DATA) ? std::min(system_max_send_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE) : std::min(system_max_send_frame_size, X300_ETH_MSG_FRAME_SIZE); default_buff_args.recv_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_RX) + (xport_type == RX_DATA) ? std::min(system_max_recv_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE) : std::min(system_max_recv_frame_size, X300_ETH_MSG_FRAME_SIZE); default_buff_args.num_send_frames = - (prefix == X300_RADIO_DEST_PREFIX_TX) + (xport_type == TX_DATA) ? X300_ETH_DATA_NUM_FRAMES : X300_ETH_MSG_NUM_FRAMES; default_buff_args.num_recv_frames = - (prefix == X300_RADIO_DEST_PREFIX_RX) + (xport_type == RX_DATA) ? X300_ETH_DATA_NUM_FRAMES : X300_ETH_MSG_NUM_FRAMES; //make a new transport - fpga has no idea how to talk to us on this yet udp_zero_copy::buff_params buff_params; - xports.recv = udp_zero_copy::make(mb.addr, + + xports.recv = udp_zero_copy::make( + interface_addr, BOOST_STRINGIZE(X300_VITA_UDP_PORT), default_buff_args, buff_params, xport_args); + // Create a threaded transport for the receive chain only + // Note that this shouldn't affect PCIe + if (xport_type == RX_DATA) { + xports.recv = zero_copy_recv_offload::make( + xports.recv, + X300_THREAD_BUFFER_TIMEOUT + ); + } xports.send = xports.recv; //For the UDP transport the buffer size if the size of the socket buffer @@ -1229,12 +1230,12 @@ x300_impl::both_xports_t x300_impl::make_transport( //send a mini packet with SID into the ZPU //ZPU will reprogram the ethernet framer UHD_LOG << "programming packet for new xport on " - << mb.addr << std::hex << "sid 0x" << sid << std::dec << std::endl; + << interface_addr << " sid " << xports.send_sid << std::endl; //YES, get a __send__ buffer from the __recv__ socket //-- this is the only way to program the framer for recv: managed_send_buffer::sptr buff = xports.recv->get_send_buff(); buff->cast<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0 - buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid); + buff->cast<boost::uint32_t *>()[1] = uhd::htonx(xports.send_sid.get()); buff->commit(8); buff.reset(); @@ -1247,48 +1248,31 @@ x300_impl::both_xports_t x300_impl::make_transport( //ethernet framer has been programmed before we return. mb.zpu_ctrl->peek32(0); } - return xports; } -boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t &config) -{ - const std::string &xport_path = mb.xport_path; - const boost::uint32_t stream = (config.dst_prefix | (config.router_dst_there << 2)) & 0xff; - - const boost::uint32_t sid = 0 - | (X300_DEVICE_HERE << 24) - | (_sid_framer << 16) - | (config.router_addr_there << 8) - | (stream << 0) - ; - UHD_LOG << std::hex - << " sid 0x" << sid - << " framer 0x" << _sid_framer - << " stream 0x" << stream - << " router_dst_there 0x" << int(config.router_dst_there) - << " router_addr_there 0x" << int(config.router_addr_there) - << std::dec << std::endl; +uhd::sid_t x300_impl::allocate_sid( + mboard_members_t &mb, + const uhd::sid_t &address, + const uint32_t src_addr, + const uint32_t src_dst +) { + uhd::sid_t sid = address; + sid.set_src_addr(src_addr); + sid.set_src_endpoint(_sid_framer); + // TODO Move all of this setup_mb() // Program the X300 to recognise it's own local address. - mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_XB_LOCAL), config.router_addr_there); + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_XB_LOCAL), address.get_dst_addr()); // Program CAM entry for outgoing packets matching a X300 resource (for example a Radio) - // This type of packet does matches the XB_LOCAL address and is looked up in the upper half of the CAM - mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 256 + (stream)), config.router_dst_there); + // This type of packet matches the XB_LOCAL address and is looked up in the upper half of the CAM + mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 256 + address.get_dst_endpoint()), address.get_dst_xbarport()); // Program CAM entry for returning packets to us (for example GR host via Eth0) // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM - mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 0 + (X300_DEVICE_HERE)), config.router_dst_here); - - if (xport_path == "nirio") { - boost::uint32_t router_config_word = ((_sid_framer & 0xff) << 16) | //Return SID - get_pcie_dma_channel(config.router_dst_there, config.dst_prefix); //Dest - mb.rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); - } + mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 0 + src_addr), src_dst); - UHD_LOG << std::hex - << "done router config for sid 0x" << sid - << std::dec << std::endl; + UHD_LOG << "done router config for sid " << sid << std::endl; //increment for next setup _sid_framer++; @@ -1296,57 +1280,9 @@ boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t return sid; } -void x300_impl::update_atr_leds(gpio_core_200_32wo::sptr leds, const std::string &rx_ant) -{ - const bool is_txrx = (rx_ant == "TX/RX"); - const int rx_led = (1 << 2); - const int tx_led = (1 << 1); - const int txrx_led = (1 << 0); - leds->set_atr_reg(dboard_iface::ATR_REG_IDLE, 0); - leds->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); - leds->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_led); - leds->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, rx_led | tx_led); -} - -void x300_impl::set_tick_rate(mboard_members_t &mb, const double rate) -{ - BOOST_FOREACH(radio_perifs_t &perif, mb.radio_perifs) { - perif.ctrl->set_tick_rate(rate); - perif.time64->set_tick_rate(rate); - perif.framer->set_tick_rate(rate); - perif.ddc->set_tick_rate(rate); - perif.deframer->set_tick_rate(rate); - perif.duc->set_tick_rate(rate); - } -} - -void x300_impl::register_loopback_self_test(wb_iface::sptr iface) -{ - bool test_fail = false; - UHD_MSG(status) << "Performing register loopback test... " << std::flush; - size_t hash = size_t(time(NULL)); - for (size_t i = 0; i < 100; i++) - { - boost::hash_combine(hash, i); - iface->poke32(radio::sr_addr(radio::TEST), boost::uint32_t(hash)); - test_fail = iface->peek32(radio::RB32_TEST) != boost::uint32_t(hash); - if (test_fail) break; //exit loop on any failure - } - UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; -} - -void x300_impl::radio_loopback(wb_iface::sptr iface, const bool on) -{ - iface->poke32(radio::sr_addr(radio::LOOPBACK), (on ? 0x1 : 0x0)); - UHD_MSG(status) << ((on)? "Radio Loopback On" : "Radio Loopback Off") << std::endl; -} - - - /*********************************************************************** * clock and time control logic **********************************************************************/ - void x300_impl::set_time_source_out(mboard_members_t &mb, const bool enb) { mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_OUT_EN, enb?1:0); @@ -1419,18 +1355,8 @@ void x300_impl::update_clock_source(mboard_members_t &mb, const std::string &sou } // Reset ADCs and DACs - for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { - radio_perifs_t &perif = mb.radio_perifs[r]; - if (perif.regmap && r==0) { //ADC/DAC reset lines only exist in Radio0 - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - perif.regmap->misc_outs_reg.flush(); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); - perif.regmap->misc_outs_reg.flush(); - } - if (perif.adc) perif.adc->reset(); - if (perif.dac) perif.dac->reset(); + BOOST_FOREACH(rfnoc::x300_radio_ctrl_impl::sptr r, mb.radios) { + r->reset_codec(); } } @@ -1460,8 +1386,12 @@ void x300_impl::update_time_source(mboard_members_t &mb, const std::string &sour void x300_impl::sync_times(mboard_members_t &mb, const uhd::time_spec_t& t) { - BOOST_FOREACH(radio_perifs_t &perif, mb.radio_perifs) - perif.time64->set_time_sync(t); + std::vector<rfnoc::block_id_t> radio_ids = find_blocks<rfnoc::x300_radio_ctrl_impl>("Radio"); + BOOST_FOREACH(const rfnoc::block_id_t &id, radio_ids) { + get_block_ctrl<rfnoc::x300_radio_ctrl_impl>(id)->set_time_sync(t); + } + + mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 0); mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 1); mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 0); } @@ -1518,30 +1448,6 @@ void x300_impl::set_mb_eeprom(i2c_iface::sptr i2c, const mboard_eeprom_t &mb_eep } /*********************************************************************** - * front-panel GPIO - **********************************************************************/ - -boost::uint32_t x300_impl::get_fp_gpio(gpio_core_200::sptr gpio) -{ - return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); -} - -void x300_impl::set_fp_gpio(gpio_core_200::sptr gpio, const gpio_attr_t attr, const boost::uint32_t value) -{ - switch (attr) - { - case GPIO_CTRL: return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); - case GPIO_DDR: return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); - case GPIO_OUT: return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); - case GPIO_ATR_0X: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); - case GPIO_ATR_RX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); - case GPIO_ATR_TX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); - case GPIO_ATR_XX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); - default: UHD_THROW_INVALID_CODE_PATH(); - } -} - -/*********************************************************************** * claimer logic **********************************************************************/ @@ -1682,9 +1588,12 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t % image_loader_path % (members.xport_path == "eth" ? "addr" : "resource") - % members.addr); + % members.get_pri_eth().addr); - throw uhd::runtime_error(str(boost::format( + std::cout << "=========================================================" << std::endl; + std::cout << "Warning:" << std::endl; + //throw uhd::runtime_error(str(boost::format( + std::cout << (str(boost::format( "Expected FPGA compatibility number %d, but got %d:\n" "The FPGA image on your device is not compatible with this host code build.\n" "Download the appropriate FPGA images for this version of UHD.\n" @@ -1696,9 +1605,17 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t ) % int(X300_FPGA_COMPAT_MAJOR) % compat_major % print_utility_error("uhd_images_downloader.py") % image_loader_cmd)); + std::cout << "=========================================================" << std::endl; } _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % compat_major % compat_minor)); + + const boost::uint32_t git_hash = members.zpu_ctrl->peek32(SR_ADDR(SET0_BASE, + ZPU_RB_GIT_HASH)); + _tree->create<std::string>(mb_path / "fpga_version_hash").set( + str(boost::format("%07x%s") + % (git_hash & 0x0FFFFFFF) + % ((git_hash & 0xF000000) ? "-dirty" : ""))); } x300_impl::x300_mboard_t x300_impl::get_mb_type_from_pcie(const std::string& resource, const std::string& rpc_port) diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index e04b06d65..d491e2bc0 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -19,51 +19,36 @@ #define INCLUDED_X300_IMPL_HPP #include <uhd/property_tree.hpp> -#include <uhd/device.hpp> +#include "../device3/device3_impl.hpp" #include <uhd/usrp/mboard_eeprom.hpp> -#include <uhd/usrp/dboard_manager.hpp> -#include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/usrp/subdev_spec.hpp> #include <uhd/types/sensors.hpp> +#include "x300_radio_ctrl_impl.hpp" #include "x300_clock_ctrl.hpp" #include "x300_fw_common.h" #include <uhd/transport/udp_simple.hpp> //mtu -#include <uhd/utils/tasks.hpp> -#include "spi_core_3000.hpp" -#include "x300_adc_ctrl.hpp" -#include "x300_dac_ctrl.hpp" -#include "rx_vita_core_3000.hpp" -#include "tx_vita_core_3000.hpp" -#include "time_core_3000.hpp" -#include "rx_dsp_core_3000.hpp" -#include "tx_dsp_core_3000.hpp" #include "i2c_core_100_wb32.hpp" -#include "radio_ctrl_core_3000.hpp" -#include "rx_frontend_core_200.hpp" -#include "tx_frontend_core_200.hpp" -#include "gpio_core_200.hpp" #include <boost/weak_ptr.hpp> #include <uhd/usrp/gps_ctrl.hpp> -#include <uhd/usrp/mboard_eeprom.hpp> -#include <uhd/transport/bounded_buffer.hpp> #include <uhd/transport/nirio/niusrprio_session.h> #include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/muxed_zero_copy_if.hpp> #include "recv_packet_demuxer_3000.hpp" #include "x300_regs.hpp" +///////////// RFNOC ///////////////////// +#include <uhd/rfnoc/block_ctrl.hpp> +///////////// RFNOC ///////////////////// +#include <boost/dynamic_bitset.hpp> static const std::string X300_FW_FILE_NAME = "usrp_x300_fw.bin"; +static const std::string X300_DEFAULT_CLOCK_SOURCE = "internal"; -static const double X300_DEFAULT_TICK_RATE = 200e6; //Hz -static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; //Hz -static const double X300_BUS_CLOCK_RATE = 166.666667e6; //Hz - -static const size_t X300_TX_HW_BUFF_SIZE = 520*1024; //512K SRAM buffer + 8K 2Clk FIFO -static const size_t X300_TX_FC_RESPONSE_FREQ = 8; //per flow-control window +static const double X300_DEFAULT_TICK_RATE = 200e6; //Hz +static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; //Hz +static const double X300_BUS_CLOCK_RATE = 166.666667e6; //Hz static const size_t X300_RX_SW_BUFF_SIZE_ETH = 0x2000000;//32MiB For an ~8k frame size any size >32MiB is just wasted buffer space static const size_t X300_RX_SW_BUFF_SIZE_ETH_MACOS = 0x100000; //1Mib -static const double X300_RX_SW_BUFF_FULL_FACTOR = 0.90; //Buffer should ideally be 90% full. -static const size_t X300_RX_FC_REQUEST_FREQ = 32; //per flow-control window //The FIFO closest to the DMA controller is 1023 elements deep for RX and 1029 elements deep for TX //where an element is 8 bytes. For best throughput ensure that the data frame fits in these buffers. @@ -73,93 +58,66 @@ static const size_t X300_PCIE_TX_DATA_FRAME_SIZE = 8192; //bytes static const size_t X300_PCIE_DATA_NUM_FRAMES = 2048; static const size_t X300_PCIE_MSG_FRAME_SIZE = 256; //bytes static const size_t X300_PCIE_MSG_NUM_FRAMES = 64; +static const size_t X300_PCIE_MAX_CHANNELS = 6; +static const size_t X300_PCIE_MAX_MUXED_XPORTS = 32; static const size_t X300_10GE_DATA_FRAME_MAX_SIZE = 8000; //bytes static const size_t X300_1GE_DATA_FRAME_MAX_SIZE = 1472; //bytes static const size_t X300_ETH_MSG_FRAME_SIZE = uhd::transport::udp_simple::mtu; //bytes +static const double X300_THREAD_BUFFER_TIMEOUT = 0.1; // Time in seconds + static const size_t X300_ETH_MSG_NUM_FRAMES = 64; static const size_t X300_ETH_DATA_NUM_FRAMES = 32; static const double X300_DEFAULT_SYSREF_RATE = 10e6; -static const size_t X300_TX_MAX_HDR_LEN = // bytes - sizeof(boost::uint32_t) // Header - + sizeof(uhd::transport::vrt::if_packet_info_t().sid) // SID - + sizeof(uhd::transport::vrt::if_packet_info_t().tsf); // Timestamp -static const size_t X300_RX_MAX_HDR_LEN = // bytes - sizeof(boost::uint32_t) // Header - + sizeof(uhd::transport::vrt::if_packet_info_t().sid) // SID - + sizeof(uhd::transport::vrt::if_packet_info_t().tsf); // Timestamp - static const size_t X300_MAX_RATE_PCIE = 800000000; // bytes/s static const size_t X300_MAX_RATE_10GIGE = 800000000; // bytes/s static const size_t X300_MAX_RATE_1GIGE = 100000000; // bytes/s #define X300_RADIO_DEST_PREFIX_TX 0 -#define X300_RADIO_DEST_PREFIX_CTRL 1 -#define X300_RADIO_DEST_PREFIX_RX 2 - -#define X300_XB_DST_E0 0 -#define X300_XB_DST_E1 1 -#define X300_XB_DST_R0 2 // Radio 0 -> Slot A -#define X300_XB_DST_R1 3 // Radio 1 -> Slot B -#define X300_XB_DST_CE0 4 -#define X300_XB_DST_CE1 5 -#define X300_XB_DST_CE2 5 -#define X300_XB_DST_PCI 7 - -#define X300_DEVICE_THERE 2 -#define X300_DEVICE_HERE 0 - -//eeprom addrs for various boards -enum + +#define X300_XB_DST_E0 0 +#define X300_XB_DST_E1 1 +#define X300_XB_DST_PCI 2 +#define X300_XB_DST_R0 3 // Radio 0 -> Slot A +#define X300_XB_DST_R1 4 // Radio 1 -> Slot B +#define X300_XB_DST_CE0 5 + +#define X300_SRC_ADDR0 0 +#define X300_SRC_ADDR1 1 +#define X300_DST_ADDR 2 + +// Ethernet ports +enum x300_eth_iface_t { - X300_DB0_RX_EEPROM = 0x5, - X300_DB0_TX_EEPROM = 0x4, - X300_DB0_GDB_EEPROM = 0x1, - X300_DB1_RX_EEPROM = 0x7, - X300_DB1_TX_EEPROM = 0x6, - X300_DB1_GDB_EEPROM = 0x3, + X300_IFACE_NONE = 0, + X300_IFACE_ETH0 = 1, + X300_IFACE_ETH1 = 2, }; -struct x300_dboard_iface_config_t +struct x300_eth_conn_t { - gpio_core_200::sptr gpio; - spi_core_3000::sptr spi; - size_t rx_spi_slaveno; - size_t tx_spi_slaveno; - i2c_core_100_wb32::sptr i2c; - x300_clock_ctrl::sptr clock; - x300_clock_which_t which_rx_clk; - x300_clock_which_t which_tx_clk; - boost::uint8_t dboard_slot; - uhd::timed_wb_iface::sptr cmd_time_ctrl; + std::string addr; + x300_eth_iface_t type; }; -uhd::usrp::dboard_iface::sptr x300_make_dboard_iface(const x300_dboard_iface_config_t &); + uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); -uhd::wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp); -uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr drv_proxy); +uhd::wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true); +uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr drv_proxy, bool enable_errors = true); uhd::device_addrs_t x300_find(const uhd::device_addr_t &hint_); -class x300_impl : public uhd::device +class x300_impl : public uhd::usrp::device3_impl { public: - typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; x300_impl(const uhd::device_addr_t &); void setup_mb(const size_t which, const uhd::device_addr_t &); ~x300_impl(void); - //the io interface - uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); - uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); - - //support old async call - bool recv_async_msg(uhd::async_metadata_t &, double); - // used by x300_find_with_addr to find X300 devices. static boost::mutex claimer_mutex; //All claims and checks in this process are serialized static bool is_claimed(uhd::wb_iface::sptr); @@ -170,43 +128,37 @@ public: static x300_mboard_t get_mb_type_from_pcie(const std::string& resource, const std::string& rpc_port); static x300_mboard_t get_mb_type_from_eeprom(const uhd::usrp::mboard_eeprom_t& mb_eeprom); -private: - boost::shared_ptr<async_md_type> _async_md; - - //perifs in the radio core - struct radio_perifs_t - { - //Interfaces - radio_ctrl_core_3000::sptr ctrl; - spi_core_3000::sptr spi; - x300_adc_ctrl::sptr adc; - x300_dac_ctrl::sptr dac; - time_core_3000::sptr time64; - rx_vita_core_3000::sptr framer; - rx_dsp_core_3000::sptr ddc; - tx_vita_core_3000::sptr deframer; - tx_dsp_core_3000::sptr duc; - gpio_core_200_32wo::sptr leds; - rx_frontend_core_200::sptr rx_fe; - tx_frontend_core_200::sptr tx_fe; - //Registers - uhd::usrp::x300::radio_regmap_t::sptr regmap; - }; +protected: + void subdev_to_blockid( + const uhd::usrp::subdev_spec_pair_t &spec, const size_t mb_i, + uhd::rfnoc::block_id_t &block_id, uhd::device_addr_t &block_args + ); + uhd::usrp::subdev_spec_pair_t blockid_to_subdev( + const uhd::rfnoc::block_id_t &blockid, const uhd::device_addr_t &block_args + ); - //overflow recovery impl - void handle_overflow(radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer); +private: //vector of member objects per motherboard struct mboard_members_t { - uhd::dict<size_t, boost::weak_ptr<uhd::rx_streamer> > rx_streamers; - uhd::dict<size_t, boost::weak_ptr<uhd::tx_streamer> > tx_streamers; - bool initialization_done; uhd::task::sptr claimer_task; - std::string addr; std::string xport_path; - int router_dst_here; + + std::vector<x300_eth_conn_t> eth_conns; + size_t next_src_addr; + + // Discover the ethernet connections per motherboard + void discover_eth(const uhd::usrp::mboard_eeprom_t mb_eeprom, + const std::vector<std::string> &ip_addrs); + + // Get the primary ethernet connection + inline const x300_eth_conn_t& get_pri_eth() const + { + return eth_conns[0]; + } + uhd::device_addr_t send_args; uhd::device_addr_t recv_args; bool if_pkt_is_big_endian; @@ -217,20 +169,9 @@ private: spi_core_3000::sptr zpu_spi; i2c_core_100_wb32::sptr zpu_i2c; - //perifs in each radio - static const size_t NUM_RADIOS = 2; - radio_perifs_t radio_perifs[NUM_RADIOS]; //!< This is hardcoded s.t. radio_perifs[0] points to slot A and [1] to B - uhd::usrp::dboard_eeprom_t db_eeproms[8]; - //! Return the index of a radio component, given a slot name. This means DSPs, radio_perifs - size_t get_radio_index(const std::string &slot_name) { - UHD_ASSERT_THROW(slot_name == "A" or slot_name == "B"); - return slot_name == "A" ? 0 : 1; - } - //other perifs on mboard x300_clock_ctrl::sptr clock; uhd::gps_ctrl::sptr gps; - gpio_core_200::sptr fp_gpio; uhd::usrp::x300::fw_regmap_t::sptr fw_regmap; @@ -240,57 +181,25 @@ private: size_t hw_rev; std::string current_refclk_src; - uhd::soft_regmap_db_t::sptr regmap_db; + std::vector<uhd::rfnoc::x300_radio_ctrl_impl::sptr> radios; }; std::vector<mboard_members_t> _mb; //task for periodically reclaiming the device from others void claimer_loop(uhd::wb_iface::sptr); - boost::mutex _transport_setup_mutex; - - void register_loopback_self_test(uhd::wb_iface::sptr iface); - - void radio_loopback(uhd::wb_iface::sptr iface, const bool on); - - /*! \brief Initialize the radio component on a given slot. - * - * Call this function once per slot (A and B) and motherboard to initialize all the radio components. - * This will: - * - Reset and init DACs and ADCs - * - Setup controls for DAC, ADC, SPI and LEDs - * - Self test ADC - * - Sync DACs (for MIMO) - * - Initialize the property tree for control objects etc. (gain, rate...) - * - * \param mb_i Motherboard index - * \param slot_name Slot name (A or B). - */ - void setup_radio(const size_t, const std::string &slot_name, const uhd::device_addr_t &dev_addr); - size_t _sid_framer; - struct sid_config_t - { - boost::uint8_t router_addr_there; - boost::uint8_t dst_prefix; //2bits - boost::uint8_t router_dst_there; - boost::uint8_t router_dst_here; - }; - boost::uint32_t allocate_sid(mboard_members_t &mb, const sid_config_t &config); - struct both_xports_t - { - uhd::transport::zero_copy_if::sptr recv; - uhd::transport::zero_copy_if::sptr send; - size_t recv_buff_size; - size_t send_buff_size; - }; - both_xports_t make_transport( - const size_t mb_index, - const boost::uint8_t& destination, - const boost::uint8_t& prefix, - const uhd::device_addr_t& args, - boost::uint32_t& sid); + uhd::sid_t allocate_sid( + mboard_members_t &mb, + const uhd::sid_t &address, + const uint32_t src_addr, + const uint32_t src_dst); + uhd::both_xports_t make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args + ); struct frame_size_t { @@ -306,6 +215,15 @@ private: */ frame_size_t determine_max_frame_size(const std::string &addr, const frame_size_t &user_mtu); + std::map<uint32_t, uint32_t> _dma_chan_pool; + uhd::transport::muxed_zero_copy_if::sptr _ctrl_dma_xport; + + /*! Allocate or return a previously allocated PCIe channel pair + * + * Note the SID is always the transmit SID (i.e. from host to device). + */ + uint32_t allocate_pcie_dma_chan(const uhd::sid_t &tx_sid, const xport_type_t xport_type); + //////////////////////////////////////////////////////////////////// // //Caching for transport interface re-use -- like sharing a DMA. @@ -328,28 +246,9 @@ private: //////////////////////////////////////////////////////////////////// uhd::dict<std::string, uhd::usrp::dboard_manager::sptr> _dboard_managers; - uhd::dict<std::string, uhd::usrp::dboard_iface::sptr> _dboard_ifaces; - void set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); - void set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); bool _ignore_cal_file; - - /*! Update the IQ MUX settings for the radio peripheral according to given subdev spec. - * - * Also checks if the given subdev is valid for this device and updates the channel to DSP mapping. - * - * \param tx_rx "tx" or "rx", depending where you're setting the subdev spec - * \param mb_i Mainboard index number. - * \param spec Subdev spec - */ - void update_subdev_spec(const std::string &tx_rx, const size_t mb_i, const uhd::usrp::subdev_spec_t &spec); - - void set_tick_rate(mboard_members_t &, const double); - void update_tick_rate(mboard_members_t &, const double); - void update_rx_samp_rate(mboard_members_t&, const size_t, const double); - void update_tx_samp_rate(mboard_members_t&, const size_t, const double); - void update_clock_control(mboard_members_t&); void initialize_clock_control(mboard_members_t &mb); void set_time_source_out(mboard_members_t&, const bool); @@ -367,21 +266,15 @@ private: void check_fw_compat(const uhd::fs_path &mb_path, uhd::wb_iface::sptr iface); void check_fpga_compat(const uhd::fs_path &mb_path, const mboard_members_t &members); - void update_atr_leds(gpio_core_200_32wo::sptr, const std::string &ant); - boost::uint32_t get_fp_gpio(gpio_core_200::sptr); - void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t); - - void self_cal_adc_capture_delay(mboard_members_t& mb, const size_t radio_i, bool print_status = false); - double self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay = false); - void self_test_adcs(mboard_members_t& mb, boost::uint32_t ramp_time_ms = 100); - - void extended_adc_test(mboard_members_t& mb, double duration_s); + /// More IO stuff + uhd::device_addr_t get_tx_hints(size_t mb_index); + uhd::device_addr_t get_rx_hints(size_t mb_index); + uhd::endianness_t get_transport_endianness(size_t mb_index) { + return _mb[mb_index].if_pkt_is_big_endian ? uhd::ENDIANNESS_BIG : uhd::ENDIANNESS_LITTLE; + }; - //**PRECONDITION** - //This function assumes that all the VITA times in "radios" are synchronized - //to a common reference. Currently, this function is called in get_tx_stream - //which also has the same precondition. - static void synchronize_dacs(const std::vector<radio_perifs_t*>& mboards); + void post_streamer_hooks(uhd::direction_t dir); }; #endif /* INCLUDED_X300_IMPL_HPP */ +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp index e3515af0c..614a98590 100644 --- a/host/lib/usrp/x300/x300_io_impl.cpp +++ b/host/lib/usrp/x300/x300_io_impl.cpp @@ -15,18 +15,19 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#define DEVICE3_STREAMER + #include "x300_regs.hpp" #include "x300_impl.hpp" -#include "validate_subdev_spec.hpp" #include "../../transport/super_recv_packet_handler.hpp" #include "../../transport/super_send_packet_handler.hpp" #include <uhd/transport/nirio_zero_copy.hpp> #include "async_packet_handler.hpp" #include <uhd/transport/bounded_buffer.hpp> -#include <uhd/transport/chdr.hpp> #include <boost/bind.hpp> #include <uhd/utils/tasks.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> #include <boost/foreach.hpp> #include <boost/make_shared.hpp> @@ -35,588 +36,62 @@ using namespace uhd::usrp; using namespace uhd::transport; /*********************************************************************** - * update streamer rates + * Hooks for get_tx_stream() and get_rx_stream() **********************************************************************/ -void x300_impl::update_tick_rate(mboard_members_t &mb, const double rate) +device_addr_t x300_impl::get_rx_hints(size_t mb_index) { - BOOST_FOREACH(const size_t &dspno, mb.rx_streamers.keys()) - { - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(mb.rx_streamers[dspno].lock()); - if (my_streamer) my_streamer->set_tick_rate(rate); - } - BOOST_FOREACH(const size_t &dspno, mb.tx_streamers.keys()) + device_addr_t rx_hints = _mb[mb_index].recv_args; + // (default to a large recv buff) + if (not rx_hints.has_key("recv_buff_size")) { - boost::shared_ptr<sph::send_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::send_packet_streamer>(mb.tx_streamers[dspno].lock()); - if (my_streamer) my_streamer->set_tick_rate(rate); - } -} - -void x300_impl::update_rx_samp_rate(mboard_members_t &mb, const size_t dspno, const double rate) -{ - if (not mb.rx_streamers.has_key(dspno)) return; - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(mb.rx_streamers[dspno].lock()); - if (not my_streamer) return; - my_streamer->set_samp_rate(rate); - const double adj = mb.radio_perifs[dspno].ddc->get_scaling_adjustment(); - my_streamer->set_scale_factor(adj); -} - -void x300_impl::update_tx_samp_rate(mboard_members_t &mb, const size_t dspno, const double rate) -{ - if (not mb.tx_streamers.has_key(dspno)) return; - boost::shared_ptr<sph::send_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::send_packet_streamer>(mb.tx_streamers[dspno].lock()); - if (not my_streamer) return; - my_streamer->set_samp_rate(rate); - const double adj = mb.radio_perifs[dspno].duc->get_scaling_adjustment(); - my_streamer->set_scale_factor(adj); -} - -/*********************************************************************** - * Setup dboard muxing for IQ - **********************************************************************/ -void x300_impl::update_subdev_spec(const std::string &tx_rx, const size_t mb_i, const subdev_spec_t &spec) -{ - UHD_ASSERT_THROW(tx_rx == "tx" or tx_rx == "rx"); - UHD_ASSERT_THROW(mb_i < _mb.size()); - const std::string mb_name = boost::lexical_cast<std::string>(mb_i); - fs_path mb_root = "/mboards/" + mb_name; - - //sanity checking - validate_subdev_spec(_tree, spec, tx_rx, mb_name); - UHD_ASSERT_THROW(spec.size() <= 2); - if (spec.size() == 1) { - UHD_ASSERT_THROW(spec[0].db_name == "A" || spec[0].db_name == "B"); - } - else if (spec.size() == 2) { - UHD_ASSERT_THROW( - (spec[0].db_name == "A" && spec[1].db_name == "B") || - (spec[0].db_name == "B" && spec[1].db_name == "A") - ); - } - - std::vector<size_t> chan_to_dsp_map(spec.size(), 0); - // setup mux for this spec - for (size_t i = 0; i < spec.size(); i++) - { - const int radio_idx = _mb[mb_i].get_radio_index(spec[i].db_name); - chan_to_dsp_map[i] = radio_idx; - - //extract connection - const std::string conn = _tree->access<std::string>(mb_root / "dboards" / spec[i].db_name / (tx_rx + "_frontends") / spec[i].sd_name / "connection").get(); - - if (tx_rx == "tx") { - //swap condition - _mb[mb_i].radio_perifs[radio_idx].tx_fe->set_mux(conn); - } else { - //swap condition - const bool fe_swapped = (conn == "QI" or conn == "Q"); - _mb[mb_i].radio_perifs[radio_idx].ddc->set_mux(conn, fe_swapped); - //see usrp/io_impl.cpp if multiple DSPs share the frontend: - _mb[mb_i].radio_perifs[radio_idx].rx_fe->set_mux(fe_swapped); + if (_mb[mb_index].xport_path != "nirio") { + //For the ethernet transport, the buffer has to be set before creating + //the transport because it is independent of the frame size and # frames + //For nirio, the buffer size is not configurable by the user + #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + //limit buffer resize on macos or it will error + rx_hints["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH_MACOS); + #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) + //set to half-a-second of buffering at max rate + rx_hints["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH); + #endif } } - - _tree->access<std::vector<size_t> >(mb_root / (tx_rx + "_chan_dsp_mapping")).set(chan_to_dsp_map); -} - - -/*********************************************************************** - * RX flow control handler - **********************************************************************/ -static size_t get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size, const device_addr_t& rx_args) -{ - double fullness_factor = rx_args.cast<double>("recv_buff_fullness", X300_RX_SW_BUFF_FULL_FACTOR); - - if (fullness_factor < 0.01 || fullness_factor > 1) { - throw uhd::value_error("recv_buff_fullness must be between 0.01 and 1 inclusive (1% to 100%)"); - } - - size_t window_in_pkts = (static_cast<size_t>(sw_buff_size * fullness_factor) / frame_size); - if (window_in_pkts == 0) { - throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); - } - return window_in_pkts; + return rx_hints; } -static void handle_rx_flowctrl(const boost::uint32_t sid, zero_copy_if::sptr xport, bool big_endian, boost::shared_ptr<boost::uint32_t> seq32_state, const size_t last_seq) -{ - managed_send_buffer::sptr buff = xport->get_send_buff(0.0); - if (not buff) - { - throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); - } - boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); - - //recover seq32 - boost::uint32_t &seq32 = *seq32_state; - const size_t seq12 = seq32 & 0xfff; - if (last_seq < seq12) seq32 += (1 << 12); - seq32 &= ~0xfff; - seq32 |= last_seq; - - //load packet info - vrt::if_packet_info_t packet_info; - packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; - packet_info.num_payload_words32 = 2; - packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); - packet_info.packet_count = seq32; - packet_info.sob = false; - packet_info.eob = false; - packet_info.sid = sid; - packet_info.has_sid = true; - packet_info.has_cid = false; - packet_info.has_tsi = false; - packet_info.has_tsf = false; - packet_info.has_tlr = false; - - //load header - if (big_endian) - vrt::chdr::if_hdr_pack_be(pkt, packet_info); - else - vrt::chdr::if_hdr_pack_le(pkt, packet_info); - - //load payload - pkt[packet_info.num_header_words32+0] = uhd::htonx<boost::uint32_t>(0); - pkt[packet_info.num_header_words32+1] = uhd::htonx<boost::uint32_t>(seq32); - - //send the buffer over the interface - buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); -} - -/*********************************************************************** - * TX flow control handler - **********************************************************************/ -struct x300_tx_fc_guts_t -{ - x300_tx_fc_guts_t(void): - stream_channel(0), - device_channel(0), - last_seq_out(0), - last_seq_ack(0), - seq_queue(1){} - size_t stream_channel; - size_t device_channel; - size_t last_seq_out; - size_t last_seq_ack; - bounded_buffer<size_t> seq_queue; - boost::shared_ptr<x300_impl::async_md_type> async_queue; - boost::shared_ptr<x300_impl::async_md_type> old_async_queue; -}; - -#define X300_ASYNC_EVENT_CODE_FLOW_CTRL 0 - -/*! - * If the return value of this function is F, the last tx'd packet - * has index N and the last ack'd packet has index M, the amount of - * FC credit we have is C = F + M - N (i.e. we can send C more packets - * before getting another ack). - */ -static size_t get_tx_flow_control_window(size_t frame_size, const device_addr_t& tx_args) +device_addr_t x300_impl::get_tx_hints(size_t mb_index) { - double hw_buff_size = tx_args.cast<double>("send_buff_size", X300_TX_HW_BUFF_SIZE); - size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / frame_size); - if (window_in_pkts == 0) { - throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); - } - return window_in_pkts; + device_addr_t tx_hints = _mb[mb_index].send_args; + return tx_hints; } -static void handle_tx_async_msgs(boost::shared_ptr<x300_tx_fc_guts_t> guts, zero_copy_if::sptr xport, bool big_endian, x300_clock_ctrl::sptr clock) +void x300_impl::post_streamer_hooks(direction_t dir) { - managed_recv_buffer::sptr buff = xport->get_recv_buff(); - if (not buff) return; - - //extract packet info - vrt::if_packet_info_t if_packet_info; - if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); - const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); - - //unpacking can fail - boost::uint32_t (*endian_conv)(boost::uint32_t) = uhd::ntohx; - try - { - if (big_endian) - { - vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); - endian_conv = uhd::ntohx; - } - else - { - vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); - endian_conv = uhd::wtohx; - } - } - catch(const std::exception &ex) - { - UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + if (dir != TX_DIRECTION) { return; } - //fill in the async metadata - async_metadata_t metadata; - load_metadata_from_buff( - endian_conv, metadata, if_packet_info, packet_buff, - clock->get_master_clock_rate(), guts->stream_channel); - - //The FC response and the burst ack are two indicators that the radio - //consumed packets. Use them to update the FC metadata - if (metadata.event_code == X300_ASYNC_EVENT_CODE_FLOW_CTRL or - metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK - ) { - const size_t seq = metadata.user_payload[0]; - guts->seq_queue.push_with_pop_on_full(seq); - } - - //FC responses don't propagate up to the user so filter them here - if (metadata.event_code != X300_ASYNC_EVENT_CODE_FLOW_CTRL) { - guts->async_queue->push_with_pop_on_full(metadata); - metadata.channel = guts->device_channel; - guts->old_async_queue->push_with_pop_on_full(metadata); - standard_async_msg_prints(metadata); - } -} - -static managed_send_buffer::sptr get_tx_buff_with_flowctrl( - task::sptr /*holds ref*/, - boost::shared_ptr<x300_tx_fc_guts_t> guts, - zero_copy_if::sptr xport, - size_t fc_pkt_window, - const double timeout -){ - while (true) - { - // delta is the amount of FC credit we've used up - const size_t delta = (guts->last_seq_out & 0xfff) - (guts->last_seq_ack & 0xfff); - // If we want to send another packet, we must have FC credit left - if ((delta & 0xfff) < fc_pkt_window) break; - - // If credit is all used up, we check seq_queue for more. - const bool ok = guts->seq_queue.pop_with_timed_wait(guts->last_seq_ack, timeout); - if (not ok) return managed_send_buffer::sptr(); //timeout waiting for flow control - } - - managed_send_buffer::sptr buff = xport->get_send_buff(timeout); - if (buff) { - guts->last_seq_out++; //update seq, this will actually be a send - } - return buff; -} - -/*********************************************************************** - * Async Data - **********************************************************************/ -bool x300_impl::recv_async_msg( - async_metadata_t &async_metadata, double timeout -){ - return _async_md->pop_with_timed_wait(async_metadata, timeout); -} - -/*********************************************************************** - * Receive streamer - **********************************************************************/ -rx_streamer::sptr x300_impl::get_rx_stream(const uhd::stream_args_t &args_) -{ - boost::mutex::scoped_lock lock(_transport_setup_mutex); - stream_args_t args = args_; - - //setup defaults for unspecified values - if (not args.otw_format.empty() and args.otw_format != "sc16") - { - throw uhd::value_error("x300_impl::get_rx_stream only supports otw_format sc16"); - } - args.otw_format = "sc16"; - args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; - - boost::shared_ptr<sph::recv_packet_streamer> my_streamer; - for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) - { - // Find the mainboard and subdev that corresponds to channel args.channels[stream_i] - const size_t chan = args.channels[stream_i]; - size_t mb_chan = chan, mb_index; - for (mb_index = 0; mb_index < _mb.size(); mb_index++) { - const subdev_spec_t &curr_subdev_spec = - _tree->access<subdev_spec_t>("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "rx_subdev_spec").get(); - if (mb_chan < curr_subdev_spec.size()) { - break; - } else { - mb_chan -= curr_subdev_spec.size(); - } + // Loop through all tx streamers. Find all radios connected to one + // streamer. Sync those. + BOOST_FOREACH(const boost::weak_ptr<uhd::tx_streamer> &streamer_w, _tx_streamers.vals()) { + const boost::shared_ptr<sph::send_packet_streamer> streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(streamer_w.lock()); + if (not streamer) { + continue; } - // Find the DSP that corresponds to this mainboard and subdev - UHD_ASSERT_THROW(mb_index < _mb.size()); - mboard_members_t &mb = _mb[mb_index]; - const std::vector<size_t> dsp_map = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "rx_chan_dsp_mapping") - .get(); //.at(mb_chan); - UHD_ASSERT_THROW(mb_chan < dsp_map.size()); - const size_t radio_index = dsp_map[mb_chan]; - UHD_ASSERT_THROW(radio_index < 2); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - - //setup the dsp transport hints (default to a large recv buff) - device_addr_t device_addr = mb.recv_args; - if (not device_addr.has_key("recv_buff_size")) - { - if (mb.xport_path != "nirio") { - //For the ethernet transport, the buffer has to be set before creating - //the transport because it is independent of the frame size and # frames - //For nirio, the buffer size is not configurable by the user - #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) - //limit buffer resize on macos or it will error - device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH_MACOS); - #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) - //set to half-a-second of buffering at max rate - device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH); - #endif - } + std::vector<rfnoc::x300_radio_ctrl_impl::sptr> radio_ctrl_blks = + streamer->get_terminator()->find_downstream_node<rfnoc::x300_radio_ctrl_impl>(); + try { + //UHD_MSG(status) << "[X300] syncing " << radio_ctrl_blks.size() << " radios " << std::endl; + rfnoc::x300_radio_ctrl_impl::synchronize_dacs(radio_ctrl_blks); } - - //allocate sid and create transport - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t data_sid; - UHD_LOG << "creating rx stream " << device_addr.to_string() << std::endl; - both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_RX, device_addr, data_sid); - UHD_LOG << boost::format("data_sid = 0x%08x, actual recv_buff_size = %d\n") % data_sid % xport.recv_buff_size << std::endl; - - // To calculate the max number of samples per packet, we assume the maximum header length - // to avoid fragmentation should the entire header be used. - const size_t bpp = xport.recv->get_recv_frame_size() - X300_RX_MAX_HDR_LEN; // bytes per packet - const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item - const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); // samples per packet - - //make the new streamer given the samples per packet - if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); - my_streamer->resize(args.channels.size()); - - //init some streamer stuff - std::string conv_endianness; - if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); - conv_endianness = "be"; - } else { - my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); - conv_endianness = "le"; + catch(const uhd::io_error &ex) { + throw uhd::io_error(str(boost::format("Failed to sync DACs! %s ") % ex.what())); } - - //set the converter - uhd::convert::id_type id; - id.input_format = args.otw_format + "_item32_" + conv_endianness; - id.num_inputs = 1; - id.output_format = args.cpu_format; - id.num_outputs = 1; - my_streamer->set_converter(id); - - perif.framer->clear(); - perif.framer->set_nsamps_per_packet(spp); //seems to be a good place to set this - perif.framer->set_sid((data_sid << 16) | (data_sid >> 16)); - perif.framer->setup(args); - perif.ddc->setup(args); - - //flow control setup - const size_t fc_window = get_rx_flow_control_window(xport.recv->get_recv_frame_size(), xport.recv_buff_size, device_addr); - const size_t fc_handle_window = std::max<size_t>(1, fc_window / X300_RX_FC_REQUEST_FREQ); - - UHD_LOG << "RX Flow Control Window = " << fc_window << ", RX Flow Control Handler Window = " << fc_handle_window << std::endl; - - perif.framer->configure_flow_control(fc_window); - - boost::shared_ptr<boost::uint32_t> seq32(new boost::uint32_t(0)); - //Give the streamer a functor to get the recv_buffer - //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency - my_streamer->set_xport_chan_get_buff( - stream_i, - boost::bind(&zero_copy_if::get_recv_buff, xport.recv, _1), - true /*flush*/ - ); - //Give the streamer a functor to handle overflows - //bind requires a weak_ptr to break the a streamer->streamer circular dependency - //Using "this" is OK because we know that x300_impl will outlive the streamer - my_streamer->set_overflow_handler( - stream_i, - boost::bind(&x300_impl::handle_overflow, this, boost::ref(perif), boost::weak_ptr<uhd::rx_streamer>(my_streamer)) - ); - //Give the streamer a functor to send flow control messages - //handle_rx_flowctrl is static and has no lifetime issues - my_streamer->set_xport_handle_flowctrl( - stream_i, boost::bind(&handle_rx_flowctrl, data_sid, xport.send, mb.if_pkt_is_big_endian, seq32, _1), - fc_handle_window, - true/*init*/ - ); - //Give the streamer a functor issue stream cmd - //bind requires a rx_vita_core_3000::sptr to add a streamer->framer lifetime dependency - my_streamer->set_issue_stream_cmd( - stream_i, boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1) - ); - - //Store a weak pointer to prevent a streamer->x300_impl->streamer circular dependency - mb.rx_streamers[radio_index] = boost::weak_ptr<sph::recv_packet_streamer>(my_streamer); - - //sets all tick and samp rates on this streamer - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_index); - _tree->access<double>(mb_path / "tick_rate").update(); - _tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update(); - } - - return my_streamer; -} - -void x300_impl::handle_overflow(x300_impl::radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer) -{ - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(streamer.lock()); - if (not my_streamer) return; //If the rx_streamer has expired then overflow handling makes no sense. - - if (my_streamer->get_num_channels() == 1) - { - perif.framer->handle_overflow(); - return; - } - - ///////////////////////////////////////////////////////////// - // MIMO overflow recovery time - ///////////////////////////////////////////////////////////// - //find out if we were in continuous mode before stopping - const bool in_continuous_streaming_mode = perif.framer->in_continuous_streaming_mode(); - //stop streaming - my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); - //flush transports - my_streamer->flush_all(0.001); - //restart streaming - if (in_continuous_streaming_mode) - { - stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); - stream_cmd.stream_now = false; - stream_cmd.time_spec = perif.time64->get_time_now() + time_spec_t(0.01); - my_streamer->issue_stream_cmd(stream_cmd); } } -/*********************************************************************** - * Transmit streamer - **********************************************************************/ -tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_) -{ - boost::mutex::scoped_lock lock(_transport_setup_mutex); - stream_args_t args = args_; - - //setup defaults for unspecified values - if (not args.otw_format.empty() and args.otw_format != "sc16") - { - throw uhd::value_error("x300_impl::get_tx_stream only supports otw_format sc16"); - } - args.otw_format = "sc16"; - args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; - - //shared async queue for all channels in streamer - boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); - - std::vector<radio_perifs_t*> radios_list; - boost::shared_ptr<sph::send_packet_streamer> my_streamer; - for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) - { - // Find the mainboard and subdev that corresponds to channel args.channels[stream_i] - const size_t chan = args.channels[stream_i]; - size_t mb_chan = chan, mb_index; - for (mb_index = 0; mb_index < _mb.size(); mb_index++) { - const subdev_spec_t &curr_subdev_spec = - _tree->access<subdev_spec_t>("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_subdev_spec").get(); - if (mb_chan < curr_subdev_spec.size()) { - break; - } else { - mb_chan -= curr_subdev_spec.size(); - } - } - // Find the DSP that corresponds to this mainboard and subdev - mboard_members_t &mb = _mb[mb_index]; - const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_chan_dsp_mapping") - .get().at(mb_chan); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - radios_list.push_back(&perif); - - //setup the dsp transport hints (TODO) - device_addr_t device_addr = mb.send_args; - - //allocate sid and create transport - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t data_sid; - UHD_LOG << "creating tx stream " << device_addr.to_string() << std::endl; - both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_TX, device_addr, data_sid); - UHD_LOG << boost::format("data_sid = 0x%08x\n") % data_sid << std::endl; - - // To calculate the max number of samples per packet, we assume the maximum header length - // to avoid fragmentation should the entire header be used. - const size_t bpp = xport.send->get_send_frame_size() - X300_TX_MAX_HDR_LEN; - const size_t bpi = convert::get_bytes_per_item(args.otw_format); - const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); - - //make the new streamer given the samples per packet - if (not my_streamer) my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); - my_streamer->resize(args.channels.size()); - - std::string conv_endianness; - if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); - conv_endianness = "be"; - } else { - my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); - conv_endianness = "le"; - } - - //set the converter - uhd::convert::id_type id; - id.input_format = args.cpu_format; - id.num_inputs = 1; - id.output_format = args.otw_format + "_item32_" + conv_endianness; - id.num_outputs = 1; - my_streamer->set_converter(id); - - perif.deframer->clear(); - perif.deframer->setup(args); - perif.duc->setup(args); - - //flow control setup - size_t fc_window = get_tx_flow_control_window(xport.send->get_send_frame_size(), device_addr); //In packets - const size_t fc_handle_window = std::max<size_t>(1, fc_window/X300_TX_FC_RESPONSE_FREQ); - - UHD_LOG << "TX Flow Control Window = " << fc_window << ", TX Flow Control Handler Window = " << fc_handle_window << std::endl; - - perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window); - boost::shared_ptr<x300_tx_fc_guts_t> guts(new x300_tx_fc_guts_t()); - guts->stream_channel = stream_i; - guts->device_channel = chan; - guts->async_queue = async_md; - guts->old_async_queue = _async_md; - task::sptr task = task::make(boost::bind(&handle_tx_async_msgs, guts, xport.recv, mb.if_pkt_is_big_endian, mb.clock)); - - //Give the streamer a functor to get the send buffer - //get_tx_buff_with_flowctrl is static so bind has no lifetime issues - //xport.send (sptr) is required to add streamer->data-transport lifetime dependency - //task (sptr) is required to add a streamer->async-handler lifetime dependency - my_streamer->set_xport_chan_get_buff( - stream_i, - boost::bind(&get_tx_buff_with_flowctrl, task, guts, xport.send, fc_window, _1) - ); - //Give the streamer a functor handled received async messages - my_streamer->set_async_receiver( - boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) - ); - my_streamer->set_xport_chan_sid(stream_i, true, data_sid); - my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet - - //Store a weak pointer to prevent a streamer->x300_impl->streamer circular dependency - mb.tx_streamers[radio_index] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); - - //sets all tick and samp rates on this streamer - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_index); - _tree->access<double>(mb_path / "tick_rate").update(); - _tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update(); - } - - synchronize_dacs(radios_list); - return my_streamer; -} +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp new file mode 100644 index 000000000..e1b724db6 --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp @@ -0,0 +1,894 @@ +// +// Copyright 2015-2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "x300_radio_ctrl_impl.hpp" + +#include "x300_dboard_iface.hpp" +#include "wb_iface_adapter.hpp" +#include "gpio_atr_3000.hpp" +#include "apply_corrections.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/make_shared.hpp> +#include <boost/date_time/posix_time/posix_time_io.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::usrp::x300; + +static const size_t IO_MASTER_RADIO = 0; + +/**************************************************************************** + * Structors + ***************************************************************************/ +UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(x300_radio_ctrl) + , _ignore_cal_file(false) +{ + UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::ctor() " << std::endl; + + //////////////////////////////////////////////////////////////////// + // Set up basic info + //////////////////////////////////////////////////////////////////// + _radio_type = (get_block_id().get_block_count() == 0) ? PRIMARY : SECONDARY; + _radio_slot = (get_block_id().get_block_count() == 0) ? "A" : "B"; + _radio_clk_rate = _tree->access<double>("master_clock_rate").get(); + + //////////////////////////////////////////////////////////////////// + // Set up peripherals + //////////////////////////////////////////////////////////////////// + wb_iface::sptr ctrl = _get_ctrl(IO_MASTER_RADIO); + _regs = boost::make_shared<radio_regmap_t>(_radio_type==PRIMARY?0:1); + _regs->initialize(*ctrl, true); + + //Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1 + if (_radio_type==PRIMARY) { + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + _regs->misc_outs_reg.flush(); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); + _regs->misc_outs_reg.flush(); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); + + //////////////////////////////////////////////////////////////// + // Setup peripherals + //////////////////////////////////////////////////////////////// + _spi = spi_core_3000::make(ctrl, + radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::SPI), + radio_ctrl_impl::regs::RB_SPI); + _leds = gpio_atr::gpio_atr_3000::make_write_only(ctrl, regs::sr_addr(regs::LEDS)); + _leds->set_atr_mode(usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _adc = x300_adc_ctrl::make(_spi, DB_ADC_SEN); + _dac = x300_dac_ctrl::make(_spi, DB_DAC_SEN, _radio_clk_rate); + + if (_radio_type==PRIMARY) { + _fp_gpio = gpio_atr::gpio_atr_3000::make(ctrl, regs::sr_addr(regs::FP_GPIO), regs::RB_FP_GPIO); + BOOST_FOREACH(const gpio_atr::gpio_attr_map_t::value_type attr, gpio_atr::gpio_attr_map) { + _tree->create<boost::uint32_t>(fs_path("gpio") / "FP0" / attr.second) + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, _fp_gpio, attr.first, _1)); + } + _tree->create<boost::uint32_t>(fs_path("gpio") / "FP0" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _fp_gpio)); + } + + //////////////////////////////////////////////////////////////// + // create legacy codec control objects + //////////////////////////////////////////////////////////////// + _tree->create<int>("rx_codecs" / _radio_slot / "gains"); //phony property so this dir exists + _tree->create<int>("tx_codecs" / _radio_slot / "gains"); //phony property so this dir exists + _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("ads62p48"); + _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("ad9146"); + + _tree->create<meta_range_t>("rx_codecs" / _radio_slot / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); + _tree->create<double>("rx_codecs" / _radio_slot / "gains" / "digital" / "value") + .add_coerced_subscriber(boost::bind(&x300_adc_ctrl::set_gain, _adc, _1)).set(0) + ; + + //////////////////////////////////////////////////////////////// + // create front-end objects + //////////////////////////////////////////////////////////////// + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core = rx_frontend_core_3000::make(_get_ctrl(i), regs::sr_addr(x300_regs::RX_FE_BASE)); + _rx_fe_map[i].core->set_adc_rate(_radio_clk_rate); + _rx_fe_map[i].core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); + _rx_fe_map[i].core->set_dc_offset_auto(rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); + _rx_fe_map[i].core->populate_subtree(_tree->subtree(_root_path / "rx_fe_corrections" / i)); + + _tx_fe_map[i].core = tx_frontend_core_200::make(_get_ctrl(i), regs::sr_addr(x300_regs::TX_FE_BASE)); + _tx_fe_map[i].core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); + _tx_fe_map[i].core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); + _tx_fe_map[i].core->populate_subtree(_tree->subtree(_root_path / "tx_fe_corrections" / i)); + } + + //////////////////////////////////////////////////////////////// + // Update default SPP (overwrites the default value from the XML file) + //////////////////////////////////////////////////////////////// + const size_t max_bytes_header = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); + const size_t default_spp = (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) + / (2 * sizeof(int16_t)); + _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); +} + +x300_radio_ctrl_impl::~x300_radio_ctrl_impl() +{ + // Tear down our part of the tree: + _tree->remove(fs_path("rx_codecs" / _radio_slot)); + _tree->remove(fs_path("tx_codecs" / _radio_slot)); + _tree->remove(_root_path / "rx_fe_corrections"); + _tree->remove(_root_path / "tx_fe_corrections"); + if (_radio_type==PRIMARY) { + BOOST_FOREACH(const gpio_atr::gpio_attr_map_t::value_type attr, gpio_atr::gpio_attr_map) { + _tree->remove(fs_path("gpio") / "FP0" / attr.second); + } + _tree->remove(fs_path("gpio") / "FP0" / "READBACK"); + } + + // Reset peripherals + if (_radio_type==PRIMARY) { + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); + _regs->misc_outs_reg.flush(); +} + +/**************************************************************************** + * API calls + ***************************************************************************/ +double x300_radio_ctrl_impl::set_rate(double /* rate */) +{ + // On X3x0, tick rate can't actually be changed at runtime + return get_rate(); +} + +void x300_radio_ctrl_impl::set_tx_antenna(const std::string &ant, const size_t chan) +{ + _tree->access<std::string>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "antenna" / "value") + ).set(ant); +} + +void x300_radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t chan) +{ + _tree->access<std::string>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "antenna" / "value") + ).set(ant); +} + +double x300_radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).set(freq).get(); +} + +double x300_radio_ctrl_impl::get_tx_frequency(const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).get(); +} + +double x300_radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).set(freq).get(); +} + +double x300_radio_ctrl_impl::get_rx_frequency(const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).get(); +} + +double x300_radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) +{ + //TODO: This is extremely hacky! + fs_path path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "gains"); + std::vector<std::string> gain_stages = _tree->list(path); + if (gain_stages.size() == 1) { + const double actual_gain = _tree->access<double>(path / gain_stages[0] / "value").set(gain).get(); + radio_ctrl_impl::set_tx_gain(actual_gain, chan); + return gain; + } else { + UHD_MSG(warning) << "set_tx_gain: could not apply gain for this daughterboard."; + radio_ctrl_impl::set_tx_gain(0.0, chan); + return 0.0; + } +} + +double x300_radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) +{ + //TODO: This is extremely hacky! + fs_path path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "gains"); + std::vector<std::string> gain_stages = _tree->list(path); + if (gain_stages.size() == 1) { + const double actual_gain = _tree->access<double>(path / gain_stages[0] / "value").set(gain).get(); + radio_ctrl_impl::set_rx_gain(actual_gain, chan); + return gain; + } else { + UHD_MSG(warning) << "set_rx_gain: could not apply gain for this daughterboard."; + radio_ctrl_impl::set_tx_gain(0.0, chan); + return 0.0; + } +} + + +template <typename map_type> +static size_t _get_chan_from_map(std::map<size_t, map_type> map, const std::string &fe) +{ + // TODO replace with 'auto' when possible + typedef typename std::map<size_t, map_type>::iterator chan_iterator; + for (chan_iterator it = map.begin(); it != map.end(); ++it) { + if (it->second.db_fe_name == fe) { + return it->first; + } + + } + throw uhd::runtime_error(str( + boost::format("Invalid daughterboard frontend name: %s") + % fe + )); +} + +size_t x300_radio_ctrl_impl::get_chan_from_dboard_fe(const std::string &fe, const uhd::direction_t direction) +{ + switch (direction) { + case uhd::TX_DIRECTION: + return _get_chan_from_map(_tx_fe_map, fe); + case uhd::RX_DIRECTION: + return _get_chan_from_map(_rx_fe_map, fe); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +std::string x300_radio_ctrl_impl::get_dboard_fe_from_chan(const size_t chan, const uhd::direction_t direction) +{ + switch (direction) { + case uhd::TX_DIRECTION: + return _tx_fe_map.at(chan).db_fe_name; + case uhd::RX_DIRECTION: + return _rx_fe_map.at(chan).db_fe_name; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +double x300_radio_ctrl_impl::get_output_samp_rate(size_t chan) +{ + // TODO: chan should never be ANY_PORT, but due to our current graph search + // method, this can actually happen: + if (chan == ANY_PORT) { + chan = 0; + for (size_t i = 0; i < _get_num_radios(); i++) { + if (_is_streamer_active(uhd::RX_DIRECTION, chan)) { + chan = i; + break; + } + } + } + return _rx_fe_map.at(chan).core->get_output_rate(); +} + +/**************************************************************************** + * Radio control and setup + ***************************************************************************/ +void x300_radio_ctrl_impl::setup_radio(uhd::i2c_iface::sptr zpu_i2c, x300_clock_ctrl::sptr clock, bool verbose) +{ + _self_cal_adc_capture_delay(verbose); + + //////////////////////////////////////////////////////////////////// + // create RF frontend interfacing + //////////////////////////////////////////////////////////////////// + static const size_t BASE_ADDR = 0x50; + static const size_t RX_EEPROM_ADDR = 0x5; + static const size_t TX_EEPROM_ADDR = 0x4; + static const size_t GDB_EEPROM_ADDR = 0x1; + const static std::vector<size_t> EEPROM_ADDRS = + boost::assign::list_of(RX_EEPROM_ADDR)(TX_EEPROM_ADDR)(GDB_EEPROM_ADDR); + const static std::vector<std::string> EEPROM_PATHS = + boost::assign::list_of("rx_eeprom")("tx_eeprom")("gdb_eeprom"); + + const size_t DB_OFFSET = (_radio_slot == "A") ? 0x0 : 0x2; + const fs_path db_path = ("dboards" / _radio_slot); + for (size_t i = 0; i < EEPROM_ADDRS.size(); i++) { + const size_t addr = EEPROM_ADDRS[i] + DB_OFFSET; + //Load EEPROM + _db_eeproms[addr].load(*zpu_i2c, BASE_ADDR | addr); + //Add to tree + _tree->create<dboard_eeprom_t>(db_path / EEPROM_PATHS[i]) + .set(_db_eeproms[addr]) + .add_coerced_subscriber(boost::bind(&dboard_eeprom_t::store, + _db_eeproms[addr], boost::ref(*zpu_i2c), (BASE_ADDR | addr))); + } + + //create a new dboard interface + x300_dboard_iface_config_t db_config; + db_config.gpio = gpio_atr::db_gpio_atr_3000::make(_get_ctrl(IO_MASTER_RADIO), + radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::GPIO), radio_ctrl_impl::regs::RB_DB_GPIO); + db_config.spi = _spi; + db_config.rx_spi_slaveno = DB_RX_SEN; + db_config.tx_spi_slaveno = DB_TX_SEN; + db_config.i2c = zpu_i2c; + db_config.clock = clock; + db_config.which_rx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; + db_config.which_tx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; + db_config.dboard_slot = (_radio_slot == "A")? 0 : 1; + db_config.cmd_time_ctrl = _get_ctrl(IO_MASTER_RADIO); + + //create a new dboard manager + boost::shared_ptr<x300_dboard_iface> db_iface = boost::make_shared<x300_dboard_iface>(db_config); + _db_manager = dboard_manager::make( + _db_eeproms[RX_EEPROM_ADDR + DB_OFFSET].id, + _db_eeproms[TX_EEPROM_ADDR + DB_OFFSET].id, + _db_eeproms[GDB_EEPROM_ADDR + DB_OFFSET].id, + db_iface, _tree->subtree(db_path), + true // defer daughterboard intitialization + ); + + size_t rx_chan = 0, tx_chan = 0; + BOOST_FOREACH(const std::string& fe, _db_manager->get_rx_frontends()) { + if (rx_chan >= _get_num_radios()) { + break; + } + _rx_fe_map[rx_chan].db_fe_name = fe; + db_iface->add_rx_fe(fe, _rx_fe_map[rx_chan].core); + const fs_path fe_path(db_path / "rx_frontends" / fe); + const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); + const double if_freq = (_tree->exists(fe_path / "if_freq/value")) ? + _tree->access<double>(fe_path / "if_freq/value").get() : 0.0; + _rx_fe_map[rx_chan].core->set_fe_connection(usrp::fe_connection_t(conn, if_freq)); + rx_chan++; + } + BOOST_FOREACH(const std::string& fe, _db_manager->get_tx_frontends()) { + if (tx_chan >= _get_num_radios()) { + break; + } + _tx_fe_map[tx_chan].db_fe_name = fe; + const fs_path fe_path(db_path / "tx_frontends" / fe); + const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); + _tx_fe_map[tx_chan].core->set_mux(conn); + tx_chan++; + } + UHD_ASSERT_THROW(rx_chan or tx_chan); + + // Initialize the daughterboards now that frontend cores and connections exist + _db_manager->initialize_dboards(); + + //now that dboard is created -- register into rx antenna event + if (not _rx_fe_map.empty() + and _tree->exists(db_path / "rx_frontends" / _rx_fe_map[0].db_fe_name / "antenna" / "value")) { + _tree->access<std::string>(db_path / "rx_frontends" / _rx_fe_map[0].db_fe_name / "antenna" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::_update_atr_leds, this, _1)); + } + _update_atr_leds(""); //init anyway, even if never called + + //bind frontend corrections to the dboard freq props + const fs_path db_tx_fe_path = db_path / "tx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { + _tree->access<double>(db_tx_fe_path / name / "freq" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::set_tx_fe_corrections, this, db_path, _root_path / "tx_fe_corrections" / name, _1)); + } + const fs_path db_rx_fe_path = db_path / "rx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { + _tree->access<double>(db_rx_fe_path / name / "freq" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::set_rx_fe_corrections, this, db_path, _root_path / "rx_fe_corrections" / name,_1)); + } + + //////////////////////////////////////////////////////////////// + // Set tick rate + //////////////////////////////////////////////////////////////// + const double tick_rate = get_output_samp_rate(0); + if (_radio_type==PRIMARY) { + // Slot A is the highlander timekeeper + _tree->access<double>("tick_rate").set(tick_rate); + } + radio_ctrl_impl::set_rate(tick_rate); +} + +void x300_radio_ctrl_impl::set_rx_fe_corrections( + const fs_path &db_path, + const fs_path &rx_fe_corr_path, + const double lo_freq +) { + if (not _ignore_cal_file) { + apply_rx_fe_corrections(_tree, db_path, rx_fe_corr_path, lo_freq); + } +} + +void x300_radio_ctrl_impl::set_tx_fe_corrections( + const fs_path &db_path, + const fs_path &tx_fe_corr_path, + const double lo_freq +) { + if (not _ignore_cal_file) { + apply_tx_fe_corrections(_tree, db_path, tx_fe_corr_path, lo_freq); + } +} + +void x300_radio_ctrl_impl::reset_codec() +{ + if (_radio_type==PRIMARY) { //ADC/DAC reset lines only exist in Radio0 + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + _regs->misc_outs_reg.flush(); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); + _regs->misc_outs_reg.flush(); + } + UHD_ASSERT_THROW(bool(_adc)); + UHD_ASSERT_THROW(bool(_dac)); + _adc->reset(); + _dac->reset(); +} + +void x300_radio_ctrl_impl::self_test_adc(boost::uint32_t ramp_time_ms) +{ + //Bypass all front-end corrections + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core->bypass_all(true); + } + + //Test basic patterns + _adc->set_test_word("ones", "ones"); _check_adc(0xfffcfffc); + _adc->set_test_word("zeros", "zeros"); _check_adc(0x00000000); + _adc->set_test_word("ones", "zeros"); _check_adc(0xfffc0000); + _adc->set_test_word("zeros", "ones"); _check_adc(0x0000fffc); + for (size_t k = 0; k < 14; k++) { + _adc->set_test_word("zeros", "custom", 1 << k); + _check_adc(1 << (k+2)); + } + for (size_t k = 0; k < 14; k++) { + _adc->set_test_word("custom", "zeros", 1 << k); + _check_adc(1 << (k+18)); + } + + //Turn on ramp pattern test + _adc->set_test_word("ramp", "ramp"); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + //Sleep added for SPI transactions to finish and ramp to start before checker is enabled. + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + + boost::this_thread::sleep(boost::posix_time::milliseconds(ramp_time_ms)); + _regs->misc_ins_reg.refresh(); + + std::string i_status, q_status; + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR)) + i_status = "Bit Errors!"; + else + i_status = "Good"; + else + i_status = "Not Locked!"; + + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR)) + q_status = "Bit Errors!"; + else + q_status = "Good"; + else + q_status = "Not Locked!"; + + //Return to normal mode + _adc->set_test_word("normal", "normal"); + + if ((i_status != "Good") or (q_status != "Good")) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed for %s. Ramp checker status: {ADC_A=%s, ADC_B=%s}")%unique_id()%i_status%q_status).str()); + } + + //Restore front-end corrections + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core->bypass_all(false); + } +} + +void x300_radio_ctrl_impl::extended_adc_test(const std::vector<x300_radio_ctrl_impl::sptr>& radios, double duration_s) +{ + static const size_t SECS_PER_ITER = 5; + UHD_MSG(status) << boost::format("Running Extended ADC Self-Test (Duration=%.0fs, %ds/iteration)...\n") + % duration_s % SECS_PER_ITER; + + size_t num_iters = static_cast<size_t>(ceil(duration_s/SECS_PER_ITER)); + size_t num_failures = 0; + for (size_t iter = 0; iter < num_iters; iter++) { + //Print date and time + boost::posix_time::time_facet *facet = new boost::posix_time::time_facet("%d-%b-%Y %H:%M:%S"); + std::ostringstream time_strm; + time_strm.imbue(std::locale(std::locale::classic(), facet)); + time_strm << boost::posix_time::second_clock::local_time(); + //Run self-test + UHD_MSG(status) << boost::format("-- [%s] Iteration %06d... ") % time_strm.str() % (iter+1); + try { + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->self_test_adc((SECS_PER_ITER*1000)/radios.size()); + } + UHD_MSG(status) << "passed" << std::endl; + } catch(std::exception &e) { + num_failures++; + UHD_MSG(status) << e.what() << std::endl; + } + } + if (num_failures == 0) { + UHD_MSG(status) << "Extended ADC Self-Test PASSED\n"; + } else { + throw uhd::runtime_error( + (boost::format("Extended ADC Self-Test FAILED!!! (%d/%d failures)\n") % num_failures % num_iters).str()); + } +} + +void x300_radio_ctrl_impl::synchronize_dacs(const std::vector<x300_radio_ctrl_impl::sptr>& radios) +{ + if (radios.size() < 2) return; //Nothing to synchronize + + //**PRECONDITION** + //This function assumes that all the VITA times in "radios" are synchronized + //to a common reference. Currently, this function is called in get_tx_stream + //which also has the same precondition. + + //Reinitialize and resync all DACs + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->_dac->reset(); + } + + //Get a rough estimate of the cumulative command latency + boost::posix_time::ptime t_start = boost::posix_time::microsec_clock::local_time(); + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->user_reg_read64(regs::RB_TIME_NOW); //Discard value. We are just timing the call + } + boost::posix_time::time_duration t_elapsed = + boost::posix_time::microsec_clock::local_time() - t_start; + + //Add 100% of headroom + uncertaintly to the command time + boost::uint64_t t_sync_us = (t_elapsed.total_microseconds() * 2) + 13000 /*Scheduler latency*/; + + //Pick radios[0] as the time reference. + uhd::time_spec_t sync_time = + radios[0]->_time64->get_time_now() + uhd::time_spec_t(((double)t_sync_us)/1e6); + + //Send the sync command + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->set_command_tick_rate(radios[i]->_radio_clk_rate, IO_MASTER_RADIO); + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0); + radios[i]->set_command_time(sync_time, IO_MASTER_RADIO); + //Arm FRAMEP/N sync pulse by asserting a rising edge + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 1); + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0); + radios[i]->set_command_time(uhd::time_spec_t(0.0), IO_MASTER_RADIO); + } + + //Wait and check status + boost::this_thread::sleep(boost::posix_time::microseconds(t_sync_us)); + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->_dac->verify_sync(); + } +} + +double x300_radio_ctrl_impl::self_cal_adc_xfer_delay( + const std::vector<x300_radio_ctrl_impl::sptr>& radios, + x300_clock_ctrl::sptr clock, + boost::function<void(double)> wait_for_clk_locked, + bool apply_delay) +{ + UHD_MSG(status) << "Running ADC transfer delay self-cal: " << std::flush; + + //Effective resolution of the self-cal. + static const size_t NUM_DELAY_STEPS = 100; + + double master_clk_period = (1.0e9 / clock->get_master_clock_rate()); //in ns + double delay_start = 0.0; + double delay_range = 2 * master_clk_period; + double delay_incr = delay_range / NUM_DELAY_STEPS; + + UHD_MSG(status) << "Measuring..." << std::flush; + double cached_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_ADC0); + double fpga_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_FPGA); + + //Iterate through several values of delays and measure ADC data integrity + std::vector< std::pair<double,bool> > results; + for (size_t i = 0; i < NUM_DELAY_STEPS; i++) { + //Delay the ADC clock (will set both Ch0 and Ch1 delays) + double delay = clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, delay_incr*i + delay_start); + wait_for_clk_locked(0.1); + + boost::uint32_t err_code = 0; + for (size_t r = 0; r < radios.size(); r++) { + //Test each channel (I and Q) individually so as to not accidentally trigger + //on the data from the other channel if there is a swap + + // -- Test I Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + radios[r]->_adc->set_test_word("ramp", "ones"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //50ms @ 200MHz = 10 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (radios[r]->_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) { + err_code += radios[r]->_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + // -- Test Q Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + radios[r]->_adc->set_test_word("ones", "ramp"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //50ms @ 200MHz = 10 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (radios[r]->_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) { + err_code += radios[r]->_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + } + //UHD_MSG(status) << (boost::format("XferDelay=%fns, Error=%d\n") % delay % err_code); + results.push_back(std::pair<double,bool>(delay, err_code==0)); + } + + //Calculate the valid window + int win_start_idx = -1, win_stop_idx = -1, cur_start_idx = -1, cur_stop_idx = -1; + for (size_t i = 0; i < results.size(); i++) { + std::pair<double,bool>& item = results[i]; + if (item.second) { //If data is stable + if (cur_start_idx == -1) { //This is the first window + cur_start_idx = i; + cur_stop_idx = i; + } else { //We are extending the window + cur_stop_idx = i; + } + } else { + if (cur_start_idx == -1) { //We haven't yet seen valid data + //Do nothing + } else if (win_start_idx == -1) { //We passed the first valid window + win_start_idx = cur_start_idx; + win_stop_idx = cur_stop_idx; + } else { //Update cached window if current window is larger + double cur_win_len = results[cur_stop_idx].first - results[cur_start_idx].first; + double cached_win_len = results[win_stop_idx].first - results[win_start_idx].first; + if (cur_win_len > cached_win_len) { + win_start_idx = cur_start_idx; + win_stop_idx = cur_stop_idx; + } + } + //Reset current window + cur_start_idx = -1; + cur_stop_idx = -1; + } + } + if (win_start_idx == -1) { + throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. Convergence error."); + } + + double win_center = (results[win_stop_idx].first + results[win_start_idx].first) / 2.0; + double win_length = results[win_stop_idx].first - results[win_start_idx].first; + if (win_length < master_clk_period/4) { + throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. Valid window too narrow."); + } + + //Cycle slip the relative delay by a clock cycle to prevent sample misalignment + //fpga_clk_delay > 0 and 0 < win_center < 2*(1/MCR) so one cycle slip is all we need + bool cycle_slip = (win_center-fpga_clk_delay >= master_clk_period); + if (cycle_slip) { + win_center -= master_clk_period; + } + + if (apply_delay) { + UHD_MSG(status) << "Validating..." << std::flush; + //Apply delay + win_center = clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, win_center); //Sets ADC0 and ADC1 + wait_for_clk_locked(0.1); + //Validate + for (size_t r = 0; r < radios.size(); r++) { + radios[r]->self_test_adc(2000); + } + } else { + //Restore delay + clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, cached_clk_delay); //Sets ADC0 and ADC1 + } + + //Teardown + for (size_t r = 0; r < radios.size(); r++) { + radios[r]->_adc->set_test_word("normal", "normal"); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + } + UHD_MSG(status) << (boost::format(" done (FPGA->ADC=%.3fns%s, Window=%.3fns)\n") % + (win_center-fpga_clk_delay) % (cycle_slip?" +cyc":"") % win_length); + + return win_center; +} +/**************************************************************************** + * Helpers + ***************************************************************************/ +void x300_radio_ctrl_impl::_update_atr_leds(const std::string &rx_ant) +{ + const bool is_txrx = (rx_ant == "TX/RX"); + const int rx_led = (1 << 2); + const int tx_led = (1 << 1); + const int txrx_led = (1 << 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_IDLE, 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); + _leds->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, tx_led); + _leds->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, rx_led | tx_led); +} + +void x300_radio_ctrl_impl::_self_cal_adc_capture_delay(bool print_status) +{ + if (print_status) UHD_MSG(status) << "Running ADC capture delay self-cal..." << std::flush; + + static const boost::uint32_t NUM_DELAY_STEPS = 32; //The IDELAYE2 element has 32 steps + static const boost::uint32_t NUM_RETRIES = 2; //Retry self-cal if it fails in warmup situations + static const boost::int32_t MIN_WINDOW_LEN = 4; + + boost::int32_t win_start = -1, win_stop = -1; + boost::uint32_t iter = 0; + while (iter++ < NUM_RETRIES) { + for (boost::uint32_t dly_tap = 0; dly_tap < NUM_DELAY_STEPS; dly_tap++) { + //Apply delay + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, dly_tap); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0); + + boost::uint32_t err_code = 0; + + // -- Test I Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + _adc->set_test_word("ramp", "ones"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //10ms @ 200MHz = 2 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + if (_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_LOCKED)) { + err_code += _regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + // -- Test Q Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + _adc->set_test_word("ones", "ramp"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //10ms @ 200MHz = 2 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + if (_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_LOCKED)) { + err_code += _regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + if (err_code == 0) { + if (win_start == -1) { //This is the first window + win_start = dly_tap; + win_stop = dly_tap; + } else { //We are extending the window + win_stop = dly_tap; + } + } else { + if (win_start != -1) { //A valid window turned invalid + if (win_stop - win_start >= MIN_WINDOW_LEN) { + break; //Valid window found + } else { + win_start = -1; //Reset window + } + } + } + //UHD_MSG(status) << (boost::format("CapTap=%d, Error=%d\n") % dly_tap % err_code); + } + + //Retry the self-cal if it fails + if ((win_start == -1 || (win_stop - win_start) < MIN_WINDOW_LEN) && iter < NUM_RETRIES /*not last iteration*/) { + win_start = -1; + win_stop = -1; + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + } else { + break; + } + } + _adc->set_test_word("normal", "normal"); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + + if (win_start == -1) { + throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. Convergence error."); + } + + if (win_stop-win_start < MIN_WINDOW_LEN) { + throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. Valid window too narrow."); + } + + boost::uint32_t ideal_tap = (win_stop + win_start) / 2; + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, ideal_tap); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0); + + if (print_status) { + double tap_delay = (1.0e12 / _radio_clk_rate) / (2*32); //in ps + UHD_MSG(status) << boost::format(" done (Tap=%d, Window=%d, TapDelay=%.3fps, Iter=%d)\n") % ideal_tap % (win_stop-win_start) % tap_delay % iter; + } +} + +void x300_radio_ctrl_impl::_check_adc(const boost::uint32_t val) +{ + //Wait for previous control transaction to flush + user_reg_read64(regs::RB_TEST); + //Wait for ADC test pattern to propagate + boost::this_thread::sleep(boost::posix_time::microsec(5)); + //Read value of RX readback register and verify + boost::uint32_t adc_rb = static_cast<boost::uint32_t>(user_reg_read64(regs::RB_TEST)>>32); + adc_rb ^= 0xfffc0000; //adapt for I inversion in FPGA + if (val != adc_rb) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed for %s. (Exp=0x%x, Got=0x%x)")%unique_id()%val%adc_rb).str()); + } +} + +/**************************************************************************** + * Helpers + ***************************************************************************/ +bool x300_radio_ctrl_impl::check_radio_config() +{ + UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::check_radio_config() " << std::endl; + const fs_path rx_fe_path = fs_path("dboards" / _radio_slot / "rx_frontends"); + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + if (_tree->exists(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled")) { + const bool chan_active = _is_streamer_active(uhd::RX_DIRECTION, chan); + if (chan_active) { + _tree->access<bool>(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled") + .set(chan_active) + ; + } + } + } + + const fs_path tx_fe_path = fs_path("dboards" / _radio_slot / "tx_frontends"); + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + if (_tree->exists(tx_fe_path / _tx_fe_map.at(chan).db_fe_name / "enabled")) { + const bool chan_active = _is_streamer_active(uhd::TX_DIRECTION, chan); + if (chan_active) { + _tree->access<bool>(tx_fe_path / _tx_fe_map.at(chan).db_fe_name / "enabled") + .set(chan_active) + ; + } + } + } + + return true; +} + +/**************************************************************************** + * Register block + ***************************************************************************/ +UHD_RFNOC_BLOCK_REGISTER(x300_radio_ctrl, "X300Radio"); diff --git a/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp b/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp new file mode 100644 index 000000000..770519eba --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp @@ -0,0 +1,198 @@ +// +// Copyright 2015-2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP + +#include "radio_ctrl_impl.hpp" +#include "x300_clock_ctrl.hpp" +#include "spi_core_3000.hpp" +#include "x300_adc_ctrl.hpp" +#include "x300_dac_ctrl.hpp" +#include "x300_regs.hpp" +#include "rx_frontend_core_3000.hpp" +#include "tx_frontend_core_200.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/gpio_defs.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to an X300 radio. + */ +class x300_radio_ctrl_impl : public radio_ctrl_impl +{ +public: + typedef boost::shared_ptr<x300_radio_ctrl_impl> sptr; + + /************************************************************************ + * Structors + ***********************************************************************/ + UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(x300_radio_ctrl) + virtual ~x300_radio_ctrl_impl(); + + /************************************************************************ + * API calls + ***********************************************************************/ + double set_rate(double rate); + + void set_tx_antenna(const std::string &ant, const size_t chan); + void set_rx_antenna(const std::string &ant, const size_t chan); + + double set_tx_frequency(const double freq, const size_t chan); + double set_rx_frequency(const double freq, const size_t chan); + double get_tx_frequency(const size_t chan); + double get_rx_frequency(const size_t chan); + + double set_tx_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + + size_t get_chan_from_dboard_fe(const std::string &fe, const direction_t dir); + std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); + + double get_output_samp_rate(size_t port); + + /************************************************************************ + * Hardware setup and control + ***********************************************************************/ + /*! Set up the radio. No API calls may be made before this one. + */ + void setup_radio( + uhd::i2c_iface::sptr zpu_i2c, x300_clock_ctrl::sptr clock, bool verbose); + + void reset_codec(); + + void self_test_adc( + boost::uint32_t ramp_time_ms = 100); + + static void extended_adc_test( + const std::vector<x300_radio_ctrl_impl::sptr>&, double duration_s); + + static void synchronize_dacs( + const std::vector<x300_radio_ctrl_impl::sptr>& radios); + + static double self_cal_adc_xfer_delay( + const std::vector<x300_radio_ctrl_impl::sptr>& radios, + x300_clock_ctrl::sptr clock, + boost::function<void(double)> wait_for_clk_locked, + bool apply_delay); + +protected: + virtual bool check_radio_config(); + +private: + class radio_regmap_t : public uhd::soft_regmap_t { + public: + typedef boost::shared_ptr<radio_regmap_t> sptr; + class misc_outs_reg_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2] + UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3] + UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9] + UHD_DEFINE_SOFT_REG_FIELD(DAC_SYNC, /*width*/ 1, /*shift*/ 10); //[10] + + misc_outs_reg_t(): uhd::soft_reg32_wo_t(regs::sr_addr(regs::MISC_OUTS)) { + //Initial values + set(DAC_ENABLED, 0); + set(DAC_RESET_N, 0); + set(ADC_RESET, 0); + set(ADC_DATA_DLY_STB, 0); + set(ADC_DATA_DLY_VAL, 16); + set(ADC_CHECKER_ENABLED, 0); + set(DAC_SYNC, 0); + } + } misc_outs_reg; + + class misc_ins_reg_t : public uhd::soft_reg64_ro_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 32); //[0] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 33); //[1] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 34); //[2] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 35); //[3] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 36); //[4] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 37); //[5] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 38); //[6] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 39); //[7] + + misc_ins_reg_t(): uhd::soft_reg64_ro_t(regs::RB_MISC_IO) { } + } misc_ins_reg; + + radio_regmap_t(int radio_num) : soft_regmap_t("radio" + boost::lexical_cast<std::string>(radio_num) + "_regmap") { + add_to_map(misc_outs_reg, "misc_outs_reg", PRIVATE); + add_to_map(misc_ins_reg, "misc_ins_reg", PRIVATE); + } + }; + + struct x300_regs { + static const uint32_t TX_FE_BASE = 224; + static const uint32_t RX_FE_BASE = 232; + }; + + void _update_atr_leds(const std::string &rx_ant); + + void _self_cal_adc_capture_delay(bool print_status); + + void _check_adc(const boost::uint32_t val); + + void set_rx_fe_corrections(const uhd::fs_path &db_path, const uhd::fs_path &rx_fe_corr_path, const double lo_freq); + void set_tx_fe_corrections(const uhd::fs_path &db_path, const uhd::fs_path &tx_fe_corr_path, const double lo_freq); + +private: // members + enum radio_connection_t { PRIMARY, SECONDARY }; + + radio_connection_t _radio_type; + std::string _radio_slot; + //! Radio clock rate is the rate at which the ADC and DAC are running at. + // Not necessarily this block's sampling rate (tick rate). + double _radio_clk_rate; + + radio_regmap_t::sptr _regs; + usrp::gpio_atr::gpio_atr_3000::sptr _leds; + spi_core_3000::sptr _spi; + x300_adc_ctrl::sptr _adc; + x300_dac_ctrl::sptr _dac; + usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + + std::map<size_t, usrp::dboard_eeprom_t> _db_eeproms; + usrp::dboard_manager::sptr _db_manager; + + struct rx_fe_perif { + std::string name; + std::string db_fe_name; + rx_frontend_core_3000::sptr core; + }; + struct tx_fe_perif { + std::string name; + std::string db_fe_name; + tx_frontend_core_200::sptr core; + }; + + std::map<size_t, rx_fe_perif> _rx_fe_map; + std::map<size_t, tx_fe_perif> _tx_fe_map; + + bool _ignore_cal_file; + +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp index 3e0966c83..c5ed1460b 100644 --- a/host/lib/usrp/x300/x300_regs.hpp +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -22,45 +22,8 @@ #include <stdint.h> #include <uhd/utils/soft_register.hpp> -namespace uhd { namespace usrp { namespace radio { - -static UHD_INLINE uint32_t sr_addr(const uint32_t offset) -{ - return offset * 4; -} - -static const uint32_t DACSYNC = 5; -static const uint32_t LOOPBACK = 6; -static const uint32_t TEST = 7; -static const uint32_t SPI = 8; -static const uint32_t GPIO = 16; -static const uint32_t MISC_OUTS = 24; -static const uint32_t READBACK = 32; -static const uint32_t TX_CTRL = 64; -static const uint32_t RX_CTRL = 96; -static const uint32_t TIME = 128; -static const uint32_t RX_DSP = 144; -static const uint32_t TX_DSP = 184; -static const uint32_t LEDS = 195; -static const uint32_t FP_GPIO = 200; -static const uint32_t RX_FRONT = 208; -static const uint32_t TX_FRONT = 216; - -static const uint32_t RB32_GPIO = 0; -static const uint32_t RB32_SPI = 4; -static const uint32_t RB64_TIME_NOW = 8; -static const uint32_t RB64_TIME_PPS = 16; -static const uint32_t RB32_TEST = 24; -static const uint32_t RB32_RX = 28; -static const uint32_t RB32_FP_GPIO = 32; -static const uint32_t RB32_MISC_INS = 36; - -}}} // namespace - -#define localparam static const int - -localparam BL_ADDRESS = 0; -localparam BL_DATA = 1; +static const int BL_ADDRESS = 0; +static const int BL_DATA = 1; //wishbone settings map - relevant to host code #define SET0_BASE 0xa000 @@ -70,13 +33,15 @@ localparam BL_DATA = 1; #define I2C1_BASE 0xff00 #define SR_ADDR(base, offset) ((base) + (offset)*4) -localparam ZPU_SR_LEDS = 00; -localparam ZPU_SR_SW_RST = 01; -localparam ZPU_SR_CLOCK_CTRL = 02; -localparam ZPU_SR_XB_LOCAL = 03; -localparam ZPU_SR_SPI = 32; -localparam ZPU_SR_ETHINT0 = 40; -localparam ZPU_SR_ETHINT1 = 56; +static const int ZPU_SR_LEDS = 00; +static const int ZPU_SR_SW_RST = 01; +static const int ZPU_SR_CLOCK_CTRL = 02; +static const int ZPU_SR_XB_LOCAL = 03; +static const int ZPU_SR_SPI = 32; +static const int ZPU_SR_ETHINT0 = 40; +static const int ZPU_SR_ETHINT1 = 56; +static const int ZPU_SR_DRAM_FIFO0 = 72; +static const int ZPU_SR_DRAM_FIFO1 = 80; //reset bits #define ZPU_SR_SW_RST_ETH_PHY (1<<0) @@ -84,11 +49,17 @@ localparam ZPU_SR_ETHINT1 = 56; #define ZPU_SR_SW_RST_RADIO_CLK_PLL (1<<2) #define ZPU_SR_SW_RST_ADC_IDELAYCTRL (1<<3) -localparam ZPU_RB_SPI = 2; -localparam ZPU_RB_CLK_STATUS = 3; -localparam ZPU_RB_COMPAT_NUM = 6; -localparam ZPU_RB_ETH_TYPE0 = 4; -localparam ZPU_RB_ETH_TYPE1 = 5; +static const int ZPU_RB_SPI = 2; +static const int ZPU_RB_CLK_STATUS = 3; +static const int ZPU_RB_COMPAT_NUM = 6; +static const int ZPU_RB_NUM_CE = 7; +static const int ZPU_RB_GIT_HASH = 10; +static const int ZPU_RB_SFP0_TYPE = 4; +static const int ZPU_RB_SFP1_TYPE = 5; + +static const uint32_t RB_SFP_1G_ETH = 0; +static const uint32_t RB_SFP_10G_ETH = 1; +static const uint32_t RB_SFP_AURORA = 2; //spi slaves on radio #define DB_DAC_SEN (1 << 7) @@ -244,49 +215,6 @@ namespace uhd { namespace usrp { namespace x300 { } }; - class radio_regmap_t : public uhd::soft_regmap_t { - public: - typedef boost::shared_ptr<radio_regmap_t> sptr; - class misc_outs_reg_t : public uhd::soft_reg32_wo_t { - public: - UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0] - UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1] - UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2] - UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3] - UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9] - - misc_outs_reg_t(): uhd::soft_reg32_wo_t(uhd::usrp::radio::sr_addr(uhd::usrp::radio::MISC_OUTS)) { - //Initial values - set(DAC_ENABLED, 0); - set(DAC_RESET_N, 0); - set(ADC_RESET, 0); - set(ADC_DATA_DLY_STB, 0); - set(ADC_DATA_DLY_VAL, 16); - set(ADC_CHECKER_ENABLED, 0); - } - } misc_outs_reg; - - class misc_ins_reg_t : public uhd::soft_reg32_ro_t { - public: - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 0); //[0] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 1); //[1] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 2); //[2] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 3); //[3] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 4); //[4] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 5); //[5] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 6); //[6] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 7); //[7] - - misc_ins_reg_t(): uhd::soft_reg32_ro_t(uhd::usrp::radio::RB32_MISC_INS) { } - } misc_ins_reg; - - radio_regmap_t(int radio_num) : soft_regmap_t("radio" + boost::lexical_cast<std::string>(radio_num) + "_regmap") { - add_to_map(misc_outs_reg, "misc_outs_reg", PUBLIC); - add_to_map(misc_ins_reg, "misc_ins_reg", PUBLIC); - } - }; - }}} #endif /* INCLUDED_X300_REGS_HPP */ diff --git a/host/lib/usrp_clock/octoclock/CMakeLists.txt b/host/lib/usrp_clock/octoclock/CMakeLists.txt index a54d27c52..d2b70e356 100644 --- a/host/lib/usrp_clock/octoclock/CMakeLists.txt +++ b/host/lib/usrp_clock/octoclock/CMakeLists.txt @@ -18,8 +18,6 @@ ######################################################################## # Conditionally configure the OctoClock support ######################################################################## -LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_OCTOCLOCK) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_eeprom.cpp diff --git a/host/lib/usrp_clock/octoclock/octoclock_impl.cpp b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp index 15d919272..297983c15 100644 --- a/host/lib/usrp_clock/octoclock/octoclock_impl.cpp +++ b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp @@ -243,21 +243,21 @@ octoclock_impl::octoclock_impl(const device_addr_t &_device_addr){ _oc_dict[oc].eeprom = octoclock_eeprom_t(_oc_dict[oc].ctrl_xport, _proto_ver); _tree->create<octoclock_eeprom_t>(oc_path / "eeprom") .set(_oc_dict[oc].eeprom) - .subscribe(boost::bind(&octoclock_impl::_set_eeprom, this, oc, _1)); + .add_coerced_subscriber(boost::bind(&octoclock_impl::_set_eeprom, this, oc, _1)); //////////////////////////////////////////////////////////////////// // Initialize non-GPSDO sensors //////////////////////////////////////////////////////////////////// _tree->create<boost::uint32_t>(oc_path / "time") - .publish(boost::bind(&octoclock_impl::_get_time, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_get_time, this, oc)); _tree->create<sensor_value_t>(oc_path / "sensors/ext_ref_detected") - .publish(boost::bind(&octoclock_impl::_ext_ref_detected, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_ext_ref_detected, this, oc)); _tree->create<sensor_value_t>(oc_path / "sensors/gps_detected") - .publish(boost::bind(&octoclock_impl::_gps_detected, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_gps_detected, this, oc)); _tree->create<sensor_value_t>(oc_path / "sensors/using_ref") - .publish(boost::bind(&octoclock_impl::_which_ref, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_which_ref, this, oc)); _tree->create<sensor_value_t>(oc_path / "sensors/switch_pos") - .publish(boost::bind(&octoclock_impl::_switch_pos, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_switch_pos, this, oc)); //////////////////////////////////////////////////////////////////// // Check reference and GPSDO @@ -277,7 +277,7 @@ octoclock_impl::octoclock_impl(const device_addr_t &_device_addr){ if(_oc_dict[oc].gps and _oc_dict[oc].gps->gps_detected()){ BOOST_FOREACH(const std::string &name, _oc_dict[oc].gps->get_sensors()){ _tree->create<sensor_value_t>(oc_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, _oc_dict[oc].gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _oc_dict[oc].gps, name)); } } else{ diff --git a/host/lib/utils/log.cpp b/host/lib/utils/log.cpp index 8d42af9c4..4e58ce894 100644 --- a/host/lib/utils/log.cpp +++ b/host/lib/utils/log.cpp @@ -1,5 +1,5 @@ // -// Copyright 2012,2014 Ettus Research LLC +// Copyright 2012,2014,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -24,19 +24,7 @@ #include <boost/thread/mutex.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/thread/locks.hpp> -#ifdef BOOST_MSVC -//whoops! https://svn.boost.org/trac/boost/ticket/5287 -//enjoy this useless dummy class instead -namespace boost{ namespace interprocess{ - struct file_lock{ - file_lock(const char * = NULL){} - void lock(void){} - void unlock(void){} - }; -}} //namespace -#else #include <boost/interprocess/sync/file_lock.hpp> -#endif #include <fstream> #include <cctype> diff --git a/host/lib/utils/msg.cpp b/host/lib/utils/msg.cpp index de98ada64..95879a116 100644 --- a/host/lib/utils/msg.cpp +++ b/host/lib/utils/msg.cpp @@ -79,6 +79,8 @@ void uhd::msg::register_handler(const handler_t &handler){ } static void default_msg_handler(uhd::msg::type_t type, const std::string &msg){ + static boost::mutex msg_mutex; + boost::mutex::scoped_lock lock(msg_mutex); switch(type){ case uhd::msg::fastpath: std::cerr << msg << std::flush; diff --git a/host/lib/utils/paths.cpp b/host/lib/utils/paths.cpp index 9cbc83062..38839c8d4 100644 --- a/host/lib/utils/paths.cpp +++ b/host/lib/utils/paths.cpp @@ -17,7 +17,6 @@ #include <uhd/config.hpp> #include <uhd/exception.hpp> -#include <uhd/transport/nirio/nifpga_lvbitx.h> #include <uhd/utils/paths.hpp> #include <boost/algorithm/string.hpp> diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 8b12c961f..8f7fdcd7c 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -45,11 +45,26 @@ SET(test_sources subdev_spec_test.cpp time_spec_test.cpp vrt_test.cpp + expert_test.cpp + fe_conn_test.cpp ) #turn each test cpp file into an executable with an int main() function ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN) +IF(ENABLE_RFNOC) + LIST(APPEND test_sources + block_id_test.cpp + blockdef_test.cpp + device3_test.cpp + graph_search_test.cpp + node_connect_test.cpp + rate_node_test.cpp + stream_sig_test.cpp + tick_node_test.cpp + ) +ENDIF(ENABLE_RFNOC) + IF(ENABLE_C_API) LIST(APPEND test_sources eeprom_c_test.c @@ -70,6 +85,36 @@ FOREACH(test_source ${test_sources}) UHD_INSTALL(TARGETS ${test_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) ENDFOREACH(test_source) +# Other tests that don't directly link with libuhd: (TODO find a nicer way to do this) +INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}/lib/rfnoc/nocscript/) +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/) +ADD_EXECUTABLE(nocscript_expr_test + nocscript_expr_test.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp +) +TARGET_LINK_LIBRARIES(nocscript_expr_test uhd ${Boost_LIBRARIES}) +UHD_ADD_TEST(nocscript_expr_test nocscript_expr_test) +UHD_INSTALL(TARGETS nocscript_expr_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) + +ADD_EXECUTABLE(nocscript_ftable_test + nocscript_ftable_test.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/function_table.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp +) +TARGET_LINK_LIBRARIES(nocscript_ftable_test uhd ${Boost_LIBRARIES}) +UHD_ADD_TEST(nocscript_ftable_test nocscript_ftable_test) +UHD_INSTALL(TARGETS nocscript_ftable_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) + +ADD_EXECUTABLE(nocscript_parser_test + nocscript_parser_test.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/parser.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/function_table.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp +) +TARGET_LINK_LIBRARIES(nocscript_parser_test uhd ${Boost_LIBRARIES}) +UHD_ADD_TEST(nocscript_parser_test nocscript_parser_test) +UHD_INSTALL(TARGETS nocscript_parser_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) + ######################################################################## # demo of a loadable module ######################################################################## @@ -77,3 +122,5 @@ IF(MSVC OR APPLE OR LINUX) ADD_LIBRARY(module_test MODULE module_test.cpp) TARGET_LINK_LIBRARIES(module_test uhd) ENDIF() + +ADD_SUBDIRECTORY(devtest) diff --git a/host/tests/block_id_test.cpp b/host/tests/block_id_test.cpp new file mode 100644 index 000000000..cab8a8cca --- /dev/null +++ b/host/tests/block_id_test.cpp @@ -0,0 +1,117 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <iostream> +#include <boost/test/unit_test.hpp> +#include <uhd/exception.hpp> +#include <uhd/rfnoc/block_id.hpp> + +using namespace uhd::rfnoc; + +BOOST_AUTO_TEST_CASE(test_block_id) { + BOOST_CHECK(block_id_t::is_valid_block_id("00/Filter_1")); + BOOST_CHECK(not block_id_t::is_valid_block_id("0/MAG_SQUARE")); + BOOST_CHECK(block_id_t::is_valid_blockname("FilterFoo")); + BOOST_CHECK(not block_id_t::is_valid_blockname("Filter_Foo")); + BOOST_CHECK(not block_id_t::is_valid_blockname("Filter/Foo")); + BOOST_CHECK(not block_id_t::is_valid_blockname("0Filter/Foo")); + BOOST_CHECK(not block_id_t::is_valid_blockname("0/Filter/Foo")); + + BOOST_REQUIRE_THROW(block_id_t invalid_block_id("0Filter/1"), uhd::value_error); + + block_id_t block_id("0/FFT_1"); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 0); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 1); + + block_id.set_device_no(17); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 1); + + block_id.set_block_count(11); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 11); + + block_id.set_block_name("FooBar"); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 11); + + BOOST_CHECK(not block_id.set_block_name("Foo_Bar")); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar"); // Is unchanged because invalid + BOOST_CHECK_EQUAL(block_id.get_block_count(), 11); + + block_id++; + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 12); + + block_id_t other_block_id(7, "BlockName", 3); + BOOST_CHECK_EQUAL(other_block_id.get_device_no(), 7); + BOOST_CHECK_EQUAL(other_block_id.get_block_name(), "BlockName"); + BOOST_CHECK_EQUAL(other_block_id.get_block_count(), 3); + BOOST_CHECK_EQUAL(other_block_id.to_string(), "7/BlockName_3"); + + // Cast + std::string block_id_str = std::string(other_block_id); + std::cout << "Should print '7/BlockName_3': " << block_id_str << std::endl; + BOOST_CHECK_EQUAL(block_id_str, "7/BlockName_3"); + + // Operators + std::cout << "Testing ostream printing (<<): " << other_block_id << std::endl; + BOOST_CHECK_EQUAL(other_block_id, block_id_str); + BOOST_CHECK_EQUAL(other_block_id, "7/BlockName_3"); + + // match() + BOOST_CHECK(other_block_id.match("BlockName")); + BOOST_CHECK(other_block_id.match("7/BlockName")); + BOOST_CHECK(other_block_id.match("BlockName_3")); + BOOST_CHECK(other_block_id.match("7/BlockName_3")); + BOOST_CHECK(not other_block_id.match("8/BlockName")); + BOOST_CHECK(not other_block_id.match("8/BlockName_3")); + BOOST_CHECK(not other_block_id.match("Block_Name_3")); + BOOST_CHECK(not other_block_id.match("BlockName_4")); + BOOST_CHECK(not other_block_id.match("BlockName_X")); + BOOST_CHECK(not other_block_id.match("2093ksdjfflsdkjf")); +} + +BOOST_AUTO_TEST_CASE(test_block_id_set) { + // test set() + block_id_t block_id_for_set(5, "Blockname", 9); + block_id_for_set.set("FirFilter"); + BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 5); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "FirFilter"); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 9); + block_id_for_set.set("1/FirFilter2"); + BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 1); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "FirFilter2"); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 9); + block_id_for_set.set("Sync_3"); + BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 1); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "Sync"); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 3); +} + +BOOST_AUTO_TEST_CASE(test_block_id_cmp) { + BOOST_CHECK(block_id_t("0/FFT_1") == block_id_t("0/FFT_1")); + BOOST_CHECK(block_id_t("0/FFT_1") != block_id_t("1/FFT_1")); + BOOST_CHECK(block_id_t("0/FFT_1") < block_id_t("1/aaaaaaaaa_0")); + BOOST_CHECK(not (block_id_t("0/FFT_1") > block_id_t("1/aaaaaaaaa_0"))); +} diff --git a/host/tests/blockdef_test.cpp b/host/tests/blockdef_test.cpp new file mode 100644 index 000000000..2cd06a43a --- /dev/null +++ b/host/tests/blockdef_test.cpp @@ -0,0 +1,94 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <iostream> +#include <map> +#include <boost/cstdint.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/test/unit_test.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <uhd/rfnoc/blockdef.hpp> + +using namespace uhd::rfnoc; + +BOOST_AUTO_TEST_CASE(test_lookup) { + std::map<boost::uint64_t, std::string> blocknames = boost::assign::list_of< std::pair<boost::uint64_t, std::string> > + (0, "NullSrcSink") + (0xFF70000000000000, "FFT") + (0xF112000000000001, "FIR") + (0xF1F0000000000000, "FIFO") + (0xD053000000000000, "Window") + (0x5CC0000000000000, "SchmidlCox") + ; + + std::cout << blocknames.size() << std::endl; + + for (std::map<boost::uint64_t, std::string>::iterator it = blocknames.begin(); it != blocknames.end(); ++it) { + std::cout << "Testing " << it->second << " => " << str(boost::format("%016X") % it->first) << std::endl; + blockdef::sptr block_definition = blockdef::make_from_noc_id(it->first); + // If the previous function fails, it'll return a NULL pointer + BOOST_REQUIRE(block_definition); + BOOST_CHECK(block_definition->is_block()); + BOOST_CHECK_EQUAL(block_definition->get_name(), it->second); + } +} + +BOOST_AUTO_TEST_CASE(test_ports) { + // Create an FFT: + blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000); + blockdef::ports_t in_ports = block_definition->get_input_ports(); + BOOST_REQUIRE_EQUAL(in_ports.size(), 1); + BOOST_CHECK_EQUAL(in_ports[0]["name"], "in"); + BOOST_CHECK_EQUAL(in_ports[0]["type"], "sc16"); + BOOST_CHECK(in_ports[0].has_key("vlen")); + BOOST_CHECK(in_ports[0].has_key("pkt_size")); + + blockdef::ports_t out_ports = block_definition->get_output_ports(); + BOOST_REQUIRE_EQUAL(out_ports.size(), 1); + BOOST_CHECK_EQUAL(out_ports[0]["name"], "out"); + BOOST_CHECK(out_ports[0].has_key("vlen")); + BOOST_CHECK(out_ports[0].has_key("pkt_size")); + + BOOST_CHECK_EQUAL(block_definition->get_all_port_numbers().size(), 1); + BOOST_CHECK_EQUAL(block_definition->get_all_port_numbers()[0], 0); +} + +BOOST_AUTO_TEST_CASE(test_args) { + // Create an FFT: + blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000); + blockdef::args_t args = block_definition->get_args(); + BOOST_REQUIRE(args.size() >= 3); + BOOST_CHECK_EQUAL(args[0]["name"], "spp"); + BOOST_CHECK_EQUAL(args[0]["type"], "int"); + BOOST_CHECK_EQUAL(args[0]["value"], "256"); +} + +BOOST_AUTO_TEST_CASE(test_regs) { + // Create an FFT: + blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000); + blockdef::registers_t sregs = block_definition->get_settings_registers(); + BOOST_REQUIRE_EQUAL(sregs.size(), 3); + BOOST_CHECK_EQUAL(sregs["FFT_RESET"], 131); + BOOST_CHECK_EQUAL(sregs["FFT_SIZE_LOG2"], 132); + BOOST_CHECK_EQUAL(sregs["MAGNITUDE_OUT"], 133); + blockdef::registers_t user_regs = block_definition->get_readback_registers(); + BOOST_REQUIRE_EQUAL(user_regs.size(), 2); + BOOST_CHECK_EQUAL(user_regs["RB_FFT_RESET"], 0); + BOOST_CHECK_EQUAL(user_regs["RB_MAGNITUDE_OUT"], 1); +} + diff --git a/host/tests/convert_test.cpp b/host/tests/convert_test.cpp index d71d756dd..8d359d2e2 100644 --- a/host/tests/convert_test.cpp +++ b/host/tests/convert_test.cpp @@ -417,16 +417,16 @@ BOOST_AUTO_TEST_CASE(test_convert_types_sc16_and_sc8){ } /*********************************************************************** - * Test short conversion + * Test u8 conversion **********************************************************************/ static void test_convert_types_u8( size_t nsamps, convert::id_type &id ){ //fill the input samples std::vector<boost::uint8_t> input(nsamps), output(nsamps); - //BOOST_FOREACH(boost::uint8_t &in, input) in = boost::uint8_t(std::rand() & 0xFF); - boost::uint32_t d = 48; - BOOST_FOREACH(boost::uint8_t &in, input) in = d++; + BOOST_FOREACH(boost::uint8_t &in, input) in = boost::uint8_t(std::rand() & 0xFF); + //boost::uint32_t d = 48; + //BOOST_FOREACH(boost::uint8_t &in, input) in = d++; //run the loopback and test convert::id_type in_id = id; @@ -455,3 +455,158 @@ BOOST_AUTO_TEST_CASE(test_convert_types_u8_and_u8){ test_convert_types_u8(nsamps, id); } } + +/*********************************************************************** + * Test s8 conversion + **********************************************************************/ +static void test_convert_types_s8( + size_t nsamps, convert::id_type &id +){ + //fill the input samples + std::vector<boost::int8_t> input(nsamps), output(nsamps); + BOOST_FOREACH(boost::int8_t &in, input) in = boost::int8_t(std::rand() & 0xFF); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_s8_and_s8){ + convert::id_type id; + id.input_format = "s8"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "s8_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_s8(nsamps, id); + } + + //try various lengths to test edge cases + id.output_format = "s8_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_s8(nsamps, id); + } +} + +/*********************************************************************** + * Test s16 conversion + **********************************************************************/ +static void test_convert_types_s16( + size_t nsamps, convert::id_type &id +){ + //fill the input samples + std::vector<boost::int16_t> input(nsamps), output(nsamps); + BOOST_FOREACH(boost::int16_t &in, input) in = boost::int16_t(std::rand() & 0xFFFF); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_s16_and_s16){ + convert::id_type id; + id.input_format = "s16"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "s16_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_s16(nsamps, id); + } + + //try various lengths to test edge cases + id.output_format = "s16_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_s16(nsamps, id); + } +} + +/*********************************************************************** + * Test fc32 -> fc32 conversion + **********************************************************************/ +static void test_convert_types_fc32( + size_t nsamps, convert::id_type &id +){ + //fill the input samples + std::vector< std::complex<float> > input(nsamps), output(nsamps); + BOOST_FOREACH(fc32_t &in, input) in = fc32_t( + (std::rand()/float(RAND_MAX/2)) - 1, + (std::rand()/float(RAND_MAX/2)) - 1 + ); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_fc32_and_fc32){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "fc32_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_fc32(nsamps, id); + } + + //try various lengths to test edge cases + id.output_format = "fc32_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_fc32(nsamps, id); + } +} + +/*********************************************************************** + * Test f32 -> f32 conversion + **********************************************************************/ +static void test_convert_types_f32( + size_t nsamps, convert::id_type &id +){ + //fill the input samples + std::vector<float> input(nsamps), output(nsamps); + BOOST_FOREACH(float &in, input) in = float((std::rand()/float(RAND_MAX/2)) - 1); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_f32_and_f32){ + convert::id_type id; + id.input_format = "f32"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "f32_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_f32(nsamps, id); + } + + //try various lengths to test edge cases + id.output_format = "f32_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_f32(nsamps, id); + } +} diff --git a/host/tests/device3_test.cpp b/host/tests/device3_test.cpp new file mode 100644 index 000000000..593facb9a --- /dev/null +++ b/host/tests/device3_test.cpp @@ -0,0 +1,175 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <exception> +#include <iostream> +#include <boost/test/unit_test.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/device3.hpp> +#include <uhd/rfnoc/block_ctrl.hpp> +#include <uhd/rfnoc/graph.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +static const boost::uint64_t TEST_NOC_ID = 0xAAAABBBBCCCCDDDD; +static const sid_t TEST_SID0 = 0x00000200; // 0.0.2.0 +static const sid_t TEST_SID1 = 0x00000210; // 0.0.2.F + +// Pseudo-wb-iface +class pseudo_wb_iface_impl : public uhd::wb_iface +{ + public: + pseudo_wb_iface_impl() {}; + ~pseudo_wb_iface_impl() {}; + + void poke64(const wb_addr_type addr, const boost::uint64_t data) { + std::cout << str(boost::format("[PSEUDO] poke64 to addr: %016X, data == %016X") % addr % data) << std::endl; + }; + + boost::uint64_t peek64(const wb_addr_type addr) { + std::cout << str(boost::format("[PSEUDO] peek64 to addr: %016X") % addr) << std::endl; + switch (addr) { + case SR_READBACK_REG_ID: + return TEST_NOC_ID; + case SR_READBACK_REG_FIFOSIZE: + return 0x000000000000000B; + case SR_READBACK_REG_USER: + return 0x0123456789ABCDEF; + default: + return 0; + } + return 0; + } + + void poke32(const wb_addr_type addr, const boost::uint32_t data) { + std::cout << str(boost::format("poke32 to addr: %08X, data == %08X") % addr % data) << std::endl; + } + + boost::uint32_t peek32(const wb_addr_type addr) { + std::cout << str(boost::format("peek32 to addr: %08X") % addr) << std::endl; + return 0; + } +}; + +// Pseudo-device +class pseudo_device3_impl : public uhd::device3 +{ + public: + pseudo_device3_impl() + { + _tree = uhd::property_tree::make(); + _tree->create<std::string>("/name").set("Test Pseudo-Device3"); + + // We can re-use this: + std::map<size_t, wb_iface::sptr> ctrl_ifaces = boost::assign::map_list_of + (0, wb_iface::sptr(new pseudo_wb_iface_impl())) + ; + + // Add two block controls: + uhd::rfnoc::make_args_t make_args; + make_args.ctrl_ifaces = ctrl_ifaces; + make_args.base_address = TEST_SID0.get_dst(); + make_args.device_index = 0; + make_args.tree = _tree; + make_args.is_big_endian = false; + std::cout << "[PSEUDO] Generating block controls 1/2:" << std::endl; + _rfnoc_block_ctrl.push_back( block_ctrl_base::make(make_args) ); + + std::cout << "[PSEUDO] Generating block controls 2/2:" << std::endl; + make_args.base_address = TEST_SID1.get_dst(); + _rfnoc_block_ctrl.push_back( block_ctrl::make(make_args) ); + } + + rx_streamer::sptr get_rx_stream(const stream_args_t &args) { + throw uhd::not_implemented_error(args.args.to_string()); + } + + tx_streamer::sptr get_tx_stream(const stream_args_t &args) { + throw uhd::not_implemented_error(args.args.to_string()); + } + + bool recv_async_msg(async_metadata_t &async_metadata, double timeout) { + throw uhd::not_implemented_error(str(boost::format("%d %f") % async_metadata.channel % timeout)); + } + + rfnoc::graph::sptr create_graph(const std::string &) { return rfnoc::graph::sptr(); } +}; + +device3::sptr make_pseudo_device() +{ + return device3::sptr(new pseudo_device3_impl()); +} + +class dummy_block_ctrl : public block_ctrl { + int foo; +}; + +BOOST_AUTO_TEST_CASE(test_device3) { + device3::sptr my_device = make_pseudo_device(); + + std::cout << "Checking block 0..." << std::endl; + BOOST_REQUIRE(my_device->find_blocks("Block").size()); + + std::cout << "Getting block 0..." << std::endl; + block_ctrl_base::sptr block0 = my_device->get_block_ctrl(my_device->find_blocks("Block")[0]); + BOOST_REQUIRE(block0); + BOOST_CHECK_EQUAL(block0->get_block_id(), "0/Block_0"); + + std::cout << "Checking block 1..." << std::endl; + BOOST_REQUIRE(my_device->has_block(block_id_t("0/Block_1"))); + + std::cout << "Getting block 1..." << std::endl; + block_ctrl_base::sptr block1 = my_device->get_block_ctrl(block_id_t("0/Block_1")); + BOOST_REQUIRE(block1); + BOOST_CHECK_EQUAL(block1->get_block_id(), "0/Block_1"); +} + +BOOST_AUTO_TEST_CASE(test_device3_cast) { + device3::sptr my_device = make_pseudo_device(); + + std::cout << "Getting block 0..." << std::endl; + block_ctrl::sptr block0 = my_device->get_block_ctrl<block_ctrl>(block_id_t("0/Block_0")); + BOOST_REQUIRE(block0); + BOOST_CHECK_EQUAL(block0->get_block_id(), "0/Block_0"); + + std::cout << "Getting block 1..." << std::endl; + block_ctrl_base::sptr block1 = my_device->get_block_ctrl<block_ctrl>(block_id_t("0/Block_1")); + BOOST_CHECK_EQUAL(block1->get_block_id(), "0/Block_1"); +} + +BOOST_AUTO_TEST_CASE(test_device3_fail) { + device3::sptr my_device = make_pseudo_device(); + + BOOST_CHECK(not my_device->has_block(block_id_t("0/FooBarBlock_0"))); + BOOST_CHECK(not my_device->has_block<dummy_block_ctrl>(block_id_t("0/Block_1"))); + + BOOST_CHECK(my_device->find_blocks("FooBarBlock").size() == 0); + BOOST_CHECK(my_device->find_blocks<block_ctrl>("FooBarBlock").size() == 0); + + BOOST_REQUIRE_THROW( + my_device->get_block_ctrl(block_id_t("0/FooBarBlock_17")), + uhd::lookup_error + ); + BOOST_REQUIRE_THROW( + my_device->get_block_ctrl<dummy_block_ctrl>(block_id_t("0/Block_1")), + uhd::lookup_error + ); +} + +// vim: sw=4 et: diff --git a/host/tests/devtest/CMakeLists.txt b/host/tests/devtest/CMakeLists.txt new file mode 100644 index 000000000..6fa921bbd --- /dev/null +++ b/host/tests/devtest/CMakeLists.txt @@ -0,0 +1,58 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +# Formatting +MESSAGE(STATUS "") + +# All devtest files get installed: +FILE(GLOB py_devtest_files "*.py") +UHD_INSTALL(PROGRAMS + ${py_devtest_files} + DESTINATION ${PKG_LIB_DIR}/tests/devtest + COMPONENT tests +) + +# Arguments: +# - pattern: This will be used to identify which devtest_*.py is to be executed. +# - filter: Will be used in args strings as "type=<filter>". +# - devtype: A descriptive string. Is only used for CMake output. +MACRO(ADD_DEVTEST pattern filter devtype) + MESSAGE(STATUS "Adding ${devtype} device test target") + ADD_CUSTOM_TARGET("test_${pattern}" + ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_testsuite.py + "--src-dir" "${CMAKE_CURRENT_SOURCE_DIR}" + "--devtest-pattern" "${pattern}" + "--device-filter" "${filter}" + "--build-type" "${CMAKE_BUILD_TYPE}" + "--build-dir" "${CMAKE_BINARY_DIR}" + COMMENT "Running device test on all connected ${devtype} devices:" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ) +ENDMACRO(ADD_DEVTEST) + +IF(ENABLE_B200) + ADD_DEVTEST("b2xx" "b200" "B2XX") +ENDIF(ENABLE_B200) +IF(ENABLE_X300) + ADD_DEVTEST("x3x0" "x300" "X3x0") +ENDIF(ENABLE_X300) +IF(ENABLE_E300) + ADD_DEVTEST("e3xx" "e3x0" "E3XX") +ENDIF(ENABLE_E300) + +# Formatting +MESSAGE(STATUS "") diff --git a/host/tests/devtest/README.md b/host/tests/devtest/README.md new file mode 100644 index 000000000..ee1ff3c9f --- /dev/null +++ b/host/tests/devtest/README.md @@ -0,0 +1,28 @@ +# Device Tests + +These are a set of tests to be run with one or more attached devices. +None of these tests require special configuration; e.g., the X3x0 test +will work regardless of attached daughterboards, FPGIO wiring etc. + +## Adding new tests + +To add new tests, add new files with classes that derive from unittest.TestCase. +Most of the time, you'll want to derive from `uhd_test_case` or +`uhd_example_test_case`. + +## Adding new devices + +To add new devices, follow these steps: + +1) Add an entry to the CMakeLists.txt file in this directory using the + `ADD_DEVTEST()` macro. +2) Add a `devtest_pattern.py` file to this directory, where `pattern` is + the same pattern used in the `ADD_DEVTEST()` macro. +3) Edit this devtest file to import all the tests you want to run. Some + may require parameterization. + +The devtest file is 'executed' using Python's unittest module, so it doesn't +require any actual commands. If the device needs special initialization, +commands inside this file will be executed *if* they are *not* in a +`if __name__ == "__main__"` conditional. + diff --git a/host/tests/devtest/benchmark_rate_test.py b/host/tests/devtest/benchmark_rate_test.py new file mode 100755 index 000000000..6c5a75d7f --- /dev/null +++ b/host/tests/devtest/benchmark_rate_test.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# +# Copyright 2015-2016 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test using benchmark_rate. """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_benchmark_rate_test(uhd_example_test_case): + """ + Run benchmark_rate in various configurations. + """ + tests = {} + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_benchmark_rate_test.tests + + def run_test(self, test_name, test_args): + """ + Runs benchmark_rate with the given parameters. Parses output and writes + results to file. + + We always run both tx and rx. + """ + rel_samp_err_threshold = 0.1 # 10% off is still quite generous + samp_rate = test_args.get('rate', 1e6) + duration = test_args.get('duration', 1) + chan = test_args.get('chan', '0') + n_chans = len(chan.split(",")) + expected_samples = n_chans * duration * samp_rate + self.log.info('Running test {n}, Channel = {c}, Sample Rate = {r}'.format( + n=test_name, c=chan, r=samp_rate, + )) + args = [ + self.create_addr_args_str(), + '--duration', str(duration), + '--channels', str(chan), + ] + if 'tx' in test_args.get('direction', ''): + args.append('--tx_rate') + args.append(str(samp_rate)) + if 'rx' in test_args.get('direction', ''): + args.append('--rx_rate') + args.append(str(samp_rate)) + (app, run_results) = self.run_example('benchmark_rate', args) + match = re.search(r'(Num received samples):\s*(.*)', app.stdout) + run_results['num_rx_samples'] = int(match.group(2)) if match else -1 + if run_results['num_rx_samples'] != -1: + run_results['rel_rx_samples_error'] = 1.0 * abs(run_results['num_rx_samples'] - expected_samples) / expected_samples + else: + run_results['rel_rx_samples_error'] = 100 + match = re.search(r'(Num dropped samples):\s*(.*)', app.stdout) + run_results['num_rx_dropped'] = int(match.group(2)) if match else -1 + match = re.search(r'(Num overflows detected):\s*(.*)', app.stdout) + run_results['num_rx_overruns'] = int(match.group(2)) if match else -1 + match = re.search(r'(Num transmitted samples):\s*(.*)', app.stdout) + run_results['num_tx_samples'] = int(match.group(2)) if match else -1 + if run_results['num_tx_samples'] != -1: + run_results['rel_tx_samples_error'] = 1.0 * abs(run_results['num_tx_samples'] - expected_samples) / expected_samples + else: + run_results['rel_tx_samples_error'] = 100 + match = re.search(r'(Num sequence errors):\s*(.*)', app.stdout) + run_results['num_tx_seqerrs'] = int(match.group(2)) if match else -1 + match = re.search(r'(Num underflows detected):\s*(.*)', app.stdout) + run_results['num_tx_underruns'] = int(match.group(2)) if match else -1 + match = re.search(r'(Num timeouts):\s*(.*)', app.stdout) + run_results['num_timeouts'] = int(match.group(2)) if match else -1 + run_results['passed'] = all([ + run_results['return_code'] == 0, + run_results['num_rx_dropped'] == 0, + run_results['num_tx_seqerrs'] == 0, + run_results['num_tx_underruns'] <= test_args.get('acceptable-underruns', 0), + run_results['num_rx_samples'] > 0, + run_results['num_tx_samples'] > 0, + run_results['num_timeouts'] == 0, + run_results['rel_rx_samples_error'] < rel_samp_err_threshold, + run_results['rel_tx_samples_error'] < rel_samp_err_threshold, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/devtest_b2xx.py b/host/tests/devtest/devtest_b2xx.py new file mode 100755 index 000000000..4b81f0afe --- /dev/null +++ b/host/tests/devtest/devtest_b2xx.py @@ -0,0 +1,76 @@ +# +# Copyright 2015-2016 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Run device tests for the B2xx series. +""" +from usrp_probe_test import uhd_usrp_probe_test +from benchmark_rate_test import uhd_benchmark_rate_test +uhd_benchmark_rate_test.tests = { + 'mimo': { + 'duration': 1, + 'direction': 'tx,rx', + 'channels': ['0,1',], + 'sample-rates': [1e6], + 'products': ['B210',], + 'acceptable-underruns': 500, + }, + 'siso_chan0_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0', + 'rate': 1e6, + 'acceptable-underruns': 50, + }, + #'siso_chan0_fast': { + #'duration': 1, + #'direction': 'tx,rx', + #'chan': '0', + #'rate': 40e6, + #'acceptable-underruns': 500, + #}, + 'siso_chan1_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '1', + 'rate': 1e6, + 'acceptable-underruns': 50, + 'products': ['B210',], + }, + #'siso_chan1_fast': { + #'duration': 1, + #'direction': 'tx,rx', + #'chan': '1', + #'rate': 40e6, + #'acceptable-underruns': 500, + #'products': ['B210',], + #}, +} + +from rx_samples_to_file_test import rx_samples_to_file_test +rx_samples_to_file_test.tests = { + 'default': { + 'duration': 1, + 'subdev': 'A:A', + 'rate': 5e6, + 'products': ['B210', 'B200',], + }, +} + +from tx_bursts_test import uhd_tx_bursts_test +from test_pps_test import uhd_test_pps_test +from gpio_test import gpio_test + diff --git a/host/tests/devtest/devtest_e3xx.py b/host/tests/devtest/devtest_e3xx.py new file mode 100755 index 000000000..1cab44184 --- /dev/null +++ b/host/tests/devtest/devtest_e3xx.py @@ -0,0 +1,58 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Run device tests for the E3XX series. +""" +from usrp_probe_test import uhd_usrp_probe_test +from benchmark_rate_test import uhd_benchmark_rate_test +uhd_benchmark_rate_test.tests = { + 'mimo': { + 'duration': 1, + 'direction': 'tx,rx', + 'channels': '0,1', + 'rate': 1e6, + 'acceptable-underruns': 500, + }, + 'siso_chan0_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0', + 'rate': 1e6, + 'acceptable-underruns': 50, + }, + 'siso_chan1_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '1', + 'rate': 1e6, + 'acceptable-underruns': 50, + 'products': ['B210',], + }, +} + +from rx_samples_to_file_test import rx_samples_to_file_test +rx_samples_to_file_test.tests = { + 'default': { + 'duration': 1, + 'subdev': 'A:A', + 'rate': 5e6, + }, +} + +from tx_bursts_test import uhd_tx_bursts_test +from test_pps_test import uhd_test_pps_test + diff --git a/host/tests/devtest/devtest_x3x0.py b/host/tests/devtest/devtest_x3x0.py new file mode 100755 index 000000000..7ad6b21b6 --- /dev/null +++ b/host/tests/devtest/devtest_x3x0.py @@ -0,0 +1,57 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Run device tests for the X3x0 series. +""" + +from benchmark_rate_test import uhd_benchmark_rate_test +uhd_benchmark_rate_test.tests = { + 'mimo_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0,1', + 'rate': 1e6, + 'acceptable-underruns': 500, + }, + 'mimo_fast': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0,1', + 'rate': 12.5e6, + 'acceptable-underruns': 500, + }, + 'siso_chan0_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0', + 'rate': 1e6, + 'acceptable-underruns': 0, + }, + 'siso_chan1_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '1', + 'rate': 1e6, + 'acceptable-underruns': 0, + }, +} + +#from rx_samples_to_file_test import rx_samples_to_file_test +from tx_bursts_test import uhd_tx_bursts_test +from test_pps_test import uhd_test_pps_test +from gpio_test import gpio_test + diff --git a/host/tests/devtest/gpio_test.py b/host/tests/devtest/gpio_test.py new file mode 100755 index 000000000..d764a8d96 --- /dev/null +++ b/host/tests/devtest/gpio_test.py @@ -0,0 +1,47 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test for test_pps_input. """ + +import re +from uhd_test_base import uhd_example_test_case + +class gpio_test(uhd_example_test_case): + """ Run gpio. """ + tests = {'default': {},} + + def setup_example(self): + """ + Set args. + """ + self.test_params = gpio_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the success message. """ + self.log.info('Running test {n}'.format(n=test_name,)) + # Run example: + args = [ + self.create_addr_args_str(), + ] + (app, run_results) = self.run_example('gpio', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + app.returncode == 0, + re.search('All tests passed!', app.stdout) is not None, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/run_testsuite.py b/host/tests/devtest/run_testsuite.py new file mode 100755 index 000000000..2826f25e9 --- /dev/null +++ b/host/tests/devtest/run_testsuite.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# +# Copyright 2015-2016 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Device test runner. +""" + +from __future__ import print_function +import os +import sys +import subprocess +import argparse +import logging +from usrp_probe import get_usrp_list + +def setup_parser(): + """ Set up argparser """ + parser = argparse.ArgumentParser(description="Test utility for UHD/USRP.") + parser.add_argument('--devtest-pattern', '-p', default='*', help='e.g. b2xx') + parser.add_argument('--device-filter', '-f', default=None, required=True, help='b200, x300, ...') + parser.add_argument('--log-dir', '-l', default='.') + parser.add_argument('--src-dir', default='.', help='Directory where the test sources are stored') + parser.add_argument('--build-dir', default=None, help='Build dir (where examples/ and utils/ are)') + parser.add_argument('--build-type', default='Release') + return parser + +def setup_env(args): + " Add build dir into lib + exe paths, depending on OS " + def setup_env_win(env, build_dir, build_type): + " Add build dir into paths (Windows)" + env['PATH'] = "{build_dir}/lib/{build_type};" + \ + "{build_dir}/examples/{build_type};" + \ + "{build_dir}/utils/{build_type};{path}".format( + build_dir=build_dir, build_type=build_type, path=env.get('PATH', '') + ) + env['LIBPATH'] = "{build_dir}/lib/{build_type};{path}".format( + build_dir=build_dir, build_type=build_type, path=env.get('LIBPATH', '') + ) + env['LIB'] = "{build_dir}/lib/{build_type};{path}".format( + build_dir=build_dir, build_type=build_type, path=env.get('LIB', '') + ) + return env + def setup_env_unix(env, build_dir): + " Add build dir into paths (Unices)" + env['PATH'] = "{build_dir}/examples:{build_dir}/utils:{path}".format( + build_dir=build_dir, path=env.get('PATH', '') + ) + env['LD_LIBRARY_PATH'] = "{build_dir}/lib:{path}".format( + build_dir=build_dir, path=env.get('LD_LIBRARY_PATH', '') + ) + return env + def setup_env_osx(env, build_dir): + " Add build dir into paths (OS X)" + env['PATH'] = "{build_dir}/examples:{build_dir}/utils:{path}".format( + build_dir=build_dir, path=env.get('PATH', '') + ) + env['DYLD_LIBRARY_PATH'] = "{build_dir}/lib:{path}".format( + build_dir=build_dir, path=env.get('DYLD_LIBRARY_PATH', '') + ) + return env + ### Go + env = os.environ + if sys.platform.startswith('linux'): + env = setup_env_unix(env, args.build_dir) + elif sys.platform.startswith('win'): + env = setup_env_win(env, args.build_dir, args.build_type) + elif sys.platform.startswith('darwin'): + env = setup_env_osx(env, args.build_dir) + else: + print("Devtest not supported on this platform ({0}).".format(sys.platform)) + exit(1) + return env + +def main(): + " Go, go, go! " + args = setup_parser().parse_args() + env = setup_env(args) + devtest_pattern = "devtest_{p}.py".format(p=args.devtest_pattern) + uhd_args_list = get_usrp_list("type=" + args.device_filter, env) + if len(uhd_args_list) == 0: + print("No devices found. Exiting.") + exit(1) + tests_passed = True + for uhd_idx, uhd_info in enumerate(uhd_args_list): + print('--- Running all tests for device {dev} ({prod}, Serial: {ser}).'.format( + dev=uhd_idx, + prod=uhd_info.get('product', 'USRP'), + ser=uhd_info.get('serial') + )) + print('--- This will take some time. Better grab a cup of tea.') + args_str = uhd_info['args'] + env['_UHD_TEST_ARGS_STR'] = args_str + logfile_name = "log{}.log".format( + args_str.replace('type=', '_').replace('serial=', '_').replace(',', '') + ) + resultsfile_name = "results{}.log".format( + args_str.replace('type=', '_').replace('serial=', '_').replace(',', '') + ) + env['_UHD_TEST_LOGFILE'] = os.path.join(args.log_dir, logfile_name) + env['_UHD_TEST_RESULTSFILE'] = os.path.join(args.log_dir, resultsfile_name) + env['_UHD_TEST_LOG_LEVEL'] = str(logging.INFO) + env['_UHD_TEST_PRINT_LEVEL'] = str(logging.WARNING) + proc = subprocess.Popen( + [ + "python", "-m", "unittest", "discover", "-v", + "-s", args.src_dir, + "-p", devtest_pattern, + ], + env=env, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + ) + print(proc.communicate()[0]) + if proc.returncode != 0: + tests_passed = False + print('--- Done testing all attached devices.') + return tests_passed + +if __name__ == "__main__": + exit(not main()) + diff --git a/host/tests/devtest/rx_samples_to_file_test.py b/host/tests/devtest/rx_samples_to_file_test.py new file mode 100755 index 000000000..bac6ac256 --- /dev/null +++ b/host/tests/devtest/rx_samples_to_file_test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test the rx_samples_to_file example. """ + +from uhd_test_base import uhd_example_test_case + +class rx_samples_to_file_test(uhd_example_test_case): + """ + Run rx_samples_to_file and check output. + """ + tests = { + 'default': { + 'duration': 1, + 'rate': 5e6, + }, + } + + def setup_example(self): + """ + Set args. + """ + self.test_params = rx_samples_to_file_test.tests + + def run_test(self, test_name, test_args): + """ + Test launcher. Runs the example. + """ + self.log.info('Running test {n}, Subdev = {subdev}, Sample Rate = {rate}'.format( + n=test_name, subdev=test_args.get('subdev'), rate=test_args.get('rate'), + )) + # Run example: + args = [ + self.create_addr_args_str(), + '--null', + '--stats', + '--duration', str(test_args['duration']), + '--rate', str(test_args.get('rate', 1e6)), + '--wirefmt', test_args.get('wirefmt', 'sc16'), + ] + if test_args.has_key('subdev'): + args.append('--subdev') + args.append(test_args['subdev']) + (app, run_results) = self.run_example('rx_samples_to_file', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + not run_results['has_D'], + not run_results['has_S'], + run_results['return_code'] == 0, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/test_messages_test.py b/host/tests/devtest/test_messages_test.py new file mode 100644 index 000000000..496765c75 --- /dev/null +++ b/host/tests/devtest/test_messages_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test the test_messages example. """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_test_messages_test(uhd_example_test_case): + """ + Run test_messages and check output. + """ + tests = {'default': {},} + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_test_messages_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the failure messages. """ + self.log.info('Running test {n}'.format(n=test_name,)) + # Run example: + args = [ + self.create_addr_args_str(), + ] + if test_args.has_key('ntests'): + args.append('--ntests') + args.append(test_args['ntests']) + (app, run_results) = self.run_example('test_messages', args) + # Evaluate pass/fail: + succ_fail_re = re.compile(r'(?P<test>.*)->\s+(?P<succ>\d+) successes,\s+(?P<fail>\d+) +failures') + for mo in succ_fail_re.finditer(app.stdout): + key = mo.group("test").strip().replace(' ', '_').lower() + successes = int(mo.group("succ")) + failures = int(mo.group("fail")) + run_results[key] = "{}/{}".format(successes, successes+failures) + run_results['passed'] = bool(failures) + + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/test_pps_test.py b/host/tests/devtest/test_pps_test.py new file mode 100755 index 000000000..1e5b36e2c --- /dev/null +++ b/host/tests/devtest/test_pps_test.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test for test_pps_input. """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_test_pps_test(uhd_example_test_case): + """ Run test_pps_input. """ + tests = {'default': {},} + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_test_pps_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the success message. """ + self.log.info('Running test {n}'.format(n=test_name,)) + # Run example: + args = [ + self.create_addr_args_str(), + ] + if test_args.has_key('source'): + args.append('--source') + args.append(test_args['source']) + (app, run_results) = self.run_example('test_pps_input', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + app.returncode == 0, + re.search('Success!', app.stdout) is not None, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/tx_bursts_test.py b/host/tests/devtest/tx_bursts_test.py new file mode 100755 index 000000000..863f35fe1 --- /dev/null +++ b/host/tests/devtest/tx_bursts_test.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Run the test for tx_burst """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_tx_bursts_test(uhd_example_test_case): + """ Run test_messages. """ + tests = { + 'default': { + 'nsamps': 10000, + 'rate': 5e6, + 'channels': '0', + }, + } + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_tx_bursts_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the failure messages. """ + self.log.info('Running test {name}, Channel = {channel}, Sample Rate = {rate}'.format( + name=test_name, channel=test_args.get('channel'), rate=test_args.get('rate'), + )) + # Run example: + args = [ + self.create_addr_args_str(), + '--nsamps', str(test_args['nsamps']), + '--channels', str(test_args['channels']), + '--rate', str(test_args.get('rate', 1e6)), + ] + if test_args.has_key('subdev'): + args.append('--subdev') + args.append(test_args['subdev']) + (app, run_results) = self.run_example('tx_bursts', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + app.returncode == 0, + not run_results['has_S'], + ]) + run_results['async_burst_ack_found'] = re.search('success', app.stdout) is not None + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/uhd_test_base.py b/host/tests/devtest/uhd_test_base.py new file mode 100755 index 000000000..07a95ae3d --- /dev/null +++ b/host/tests/devtest/uhd_test_base.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python + +import os +import sys +import yaml +import unittest +import re +import time +import logging +from subprocess import Popen, PIPE, STDOUT +from usrp_probe import get_usrp_list + +#-------------------------------------------------------------------------- +# Application +#-------------------------------------------------------------------------- +class shell_application(object): + """ + Wrapper for applications that are in $PATH. + Note: The CMake infrastructure makes sure all examples and utils are in $PATH. + """ + def __init__(self, name): + self.name = name + self.stdout = '' + self.stderr = '' + self.returncode = None + self.exec_time = None + + def run(self, args = []): + cmd_line = [self.name] + cmd_line.extend(args) + start_time = time.time() + p = Popen(cmd_line, stdout=PIPE, stderr=PIPE, close_fds=True) + self.stdout, self.stderr = p.communicate() + self.returncode = p.returncode + self.exec_time = time.time() - start_time + +#-------------------------------------------------------------------------- +# Test case base +#-------------------------------------------------------------------------- +class uhd_test_case(unittest.TestCase): + """ + Base class for UHD test cases. + """ + test_name = '--TEST--' + + def set_up(self): + """ + Override this to add own setup code per test. + """ + pass + + def setUp(self): + self.name = self.__class__.__name__ + self.test_id = self.id().split('.')[-1] + self.results = {} + self.results_file = os.getenv('_UHD_TEST_RESULTSFILE', "") + if self.results_file and os.path.isfile(self.results_file): + self.results = yaml.safe_load(open(self.results_file).read()) or {} + self.args_str = os.getenv('_UHD_TEST_ARGS_STR', "") + self.usrp_info = get_usrp_list(self.args_str)[0] + if not self.results.has_key(self.usrp_info['serial']): + self.results[self.usrp_info['serial']] = {} + if not self.results[self.usrp_info['serial']].has_key(self.name): + self.results[self.usrp_info['serial']][self.name] = {} + self.setup_logger() + self.set_up() + + def setup_logger(self): + " Add logging infrastructure " + self.log = logging.getLogger("devtest.{name}".format(name=self.name)) + self.log_file = os.getenv('_UHD_TEST_LOGFILE', "devtest.log") + #self.log_level = int(os.getenv('_UHD_TEST_LOG_LEVEL', logging.DEBUG)) + #self.print_level = int(os.getenv('_UHD_TEST_PRINT_LEVEL', logging.WARNING)) + self.log_level = logging.DEBUG + self.print_level = logging.WARNING + file_handler = logging.FileHandler(self.log_file) + file_handler.setLevel(self.log_level) + console_handler = logging.StreamHandler() + console_handler.setLevel(self.print_level) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + self.log.setLevel(logging.DEBUG) + self.log.addHandler(file_handler) + self.log.addHandler(console_handler) + self.log.info("Starting test with device: {dev}".format(dev=self.args_str)) + + def tear_down(self): + pass + + def tearDown(self): + self.tear_down() + if self.results_file: + open(self.results_file, 'w').write(yaml.dump(self.results, default_flow_style=False)) + + def report_result(self, testname, key, value): + """ Store a result as a key/value pair. + After completion, all results for one test are written to the results file. + """ + if not self.results[self.usrp_info['serial']][self.name].has_key(testname): + self.results[self.usrp_info['serial']][self.name][testname] = {} + self.results[self.usrp_info['serial']][self.name][testname][key] = value + + def create_addr_args_str(self, argname="args"): + """ Returns an args string, usually '--args "type=XXX,serial=YYY" """ + if len(self.args_str) == 0: + return '' + return '--{}={}'.format(argname, self.args_str) + + def filter_warnings(self, errstr): + """ Searches errstr for UHD warnings, removes them, and puts them into a separate string. + Returns (errstr, warnstr), where errstr no longer has warning. """ + warn_re = re.compile("UHD Warning:\n(?: .*\n)+") + warnstr = "\n".join(warn_re.findall(errstr)).strip() + errstr = warn_re.sub('', errstr).strip() + return (errstr, warnstr) + + def filter_stderr(self, stderr, run_results={}): + """ Filters the output to stderr. run_results[] is a dictionary. + This function will: + - Remove UUUUU... strings, since they are generally not a problem. + - Remove all DDDD and SSSS strings, and add run_results['has_S'] = True + and run_results['has_D'] = True. + - Remove warnings and put them in run_results['warnings'] + - Put the filtered error string into run_results['errors'] and returns the dictionary + """ + errstr, run_results['warnings'] = self.filter_warnings(stderr) + # Scan for underruns and sequence errors / dropped packets not detected in the counter + errstr = re.sub('UU+', '', errstr) + (errstr, n_subs) = re.subn('SS+', '', errstr) + if n_subs: + run_results['has_S'] = True + (errstr, n_subs) = re.subn('DD+', '', errstr) + if n_subs: + run_results['has_D'] = True + errstr = re.sub("\n\n+", "\n", errstr) + run_results['errors'] = errstr.strip() + return run_results + +class uhd_example_test_case(uhd_test_case): + """ + A test case that runs an example. + """ + + def setup_example(self): + """ + Override this to add specific setup code. + """ + pass + + def set_up(self): + """ + """ + self.setup_example() + + def run_test(self, test_name, test_args): + """ + Override this to run the actual example. + + Needs to return either a boolean or a dict with key 'passed' to determine + pass/fail. + """ + raise NotImplementedError + + def run_example(self, example, args): + """ + Run `example' (which has to be a UHD example or utility) with `args'. + Return results and the app object. + """ + self.log.info("Running example: `{example} {args}'".format(example=example, args=" ".join(args))) + app = shell_application(example) + app.run(args) + run_results = { + 'return_code': app.returncode, + 'passed': False, + 'has_D': False, + 'has_S': False, + } + run_results = self.filter_stderr(app.stderr, run_results) + self.log.info('STDERR Output:') + self.log.info(str(app.stderr)) + return (app, run_results) + + + def report_example_results(self, test_name, run_results): + for key in sorted(run_results): + self.log.info('{key} = {val}'.format(key=key, val=run_results[key])) + self.report_result( + test_name, + key, run_results[key] + ) + if run_results.has_key('passed'): + self.report_result( + test_name, + 'status', + 'Passed' if run_results['passed'] else 'Failed', + ) + if run_results.has_key('errors'): + self.report_result( + test_name, + 'errors', + 'Yes' if run_results['errors'] else 'No', + ) + + def test_all(self): + """ + Hook for test runner. Needs to be a class method that starts with 'test'. + Calls run_test(). + """ + for test_name, test_args in self.test_params.iteritems(): + if not test_args.has_key('products') or (self.usrp_info['product'] in test_args.get('products', [])): + run_results = self.run_test(test_name, test_args) + passed = bool(run_results) + if isinstance(run_results, dict): + passed = run_results['passed'] + self.assertTrue( + passed, + msg="Errors occurred during test `{t}'. Check log file for details.\nRun results:\n{r}".format( + t=test_name, r=yaml.dump(run_results, default_flow_style=False) + ) + ) + diff --git a/host/tests/devtest/usrp_probe.py b/host/tests/devtest/usrp_probe.py new file mode 100644 index 000000000..50d484518 --- /dev/null +++ b/host/tests/devtest/usrp_probe.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# +# Copyright 2015-2016 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Run uhd_find_devices and parse the output. """ + +import re +import subprocess + +def get_usrp_list(device_filter=None, env=None): + """ Returns a list of dicts that contain USRP info """ + try: + cmd = ['uhd_find_devices'] + if device_filter is not None: + cmd += ['--args', device_filter] + output = subprocess.check_output(cmd, env=env) + except subprocess.CalledProcessError: + return [] + split_re = "\n*-+\n-- .*\n-+\n" + uhd_strings = re.split(split_re, output) + result = [] + for uhd_string in uhd_strings: + if not re.match("Device Address", uhd_string): + continue + this_result = {k: v for k, v in re.findall(" ([a-z]+): (.*)", uhd_string)} + args_string = "" + try: + args_string = "type={},serial={}".format(this_result['type'], this_result['serial']) + except KeyError: + continue + this_result['args'] = args_string + result.append(this_result) + return result + +if __name__ == "__main__": + print get_usrp_list() + print get_usrp_list('type=x300') diff --git a/host/tests/devtest/usrp_probe_test.py b/host/tests/devtest/usrp_probe_test.py new file mode 100755 index 000000000..a136a2af7 --- /dev/null +++ b/host/tests/devtest/usrp_probe_test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Run the test for tx_burst """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_usrp_probe_test(uhd_example_test_case): + """ Run uhd_usrp_probe """ + tests = { + 'default': { + 'init-only': False, + }, + } + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_usrp_probe_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the failure messages. """ + self.log.info('Running test {name}'.format(name=test_name)) + # Run example: + args = [ + self.create_addr_args_str(), + ] + if test_args.get('init-only'): + args.append('--init-only') + (app, run_results) = self.run_example('uhd_usrp_probe', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + app.returncode == 0, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/expert_test.cpp b/host/tests/expert_test.cpp new file mode 100644 index 000000000..016761194 --- /dev/null +++ b/host/tests/expert_test.cpp @@ -0,0 +1,256 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include "../lib/experts/expert_container.hpp" +#include "../lib/experts/expert_factory.hpp" +#include <uhd/property_tree.hpp> +#include <fstream> + +using namespace uhd::experts; + +class worker1_t : public worker_node_t { +public: + worker1_t(const node_retriever_t& db) + : worker_node_t("A+B=C"), _a(db, "A/desired"), _b(db, "B"), _c(db, "C") + { + bind_accessor(_a); + bind_accessor(_b); + bind_accessor(_c); + } + +private: + void resolve() { + _c = _a + _b; + } + + data_reader_t<int> _a; + data_reader_t<int> _b; + data_writer_t<int> _c; +}; + +//============================================================================= + +class worker2_t : public worker_node_t { +public: + worker2_t(const node_retriever_t& db) + : worker_node_t("C*D=E"), _c(db, "C"), _d(db, "D"), _e(db, "E") + { + bind_accessor(_c); + bind_accessor(_d); + bind_accessor(_e); + } + +private: + void resolve() { + _e.set(_c.get() * _d.get()); + } + + data_reader_t<int> _c; + data_reader_t<int> _d; + data_writer_t<int> _e; +}; + +//============================================================================= + +class worker3_t : public worker_node_t { +public: + worker3_t(const node_retriever_t& db) + : worker_node_t("-B=F"), _b(db, "B"), _f(db, "F") + { + bind_accessor(_b); + bind_accessor(_f); + } + +private: + void resolve() { + _f.set(-_b.get()); + } + + data_reader_t<int> _b; + data_writer_t<int> _f; +}; + +//============================================================================= + +class worker4_t : public worker_node_t { +public: + worker4_t(const node_retriever_t& db) + : worker_node_t("E-F=G"), _e(db, "E"), _f(db, "F"), _g(db, "G") + { + bind_accessor(_e); + bind_accessor(_f); + bind_accessor(_g); + } + +private: + void resolve() { + _g.set(_e.get() - _f.get()); + } + + data_reader_t<int> _e; + data_reader_t<int> _f; + data_writer_t<int> _g; +}; + +//============================================================================= + +class worker5_t : public worker_node_t { +public: + worker5_t(const node_retriever_t& db, boost::shared_ptr<int> output) + : worker_node_t("Consume_G"), _g(db, "G"), _c(db, "C"), _output(output) + { + bind_accessor(_g); +// bind_accessor(_c); + } + +private: + void resolve() { + *_output = _g; + } + + data_reader_t<int> _g; + data_writer_t<int> _c; + + boost::shared_ptr<int> _output; +}; + +class worker6_t : public worker_node_t { +public: + worker6_t() : worker_node_t("null_worker") + { + } + +private: + void resolve() { + } +}; + +//============================================================================= + +#define DUMP_VARS \ + BOOST_TEST_MESSAGE( str(boost::format("### State = {A=%d%s, B=%d%s, C=%d%s, D=%d%s, E=%d%s, F=%d%s, G=%d%s}\n") % \ + nodeA.get() % (nodeA.is_dirty()?"*":"") % \ + nodeB.get() % (nodeB.is_dirty()?"*":"") % \ + nodeC.get() % (nodeC.is_dirty()?"*":"") % \ + nodeD.get() % (nodeD.is_dirty()?"*":"") % \ + nodeE.get() % (nodeE.is_dirty()?"*":"") % \ + nodeF.get() % (nodeF.is_dirty()?"*":"") % \ + nodeG.get() % (nodeG.is_dirty()?"*":"")) ); + +#define VALIDATE_ALL_DEPENDENCIES \ + BOOST_CHECK(!nodeA.is_dirty()); \ + BOOST_CHECK(!nodeB.is_dirty()); \ + BOOST_CHECK(!nodeC.is_dirty()); \ + BOOST_CHECK(!nodeD.is_dirty()); \ + BOOST_CHECK(!nodeE.is_dirty()); \ + BOOST_CHECK(!nodeF.is_dirty()); \ + BOOST_CHECK(!nodeG.is_dirty()); \ + BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); \ + BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get()); \ + BOOST_CHECK(nodeF.get() == - nodeB.get()); \ + BOOST_CHECK(nodeG.get() == nodeE.get() - nodeF.get()); \ + BOOST_CHECK(nodeG.get() == *final_output); + + +BOOST_AUTO_TEST_CASE(test_experts){ + //Initialize container object + expert_container::sptr container = expert_factory::create_container("example"); + uhd::property_tree::sptr tree = uhd::property_tree::make(); + + //Output of expert tree + boost::shared_ptr<int> final_output = boost::make_shared<int>(); + + //Add data nodes to container + expert_factory::add_dual_prop_node<int>(container, tree, "A", 0, uhd::experts::AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<int>(container, tree, "B", 0); + expert_factory::add_data_node<int>(container, "C", 0); + expert_factory::add_data_node<int>(container, "D", 1); + expert_factory::add_prop_node<int>(container, tree, "E", 0, uhd::experts::AUTO_RESOLVE_ON_READ); + expert_factory::add_data_node<int>(container, "F", 0); + expert_factory::add_data_node<int>(container, "G", 0); + + //Add worker nodes to container + expert_factory::add_worker_node<worker1_t>(container, container->node_retriever()); + expert_factory::add_worker_node<worker2_t>(container, container->node_retriever()); + expert_factory::add_worker_node<worker3_t>(container, container->node_retriever()); + expert_factory::add_worker_node<worker4_t>(container, container->node_retriever()); + expert_factory::add_worker_node<worker5_t>(container, container->node_retriever(), final_output); + expert_factory::add_worker_node<worker6_t>(container); + + //Once initialized, getting modify access to graph nodes is possible (by design) but extremely red-flaggy! + //But we do it here to monitor things + data_node_t<int>& nodeA = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("A/desired")))); + data_node_t<int>& nodeB = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("B")))); + data_node_t<int>& nodeC = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("C")))); + data_node_t<int>& nodeD = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("D")))); + data_node_t<int>& nodeE = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("E")))); + data_node_t<int>& nodeF = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("F")))); + data_node_t<int>& nodeG = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("G")))); + + DUMP_VARS + + //Ensure init behavior + BOOST_CHECK(nodeA.is_dirty()); + BOOST_CHECK(nodeB.is_dirty()); + BOOST_CHECK(nodeC.is_dirty()); + BOOST_CHECK(nodeD.is_dirty()); + BOOST_CHECK(nodeE.is_dirty()); + BOOST_CHECK(nodeF.is_dirty()); + BOOST_CHECK(nodeG.is_dirty()); + container->resolve_all(); + VALIDATE_ALL_DEPENDENCIES //Ensure a default resolve + + //Ensure basic node value propagation + tree->access<int>("B").set(3); + BOOST_CHECK(nodeB.get() == 3); //Ensure value propagated + BOOST_CHECK(nodeB.is_dirty()); //Ensure that nothing got resolved... + container->resolve_all(); + VALIDATE_ALL_DEPENDENCIES + + nodeD.set(2); //Hack for testing + + //Ensure auto-resolve on write + tree->access<int>("A").set(200); + BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); + BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get()); + BOOST_CHECK(nodeG.get() == nodeE.get() - nodeF.get()); + container->resolve_all(); + VALIDATE_ALL_DEPENDENCIES + + container->resolve_all(); + VALIDATE_ALL_DEPENDENCIES + + //Ensure auto-resolve on read + tree->access<int>("E").get(); + BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); + BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get()); + BOOST_CHECK(!nodeE.is_dirty()); + tree->access<int>("E").set(-10); + container->resolve_all(true); + VALIDATE_ALL_DEPENDENCIES + + //Resolve to/from + tree->access<int>("A").set(-1); + container->resolve_to("C"); + BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); + BOOST_CHECK(!nodeC.is_dirty()); + container->resolve_to("Consume_G"); + VALIDATE_ALL_DEPENDENCIES +} diff --git a/host/tests/fe_conn_test.cpp b/host/tests/fe_conn_test.cpp new file mode 100644 index 000000000..b8e69816a --- /dev/null +++ b/host/tests/fe_conn_test.cpp @@ -0,0 +1,108 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/fe_connection.hpp> +#include <uhd/exception.hpp> +#include <boost/test/unit_test.hpp> + +using namespace uhd::usrp; + +BOOST_AUTO_TEST_CASE(test_quardrature){ + fe_connection_t IQ("IQ"), QI("QI"), IbQ("IbQ"), QbI("QbI"), QbIb("QbIb"); + BOOST_CHECK(IQ.get_sampling_mode()==fe_connection_t::QUADRATURE); + BOOST_CHECK(QI.get_sampling_mode()==fe_connection_t::QUADRATURE); + BOOST_CHECK(IbQ.get_sampling_mode()==fe_connection_t::QUADRATURE); + BOOST_CHECK(QbI.get_sampling_mode()==fe_connection_t::QUADRATURE); + BOOST_CHECK(QbIb.get_sampling_mode()==fe_connection_t::QUADRATURE); + + BOOST_CHECK(not IQ.is_iq_swapped()); + BOOST_CHECK(QI.is_iq_swapped()); + BOOST_CHECK(not IbQ.is_iq_swapped()); + BOOST_CHECK(QbI.is_iq_swapped()); + BOOST_CHECK(QbIb.is_iq_swapped()); + + BOOST_CHECK(not IQ.is_i_inverted()); + BOOST_CHECK(not QI.is_i_inverted()); + BOOST_CHECK(IbQ.is_i_inverted()); + BOOST_CHECK(not QbI.is_i_inverted()); + BOOST_CHECK(QbIb.is_i_inverted()); + + BOOST_CHECK(not IQ.is_q_inverted()); + BOOST_CHECK(not QI.is_q_inverted()); + BOOST_CHECK(not IbQ.is_q_inverted()); + BOOST_CHECK(QbI.is_q_inverted()); + BOOST_CHECK(QbIb.is_q_inverted()); +} + +BOOST_AUTO_TEST_CASE(test_heterodyne){ + fe_connection_t II("II"), QQ("QQ"), IbIb("IbIb"), QbQb("QbQb"); + BOOST_CHECK(II.get_sampling_mode()==fe_connection_t::HETERODYNE); + BOOST_CHECK(QQ.get_sampling_mode()==fe_connection_t::HETERODYNE); + BOOST_CHECK(IbIb.get_sampling_mode()==fe_connection_t::HETERODYNE); + BOOST_CHECK(QbQb.get_sampling_mode()==fe_connection_t::HETERODYNE); + + BOOST_CHECK(not II.is_iq_swapped()); + BOOST_CHECK(QQ.is_iq_swapped()); + BOOST_CHECK(not IbIb.is_iq_swapped()); + BOOST_CHECK(QbQb.is_iq_swapped()); + + BOOST_CHECK(not II.is_i_inverted()); + BOOST_CHECK(not QQ.is_i_inverted()); + BOOST_CHECK(IbIb.is_i_inverted()); + BOOST_CHECK(QbQb.is_i_inverted()); + + BOOST_CHECK(not II.is_q_inverted()); + BOOST_CHECK(not QQ.is_q_inverted()); + BOOST_CHECK(IbIb.is_q_inverted()); + BOOST_CHECK(QbQb.is_q_inverted()); + + BOOST_CHECK_THROW(fe_connection_t dummy("IIb"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("IbI"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("QQb"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("QbQ"), uhd::value_error); +} + +BOOST_AUTO_TEST_CASE(test_real){ + fe_connection_t I("I"), Q("Q"), Ib("Ib"), Qb("Qb"); + BOOST_CHECK(I.get_sampling_mode()==fe_connection_t::REAL); + BOOST_CHECK(Q.get_sampling_mode()==fe_connection_t::REAL); + BOOST_CHECK(Ib.get_sampling_mode()==fe_connection_t::REAL); + BOOST_CHECK(Qb.get_sampling_mode()==fe_connection_t::REAL); + + BOOST_CHECK(not I.is_iq_swapped()); + BOOST_CHECK(Q.is_iq_swapped()); + BOOST_CHECK(not Ib.is_iq_swapped()); + BOOST_CHECK(Qb.is_iq_swapped()); + + BOOST_CHECK(not I.is_i_inverted()); + BOOST_CHECK(not Q.is_i_inverted()); + BOOST_CHECK(Ib.is_i_inverted()); + BOOST_CHECK(Qb.is_i_inverted()); + + BOOST_CHECK(not I.is_q_inverted()); + BOOST_CHECK(not Q.is_q_inverted()); + BOOST_CHECK(not Ib.is_q_inverted()); + BOOST_CHECK(not Qb.is_q_inverted()); +} + +BOOST_AUTO_TEST_CASE(test_invalid){ + BOOST_CHECK_THROW(fe_connection_t dummy("blah"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("123456"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("ii"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("qb"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("IIIQ"), uhd::value_error); +} diff --git a/host/tests/graph.hpp b/host/tests/graph.hpp new file mode 100644 index 000000000..0c2be0347 --- /dev/null +++ b/host/tests/graph.hpp @@ -0,0 +1,51 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_TEST_GRAPH_HPP +#define INCLUDED_TEST_GRAPH_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> + +#define MAKE_NODE(name) test_node::sptr name(new test_node(#name)); + +// Smallest possible test class +class test_node : virtual public uhd::rfnoc::sink_node_ctrl, virtual public uhd::rfnoc::source_node_ctrl +{ +public: + typedef boost::shared_ptr<test_node> sptr; + + test_node(const std::string &test_id) : _test_id(test_id) {}; + + void issue_stream_cmd(const uhd::stream_cmd_t &, const size_t) {/* nop */}; + + std::string get_test_id() const { return _test_id; }; + +private: + const std::string _test_id; + +}; /* class test_node */ + +void connect_nodes(uhd::rfnoc::source_node_ctrl::sptr A, uhd::rfnoc::sink_node_ctrl::sptr B) +{ + A->connect_downstream(B); + B->connect_upstream(A); +} + +#endif /* INCLUDED_TEST_GRAPH_HPP */ +// vim: sw=4 et: diff --git a/host/tests/graph_search_test.cpp b/host/tests/graph_search_test.cpp new file mode 100644 index 000000000..4106d724e --- /dev/null +++ b/host/tests/graph_search_test.cpp @@ -0,0 +1,169 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "graph.hpp" +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +// test class derived, this is what we search for +class result_node : public test_node +{ +public: + typedef boost::shared_ptr<result_node> sptr; + + result_node(const std::string &test_id) : test_node(test_id) {}; + +}; /* class result_node */ + +#define MAKE_RESULT_NODE(name) result_node::sptr name(new result_node(#name)); + +BOOST_AUTO_TEST_CASE(test_simplest_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B); + + // Simplest possible scenario: Connect B downstream of A and let + // A find B + connect_nodes(node_A, node_B); + + test_node::sptr result = node_A->find_downstream_node<test_node>()[0]; + BOOST_REQUIRE(result); + BOOST_CHECK_EQUAL(result->get_test_id(), "node_B"); +} + +BOOST_AUTO_TEST_CASE(test_simple_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_NODE(node_B1); + + // Simple scenario: Connect both B{1,2} downstream of A and let + // it find them + connect_nodes(node_A, node_B0); + connect_nodes(node_A, node_B1); + + // We're still searching for test_node, so any downstream block will match + std::vector< test_node::sptr > result = node_A->find_downstream_node<test_node>(); + BOOST_REQUIRE(result.size() == 2); + BOOST_CHECK( + (result[0]->get_test_id() == "node_B0" and result[1]->get_test_id() == "node_B1") or + (result[1]->get_test_id() == "node_B0" and result[0]->get_test_id() == "node_B1") + ); + BOOST_CHECK(result[0] == node_B0 or result[0] == node_B1); +} + +BOOST_AUTO_TEST_CASE(test_linear_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_RESULT_NODE(node_B); + MAKE_RESULT_NODE(node_C); + + // Slightly more complex graph: + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + // This time, we search for result_node + std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>(); + std::cout << "size: " << result.size() << std::endl; + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK_EQUAL(result[0]->get_test_id(), "node_B"); + BOOST_FOREACH(const result_node::sptr &node, result) { + std::cout << node->get_test_id() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_multi_iter_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_NODE(node_B1); + MAKE_NODE(node_C0); + MAKE_RESULT_NODE(node_C1); + MAKE_RESULT_NODE(node_C2); + MAKE_RESULT_NODE(node_C3); + MAKE_RESULT_NODE(node_D0); + + // Slightly more complex graph: + connect_nodes(node_A, node_B0); + connect_nodes(node_A, node_B1); + connect_nodes(node_B0, node_C0); + connect_nodes(node_B0, node_C1); + connect_nodes(node_B1, node_C2); + connect_nodes(node_B1, node_C3); + connect_nodes(node_C0, node_D0); + + // This time, we search for result_node + std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>(); + BOOST_REQUIRE(result.size() == 4); + BOOST_FOREACH(const result_node::sptr &node, result) { + std::cout << node->get_test_id() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_multi_iter_cycle_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_NODE(node_B1); + MAKE_NODE(node_C0); + MAKE_RESULT_NODE(node_C1); + MAKE_RESULT_NODE(node_C2); + MAKE_RESULT_NODE(node_C3); + MAKE_RESULT_NODE(node_D0); + + // Slightly more complex graph: + connect_nodes(node_A, node_B0); + // This connection goes both ways, causing a cycle + connect_nodes(node_A, node_B1); connect_nodes(node_B1, node_A); + connect_nodes(node_B0, node_C0); + connect_nodes(node_B0, node_C1); + connect_nodes(node_B1, node_C2); + connect_nodes(node_B1, node_C3); + connect_nodes(node_C0, node_D0); + + // This time, we search for result_node + std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>(); + BOOST_REQUIRE(result.size() == 4); + BOOST_FOREACH(const result_node::sptr &node, result) { + std::cout << node->get_test_id() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_mini_cycle_downstream_and_upstream) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B); + + // Connect them in a loop + connect_nodes(node_A, node_B); connect_nodes(node_B, node_A); + + std::vector< test_node::sptr > result; + result = node_A->find_downstream_node<test_node>(); + BOOST_REQUIRE_EQUAL(result.size(), 1); + BOOST_REQUIRE(result[0] == node_B); + result = node_B->find_downstream_node<test_node>(); + BOOST_REQUIRE_EQUAL(result.size(), 1); + BOOST_REQUIRE(result[0] == node_A); + result = node_A->find_upstream_node<test_node>(); + BOOST_REQUIRE_EQUAL(result.size(), 1); + BOOST_REQUIRE(result[0] == node_B); + result = node_B->find_upstream_node<test_node>(); + BOOST_REQUIRE_EQUAL(result.size(), 1); + BOOST_REQUIRE(result[0] == node_A); +} diff --git a/host/tests/nocscript_common.hpp b/host/tests/nocscript_common.hpp new file mode 100644 index 000000000..7467e05fb --- /dev/null +++ b/host/tests/nocscript_common.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "../lib/rfnoc/nocscript/expression.hpp" +#include <boost/assign/list_of.hpp> + +using namespace uhd::rfnoc::nocscript; + +// Some global defs to make tests easier to write +expression_function::argtype_list_type one_int_arg = boost::assign::list_of(expression::TYPE_INT); +expression_function::argtype_list_type two_int_args = boost::assign::list_of(expression::TYPE_INT)(expression::TYPE_INT); +expression_function::argtype_list_type one_double_arg = boost::assign::list_of(expression::TYPE_DOUBLE); +expression_function::argtype_list_type two_double_args = boost::assign::list_of(expression::TYPE_DOUBLE)(expression::TYPE_DOUBLE); +expression_function::argtype_list_type one_bool_arg = boost::assign::list_of(expression::TYPE_BOOL); +expression_function::argtype_list_type two_bool_args = boost::assign::list_of(expression::TYPE_BOOL)(expression::TYPE_BOOL); +expression_function::argtype_list_type no_args; + +expression_container::expr_list_type empty_arg_list; + +#define E(x) expression_literal::make(x) + diff --git a/host/tests/nocscript_expr_test.cpp b/host/tests/nocscript_expr_test.cpp new file mode 100644 index 000000000..f82a613a3 --- /dev/null +++ b/host/tests/nocscript_expr_test.cpp @@ -0,0 +1,451 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "../lib/rfnoc/nocscript/function_table.hpp" +#include <boost/test/unit_test.hpp> +#include <boost/test/floating_point_comparison.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/format.hpp> +#include <algorithm> +#include <iostream> + +#include "nocscript_common.hpp" + +// We need this global variable for one of the later tests +int and_counter = 0; + +BOOST_AUTO_TEST_CASE(test_literals) +{ + expression_literal literal_int("5", expression::TYPE_INT); + BOOST_CHECK_EQUAL(literal_int.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(literal_int.get_int(), 5); + BOOST_CHECK_EQUAL(literal_int.to_bool(), true); + BOOST_REQUIRE_THROW(literal_int.get_string(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_int.get_bool(), uhd::type_error); + + expression_literal literal_int0("0", expression::TYPE_INT); + BOOST_CHECK_EQUAL(literal_int0.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(literal_int0.to_bool(), false); + + expression_literal literal_double("2.3", expression::TYPE_DOUBLE); + BOOST_CHECK_EQUAL(literal_double.infer_type(), expression::TYPE_DOUBLE); + BOOST_CHECK_CLOSE(literal_double.get_double(), 2.3, 0.01); + BOOST_CHECK_EQUAL(literal_double.to_bool(), true); + BOOST_REQUIRE_THROW(literal_double.get_string(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_double.get_bool(), uhd::type_error); + + expression_literal literal_bool(true); + BOOST_CHECK_EQUAL(literal_bool.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(literal_bool.get_bool(), true); + BOOST_CHECK_EQUAL(literal_bool.to_bool(), true); + BOOST_CHECK_EQUAL(literal_bool.eval().get_bool(), true); + BOOST_REQUIRE_THROW(literal_bool.get_string(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_bool.get_int(), uhd::type_error); + + expression_literal literal_bool_false(false); + BOOST_CHECK_EQUAL(literal_bool_false.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(literal_bool_false.get_bool(), false); + BOOST_CHECK_EQUAL(literal_bool_false.to_bool(), false); + BOOST_REQUIRE_EQUAL(literal_bool_false.eval().get_bool(), false); + BOOST_REQUIRE_THROW(literal_bool_false.get_string(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_bool_false.get_int(), uhd::type_error); + + expression_literal literal_string("'foo bar'", expression::TYPE_STRING); + BOOST_CHECK_EQUAL(literal_string.infer_type(), expression::TYPE_STRING); + BOOST_CHECK_EQUAL(literal_string.get_string(), "foo bar"); + BOOST_REQUIRE_THROW(literal_string.get_bool(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_string.get_int(), uhd::type_error); + + expression_literal literal_int_vec("[1, 2, 3]", expression::TYPE_INT_VECTOR); + BOOST_CHECK_EQUAL(literal_int_vec.infer_type(), expression::TYPE_INT_VECTOR); + std::vector<int> test_data = boost::assign::list_of(1)(2)(3); + std::vector<int> result = literal_int_vec.get_int_vector(); + BOOST_CHECK_EQUAL_COLLECTIONS(test_data.begin(), test_data.end(), + result.begin(), result.end()); + BOOST_REQUIRE_THROW(literal_int_vec.get_bool(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_int_vec.get_int(), uhd::type_error); +} + + +// Need those for the variable testing: +expression::type_t variable_get_type(const std::string &var_name) +{ + if (var_name == "spp") { + std::cout << "Returning type for $spp..." << std::endl; + return expression::TYPE_INT; + } + if (var_name == "is_true") { + std::cout << "Returning type for $is_true..." << std::endl; + return expression::TYPE_BOOL; + } + + throw uhd::syntax_error("Cannot infer type (unknown variable)"); +} + +expression_literal variable_get_value(const std::string &var_name) +{ + if (var_name == "spp") { + std::cout << "Returning value for $spp..." << std::endl; + return expression_literal(5); + } + if (var_name == "is_true") { + std::cout << "Returning value for $is_true..." << std::endl; + return expression_literal(true); + } + + throw uhd::syntax_error("Cannot read value (unknown variable)"); +} + +BOOST_AUTO_TEST_CASE(test_variables) +{ + BOOST_REQUIRE_THROW( + expression_variable v_fail( + "foo", // Invalid token + boost::bind(&variable_get_type, _1), boost::bind(&variable_get_value, _1) + ), + uhd::assertion_error + ); + + expression_variable v( + "$spp", // The token + boost::bind(&variable_get_type, _1), // type-getter + boost::bind(&variable_get_value, _1) // value-getter + ); + BOOST_CHECK_EQUAL(v.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(v.eval().get_int(), 5); +} + +BOOST_AUTO_TEST_CASE(test_container) +{ + // Create some sub-expressions: + expression_literal::sptr l_true = E(true); + expression_literal::sptr l_false = E(false); + expression_literal::sptr l_int = E(5); + BOOST_REQUIRE_EQUAL(l_false->get_bool(), false); + BOOST_REQUIRE_EQUAL(l_false->to_bool(), false); + expression_variable::sptr l_boolvar = boost::make_shared<expression_variable>( + "$is_true", + boost::bind(&variable_get_type, _1), + boost::bind(&variable_get_value, _1) + ); + + // This will throw anytime it's evaluated: + expression_variable::sptr l_failvar = boost::make_shared<expression_variable>( + "$does_not_exist", + boost::bind(&variable_get_type, _1), + boost::bind(&variable_get_value, _1) + ); + + expression_container c; + std::cout << "One true, OR: " << std::endl; + c.add(l_true); + c.set_combiner_safe(expression_container::COMBINE_OR); + expression_literal ret_val_1 = c.eval(); + BOOST_CHECK_EQUAL(ret_val_1.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(ret_val_1.eval().get_bool(), true); + + std::cout << std::endl << std::endl << "Two true, one false, OR: " << std::endl; + c.add(l_true); + c.add(l_false); + expression_literal ret_val_2 = c.eval(); + BOOST_CHECK_EQUAL(ret_val_2.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(ret_val_2.eval().get_bool(), true); + + expression_container c2; + c2.add(l_false); + c2.add(l_false); + c2.set_combiner(expression_container::COMBINE_AND); + std::cout << std::endl << std::endl << "Two false, AND: " << std::endl; + expression_literal ret_val_3 = c2.eval(); + BOOST_CHECK_EQUAL(ret_val_3.infer_type(), expression::TYPE_BOOL); + BOOST_REQUIRE_EQUAL(ret_val_3.eval().get_bool(), false); + + c2.add(l_failvar); + // Will not fail, because l_failvar never gets eval'd: + expression_literal ret_val_4 = c2.eval(); + BOOST_CHECK_EQUAL(ret_val_4.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(ret_val_4.eval().get_bool(), false); + + // Same here: + c.add(l_failvar); + expression_literal ret_val_5 = c.eval(); + BOOST_CHECK_EQUAL(ret_val_5.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(ret_val_5.eval().get_bool(), true); + + // Now it'll throw: + c.set_combiner(expression_container::COMBINE_ALL); + BOOST_REQUIRE_THROW(c.eval(), uhd::syntax_error); + + std::cout << "Checking type inference on ',' sequences: " << std::endl; + // Check types match + BOOST_CHECK_EQUAL(c2.infer_type(), expression::TYPE_BOOL); + expression_container c3; + c3.set_combiner(expression_container::COMBINE_ALL); + c3.add(l_false); + c3.add(l_int); + BOOST_CHECK_EQUAL(c3.infer_type(), expression::TYPE_INT); +} + + +// We'll define two functions here: ADD and XOR. The former shall +// be defined for INT and DOUBLE +class functable_mockup_impl : public function_table +{ + public: + functable_mockup_impl(void) {}; + + bool function_exists(const std::string &name) const { + return name == "ADD" or name == "XOR" or name == "AND"; + } + + bool function_exists( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + if (name == "ADD") { + if (arg_types.size() == 2 + and arg_types[0] == expression::TYPE_DOUBLE + and arg_types[1] == expression::TYPE_DOUBLE + ) { + return true; + } + if (arg_types.size() == 2 + and arg_types[0] == expression::TYPE_INT + and arg_types[1] == expression::TYPE_INT + ) { + return true; + } + return false; + } + + if (name == "XOR" or name == "AND") { + if (arg_types.size() == 2 + and arg_types[0] == expression::TYPE_BOOL + and arg_types[1] == expression::TYPE_BOOL + ) { + return true; + } + return false; + } + + return false; + } + + expression::type_t get_type( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + if (not function_exists(name, arg_types)) { + throw uhd::syntax_error(str( + boost::format("[EXPR_TEXT] get_type(): Unknown function: %s, %d arguments") + % name % arg_types.size() + )); + } + + if (name == "XOR" or name == "AND") { + return expression::TYPE_BOOL; + } + if (name == "ADD") { + return arg_types[0]; + } + UHD_THROW_INVALID_CODE_PATH(); + } + + expression_literal eval( + const std::string &name, + const expression_function::argtype_list_type &arg_types, + expression_container::expr_list_type &args + ) { + if (name == "XOR") { + if (arg_types.size() != 2 + or args.size() != 2 + or arg_types[0] != expression::TYPE_BOOL + or arg_types[1] != expression::TYPE_BOOL + or args[0]->infer_type() != expression::TYPE_BOOL + or args[1]->infer_type() != expression::TYPE_BOOL + ) { + throw uhd::syntax_error("eval(): XOR type mismatch"); + } + return expression_literal(bool( + args[0]->eval().get_bool() xor args[1]->eval().get_bool() + )); + } + + if (name == "AND") { + if (arg_types.size() != 2 + or args.size() != 2 + or arg_types[0] != expression::TYPE_BOOL + or arg_types[1] != expression::TYPE_BOOL + or args[0]->infer_type() != expression::TYPE_BOOL + or args[1]->infer_type() != expression::TYPE_BOOL + ) { + throw uhd::syntax_error("eval(): AND type mismatch"); + } + std::cout << "Calling AND" << std::endl; + and_counter++; + return expression_literal(bool( + args[0]->eval().get_bool() and args[1]->eval().get_bool() + )); + } + + if (name == "ADD") { + if (args.size() != 2) { + throw uhd::syntax_error("eval(): ADD type mismatch"); + } + if ((args[0]->infer_type() == expression::TYPE_INT) and + (args[1]->infer_type() == expression::TYPE_INT)) { + return expression_literal(int( + args[0]->eval().get_int() + args[1]->eval().get_int() + )); + } + else if ((args[0]->infer_type() == expression::TYPE_DOUBLE) and + (args[1]->infer_type() == expression::TYPE_DOUBLE)) { + return expression_literal(double( + args[0]->eval().get_double() + args[1]->eval().get_double() + )); + } + throw uhd::syntax_error("eval(): ADD type mismatch"); + } + throw uhd::syntax_error("eval(): unknown function"); + } + + // We don't actually need this + void register_function( + const std::string &, + const function_table::function_ptr &, + const expression::type_t, + const expression_function::argtype_list_type & + ) {}; + +}; + + +// The annoying part: Testing the test fixtures +BOOST_AUTO_TEST_CASE(test_functable_mockup) +{ + functable_mockup_impl functable; + + BOOST_CHECK(functable.function_exists("ADD")); + BOOST_CHECK(functable.function_exists("XOR")); + BOOST_CHECK(not functable.function_exists("FOOBAR")); + + BOOST_CHECK(functable.function_exists("ADD", two_int_args)); + BOOST_CHECK(functable.function_exists("ADD", two_double_args)); + BOOST_CHECK(functable.function_exists("XOR", two_bool_args)); + BOOST_CHECK(not functable.function_exists("ADD", two_bool_args)); + BOOST_CHECK(not functable.function_exists("ADD", no_args)); + BOOST_CHECK(not functable.function_exists("XOR", no_args)); + + BOOST_CHECK_EQUAL(functable.get_type("ADD", two_int_args), expression::TYPE_INT); + BOOST_CHECK_EQUAL(functable.get_type("ADD", two_double_args), expression::TYPE_DOUBLE); + BOOST_CHECK_EQUAL(functable.get_type("XOR", two_bool_args), expression::TYPE_BOOL); + + expression_container::expr_list_type add_args_int = + boost::assign::list_of(E(2))(E(3)) + ; + expression_container::expr_list_type add_args_dbl = + boost::assign::list_of + (E(2.25)) + (E(5.0)) + ; + expression_container::expr_list_type xor_args_bool = + boost::assign::list_of + (E(true)) + (E(false)) + ; + + BOOST_CHECK_EQUAL(functable.eval("ADD", two_int_args, add_args_int), expression_literal(5)); + BOOST_CHECK_EQUAL(functable.eval("ADD", two_double_args, add_args_dbl), expression_literal(7.25)); + BOOST_CHECK_EQUAL(functable.eval("XOR", two_bool_args, xor_args_bool), expression_literal(true)); +} + +BOOST_AUTO_TEST_CASE(test_function_expression) +{ + function_table::sptr ft = boost::make_shared<functable_mockup_impl>(); + + // Very simple function: ADD(2, 3) + expression_function func1("ADD", ft); + func1.add(E(2)); + func1.add(E(3)); + + BOOST_CHECK_EQUAL(func1.eval(), expression_literal(5)); + + // More elaborate: ADD(ADD(2, 3), ADD(ADD(4, 5), 6)) ?= 20 + // f4 f1 f3 f2 + expression_function f1("ADD", ft); + f1.add(E(2)); + f1.add(E(3)); + expression_function f2("ADD", ft); + f2.add(E(4)); + f2.add(E(5)); + expression_function f3("ADD", ft); + f3.add(boost::make_shared<expression_function>(f2)); + f3.add(E(6)); + expression_function f4("ADD", ft); + f4.add(boost::make_shared<expression_function>(f1)); + f4.add(boost::make_shared<expression_function>(f3)); + + BOOST_CHECK_EQUAL(f4.eval().get_int(), 20); +} + +BOOST_AUTO_TEST_CASE(test_function_expression_laziness) +{ + function_table::sptr ft = boost::make_shared<functable_mockup_impl>(); + + // We run AND(AND(false, false), AND(false, false)). + // f1 f2 f3 + // That makes three ANDs + // in total. However, we will only see AND being evaluated twice, because + // the outcome is clear after running the first AND in the argument list. + expression_function::sptr f2 = boost::make_shared<expression_function>("AND", ft); + f2->add(E(false)); + f2->add(E(false)); + BOOST_CHECK(not f2->eval().get_bool()); + + expression_function::sptr f3 = boost::make_shared<expression_function>("AND", ft); + f3->add(E(false)); + f3->add(E(false)); + BOOST_CHECK(not f3->eval().get_bool()); + + and_counter = 0; + expression_function::sptr f1 = boost::make_shared<expression_function>("AND", ft); + f1->add(f2); + f1->add(f3); + + BOOST_CHECK(not f1->eval().get_bool()); + BOOST_CHECK_EQUAL(and_counter, 2); +} + +BOOST_AUTO_TEST_CASE(test_sptrs) +{ + expression_container::sptr c = expression_container::make(); + BOOST_CHECK_EQUAL(c->infer_type(), expression::TYPE_BOOL); + BOOST_CHECK(c->eval().get_bool()); + + expression_variable::sptr v = expression_variable::make( + "$spp", + boost::bind(&variable_get_type, _1), // type-getter + boost::bind(&variable_get_value, _1) // value-getter + ); + + c->add(v); + BOOST_REQUIRE_EQUAL(c->infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(c->eval().get_int(), 5); +} + diff --git a/host/tests/nocscript_ftable_test.cpp b/host/tests/nocscript_ftable_test.cpp new file mode 100644 index 000000000..283245132 --- /dev/null +++ b/host/tests/nocscript_ftable_test.cpp @@ -0,0 +1,259 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "../lib/rfnoc/nocscript/function_table.hpp" +#include <boost/test/unit_test.hpp> +#include <boost/test/floating_point_comparison.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> +#include <iostream> + +#include "nocscript_common.hpp" + +BOOST_AUTO_TEST_CASE(test_basic_funcs) +{ + function_table::sptr ft = function_table::make(); + BOOST_CHECK(ft->function_exists("ADD")); + BOOST_CHECK(ft->function_exists("ADD", two_int_args)); + BOOST_CHECK(ft->function_exists("LE", two_int_args)); + BOOST_CHECK(ft->function_exists("GE")); + BOOST_CHECK(ft->function_exists("GE", two_int_args)); + BOOST_CHECK(ft->function_exists("TRUE")); + BOOST_CHECK(ft->function_exists("FALSE")); + + // Math + expression_container::expr_list_type two_int_values = boost::assign::list_of(E(2))(E(3)); + expression_container::expr_list_type two_int_values2 = boost::assign::list_of(E(3))(E(2)); + expression_container::expr_list_type two_double_values = boost::assign::list_of(E(2.0))(E(3.0)); + + BOOST_REQUIRE_EQUAL(ft->get_type("ADD", two_int_args), expression::TYPE_INT); + expression_literal e_add = ft->eval("ADD", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_add.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_add.get_int(), 5); + + BOOST_REQUIRE_EQUAL(ft->get_type("MULT", two_int_args), expression::TYPE_INT); + expression_literal e_mult_i = ft->eval("MULT", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_mult_i.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_mult_i.get_int(), 6); + + BOOST_REQUIRE_EQUAL(ft->get_type("MULT", two_double_args), expression::TYPE_DOUBLE); + expression_literal e_mult_d = ft->eval("MULT", two_double_args, two_double_values); + BOOST_REQUIRE_EQUAL(e_mult_d.infer_type(), expression::TYPE_DOUBLE); + BOOST_CHECK_CLOSE(e_mult_d.get_double(), 6.0, 0.01); + + BOOST_REQUIRE_EQUAL(ft->get_type("DIV", two_double_args), expression::TYPE_DOUBLE); + expression_literal e_div_d = ft->eval("DIV", two_double_args, two_double_values); + BOOST_REQUIRE_EQUAL(e_div_d.infer_type(), expression::TYPE_DOUBLE); + BOOST_CHECK_CLOSE(e_div_d.get_double(), 2.0/3.0, 0.01); + + BOOST_REQUIRE_EQUAL(ft->get_type("MODULO", two_int_args), expression::TYPE_INT); + expression_literal e_modulo_i = ft->eval("MODULO", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_modulo_i.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_modulo_i.get_int(), 2 % 3); + + BOOST_REQUIRE_EQUAL(ft->get_type("LE", two_int_args), expression::TYPE_BOOL); + expression_literal e_le = ft->eval("LE", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_le.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(e_le.get_bool(), true); + BOOST_CHECK_EQUAL(ft->eval("LE", two_int_args, two_int_values2).get_bool(), false); + expression_literal e_ge = ft->eval("GE", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(ft->get_type("GE", two_int_args), expression::TYPE_BOOL); + BOOST_REQUIRE_EQUAL(e_ge.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(e_ge.get_bool(), false); + + expression_container::expr_list_type sixty_four = boost::assign::list_of(E(64)); + expression_literal e_pwr2 = ft->eval("IS_PWR_OF_2", one_int_arg, sixty_four); + BOOST_REQUIRE_EQUAL(e_pwr2.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(e_pwr2.get_bool(), true); + expression_literal e_log2 = ft->eval("LOG2", one_int_arg, sixty_four); + BOOST_REQUIRE_EQUAL(e_log2.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_log2.get_int(), 6); + + // Boolean Logic + expression_container::expr_list_type e_true = boost::assign::list_of(E(true)); + expression_container::expr_list_type e_false = boost::assign::list_of(E(false)); + BOOST_CHECK(ft->eval("TRUE", no_args, empty_arg_list).to_bool()); + BOOST_CHECK(ft->eval("TRUE", no_args, empty_arg_list).get_bool()); + BOOST_CHECK_EQUAL(ft->eval("FALSE", no_args, empty_arg_list).to_bool(), false); + BOOST_CHECK_EQUAL(ft->eval("FALSE", no_args, empty_arg_list).get_bool(), false); + BOOST_CHECK(ft->eval("NOT", one_bool_arg, e_false).to_bool()); + + // Control + std::cout << "Checking ~1s sleep until... "; + expression_container::expr_list_type e_sleeptime = boost::assign::list_of(E(.999)); + BOOST_CHECK(ft->eval("SLEEP", one_double_arg, e_sleeptime).get_bool()); + std::cout << "Now." << std::endl; +} + +// Some bogus function to test the registry +expression_literal add_plus2_int(expression_container::expr_list_type args) +{ + return expression_literal(args[0]->eval().get_int() + args[1]->eval().get_int() + 2); +} + +BOOST_AUTO_TEST_CASE(test_add_funcs) +{ + function_table::sptr ft = function_table::make(); + + BOOST_CHECK(not ft->function_exists("ADD_PLUS_2")); + + expression_function::argtype_list_type add_int_args = boost::assign::list_of(expression::TYPE_INT)(expression::TYPE_INT); + ft->register_function( + "ADD_PLUS_2", + boost::bind(&add_plus2_int, _1), + expression::TYPE_INT, + add_int_args + ); + + BOOST_CHECK(ft->function_exists("ADD_PLUS_2")); + BOOST_CHECK(ft->function_exists("ADD_PLUS_2", add_int_args)); + + expression_container::expr_list_type add_int_values = boost::assign::list_of(E(2))(E(3)); + expression_literal e = ft->eval("ADD_PLUS_2", two_int_args, add_int_values); + BOOST_REQUIRE_EQUAL(e.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e.get_int(), 7); +} + +int dummy_true_counter = 0; +// Some bogus function to test the registry +expression_literal dummy_true(expression_container::expr_list_type) +{ + dummy_true_counter++; + std::cout << "Running dummy/true statement." << std::endl; + return expression_literal(true); +} + +int dummy_false_counter = 0; +// Some bogus function to test the registry +expression_literal dummy_false(expression_container::expr_list_type) +{ + dummy_false_counter++; + std::cout << "Running dummy/false statement." << std::endl; + return expression_literal(false); +} + +BOOST_AUTO_TEST_CASE(test_conditionals) +{ + function_table::sptr ft = function_table::make(); + ft->register_function( + "DUMMY", + boost::bind(&dummy_true, _1), + expression::TYPE_BOOL, + no_args + ); + ft->register_function( + "DUMMY_F", + boost::bind(&dummy_false, _1), + expression::TYPE_BOOL, + no_args + ); + BOOST_REQUIRE(ft->function_exists("DUMMY", no_args)); + BOOST_REQUIRE(ft->function_exists("DUMMY_F", no_args)); + + expression_function::sptr dummy_statement = boost::make_shared<expression_function>("DUMMY", ft); + expression_function::sptr if_statement = boost::make_shared<expression_function>("IF", ft); + if_statement->add(E(true)); + if_statement->add(dummy_statement); + + std::cout << "Dummy statement should run once until END:" << std::endl; + dummy_true_counter = 0; + BOOST_CHECK(if_statement->eval().get_bool()); + BOOST_CHECK_EQUAL(dummy_true_counter, 1); + std::cout << "END." << std::endl; + + std::cout << "Dummy statement should not run until END:" << std::endl; + expression_function::sptr if_statement2 = boost::make_shared<expression_function>("IF", ft); + if_statement2->add(E(false)); + if_statement2->add(dummy_statement); + dummy_true_counter = 0; + BOOST_CHECK(not if_statement2->eval().get_bool()); + BOOST_CHECK_EQUAL(dummy_true_counter, 0); + std::cout << "END." << std::endl; + + expression_function::sptr if_else_statement = boost::make_shared<expression_function>("IF_ELSE", ft); + expression_function::sptr dummy_statement_f = boost::make_shared<expression_function>("DUMMY_F", ft); + if_else_statement->add(E(true)); + if_else_statement->add(dummy_statement); + if_else_statement->add(dummy_statement_f); + dummy_true_counter = 0; + dummy_false_counter = 0; + std::cout << "Should execute dummy/true statement before END:" << std::endl; + BOOST_CHECK(if_else_statement->eval().get_bool()); + BOOST_CHECK_EQUAL(dummy_true_counter, 1); + BOOST_CHECK_EQUAL(dummy_false_counter, 0); + std::cout << "END." << std::endl; + + expression_function::sptr if_else_statement2 = boost::make_shared<expression_function>("IF_ELSE", ft); + if_else_statement2->add(E(false)); + if_else_statement2->add(dummy_statement); + if_else_statement2->add(dummy_statement_f); + dummy_true_counter = 0; + dummy_false_counter = 0; + std::cout << "Should execute dummy/false statement before END:" << std::endl; + BOOST_CHECK(not if_else_statement2->eval().get_bool()); + BOOST_CHECK_EQUAL(dummy_true_counter, 0); + BOOST_CHECK_EQUAL(dummy_false_counter, 1); + std::cout << "END." << std::endl; +} + +BOOST_AUTO_TEST_CASE(test_bitwise_funcs) +{ + function_table::sptr ft = function_table::make(); + BOOST_CHECK(ft->function_exists("SHIFT_RIGHT")); + BOOST_CHECK(ft->function_exists("SHIFT_RIGHT", two_int_args)); + BOOST_CHECK(ft->function_exists("SHIFT_LEFT")); + BOOST_CHECK(ft->function_exists("SHIFT_LEFT", two_int_args)); + BOOST_CHECK(ft->function_exists("BITWISE_AND")); + BOOST_CHECK(ft->function_exists("BITWISE_AND", two_int_args)); + BOOST_CHECK(ft->function_exists("BITWISE_OR")); + BOOST_CHECK(ft->function_exists("BITWISE_OR", two_int_args)); + BOOST_CHECK(ft->function_exists("BITWISE_XOR")); + BOOST_CHECK(ft->function_exists("BITWISE_XOR", two_int_args)); + + // Bitwise Math + int int_value1 = 0x2; + int int_value2 = 0x3; + expression_container::expr_list_type two_int_values = boost::assign::list_of(E(int_value1))(E(int_value2)); + + BOOST_REQUIRE_EQUAL(ft->get_type("SHIFT_RIGHT", two_int_args), expression::TYPE_INT); + expression_literal e_shift_right = ft->eval("SHIFT_RIGHT", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_shift_right.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_shift_right.get_int(), int_value1 >> int_value2); + + BOOST_REQUIRE_EQUAL(ft->get_type("SHIFT_LEFT", two_int_args), expression::TYPE_INT); + expression_literal e_shift_left = ft->eval("SHIFT_LEFT", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_shift_left.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_shift_left.get_int(), int_value1 << int_value2); + + BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_AND", two_int_args), expression::TYPE_INT); + expression_literal e_bitwise_and = ft->eval("BITWISE_AND", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_bitwise_and.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_bitwise_and.get_int(), int_value1 & int_value2); + + BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_OR", two_int_args), expression::TYPE_INT); + expression_literal e_bitwise_or = ft->eval("BITWISE_OR", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_bitwise_or.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_bitwise_or.get_int(), int_value1 | int_value2); + + BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_XOR", two_int_args), expression::TYPE_INT); + expression_literal e_bitwise_xor = ft->eval("BITWISE_XOR", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_bitwise_xor.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_bitwise_xor.get_int(), int_value1 ^ int_value2); +} diff --git a/host/tests/nocscript_parser_test.cpp b/host/tests/nocscript_parser_test.cpp new file mode 100644 index 000000000..a9c25977e --- /dev/null +++ b/host/tests/nocscript_parser_test.cpp @@ -0,0 +1,167 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "../lib/rfnoc/nocscript/function_table.hpp" +#include "../lib/rfnoc/nocscript/parser.hpp" +#include <uhd/exception.hpp> +#include <boost/test/unit_test.hpp> +#include <boost/test/floating_point_comparison.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> +#include <iostream> + +#include "nocscript_common.hpp" + +const int SPP_VALUE = 64; + +// Need those for the variable testing: +expression::type_t variable_get_type(const std::string &var_name) +{ + if (var_name == "spp") { + std::cout << "Returning type for $spp..." << std::endl; + return expression::TYPE_INT; + } + if (var_name == "is_true") { + std::cout << "Returning type for $is_true..." << std::endl; + return expression::TYPE_BOOL; + } + + throw uhd::syntax_error("Cannot infer type (unknown variable)"); +} + +expression_literal variable_get_value(const std::string &var_name) +{ + if (var_name == "spp") { + std::cout << "Returning value for $spp..." << std::endl; + return expression_literal(SPP_VALUE); + } + if (var_name == "is_true") { + std::cout << "Returning value for $is_true..." << std::endl; + return expression_literal(true); + } + + throw uhd::syntax_error("Cannot read value (unknown variable)"); +} + +#define SETUP_FT_AND_PARSER() \ + function_table::sptr ft = function_table::make(); \ + parser::sptr p = parser::make( \ + ft, \ + boost::bind(&variable_get_type, _1), \ + boost::bind(&variable_get_value, _1) \ + ); + +BOOST_AUTO_TEST_CASE(test_fail) +{ + SETUP_FT_AND_PARSER(); + + // Missing closing parens: + BOOST_REQUIRE_THROW(p->create_expr_tree("ADD1(1, "), uhd::syntax_error); + // Double comma: + BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1,, 2)"), uhd::syntax_error); + // No comma: + BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1 2)"), uhd::syntax_error); + // Double closing parens: + BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1, 2))"), uhd::syntax_error); + // Unknown function: + BOOST_REQUIRE_THROW(p->create_expr_tree("GLORPGORP(1, 2)"), uhd::syntax_error); +} + +BOOST_AUTO_TEST_CASE(test_adds_no_vars) +{ + SETUP_FT_AND_PARSER(); + BOOST_REQUIRE(ft->function_exists("ADD")); + + const std::string line("ADD(1, ADD(2, ADD(3, 4)))"); + expression::sptr e = p->create_expr_tree(line); + expression_literal result = e->eval(); + + BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(result.get_int(), 1+2+3+4); +} + +BOOST_AUTO_TEST_CASE(test_adds_with_vars) +{ + SETUP_FT_AND_PARSER(); + + const std::string line("ADD(1, ADD(2, $spp))"); + expression::sptr e = p->create_expr_tree(line); + expression_literal result = e->eval(); + + BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(result.get_int(), 1+2+SPP_VALUE); +} + +BOOST_AUTO_TEST_CASE(test_fft_check) +{ + SETUP_FT_AND_PARSER(); + + const std::string line("GE($spp, 16) AND LE($spp, 4096) AND IS_PWR_OF_2($spp)"); + expression::sptr e = p->create_expr_tree(line); + expression_literal result = e->eval(); + + BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK(result.get_bool()); +} + +BOOST_AUTO_TEST_CASE(test_pure_string) +{ + SETUP_FT_AND_PARSER(); + + // Eval all, return last expression + const std::string line("'foo foo', \"bar\""); + expression_literal result = p->create_expr_tree(line)->eval(); + + BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_STRING); + BOOST_CHECK_EQUAL(result.get_string(), "bar"); +} + +int dummy_false_counter = 0; +expression_literal dummy_false(expression_container::expr_list_type) +{ + dummy_false_counter++; + std::cout << "Running dummy/false statement." << std::endl; + return expression_literal(false); +} + +BOOST_AUTO_TEST_CASE(test_multi_commmand) +{ + SETUP_FT_AND_PARSER(); + + ft->register_function( + "DUMMY", + boost::bind(&dummy_false, _1), + expression::TYPE_BOOL, + no_args + ); + + dummy_false_counter = 0; + p->create_expr_tree("DUMMY(), DUMMY(), DUMMY()")->eval(); + BOOST_CHECK_EQUAL(dummy_false_counter, 3); + + dummy_false_counter = 0; + p->create_expr_tree("DUMMY() AND DUMMY() AND DUMMY()")->eval(); + BOOST_CHECK_EQUAL(dummy_false_counter, 1); + + dummy_false_counter = 0; + p->create_expr_tree("DUMMY() OR DUMMY() OR DUMMY()")->eval(); + BOOST_CHECK_EQUAL(dummy_false_counter, 3); +} + diff --git a/host/tests/node_connect_test.cpp b/host/tests/node_connect_test.cpp new file mode 100644 index 000000000..ff01828e3 --- /dev/null +++ b/host/tests/node_connect_test.cpp @@ -0,0 +1,143 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "graph.hpp" +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +class source_node : public test_node +{ +public: + typedef boost::shared_ptr<source_node> sptr; + + source_node(const std::string &test_id, size_t output_port) + : test_node(test_id) + , active_rx_streamer_on_port(0) + , _output_port(output_port) {}; + + void set_rx_streamer(bool active, const size_t port) + { + if (active) { + std::cout << "[source_node] Someone is registering a rx streamer on port " << port << std::endl; + active_rx_streamer_on_port = port; + } + } + size_t active_rx_streamer_on_port; + +protected: + size_t _request_output_port( + const size_t, + const uhd::device_addr_t & + ) const { + return _output_port; + } + + const size_t _output_port; + +}; /* class result_node */ + +class sink_node : public test_node +{ +public: + typedef boost::shared_ptr<sink_node> sptr; + + sink_node(const std::string &test_id, size_t input_port) + : test_node(test_id) + , active_tx_streamer_on_port(0) + , _input_port(input_port) {}; + + void set_tx_streamer(bool active, const size_t port) + { + if (active) { + std::cout << "[sink_node] Someone is registering a tx streamer on port " << port << std::endl; + active_tx_streamer_on_port = port; + } + } + size_t active_tx_streamer_on_port; + +protected: + size_t _request_input_port( + const size_t, + const uhd::device_addr_t & + ) const { + return _input_port; + } + + const size_t _input_port; + +}; /* class result_node */ + +#define MAKE_SOURCE_NODE(name, port) source_node::sptr name(new source_node(#name, port)); +#define MAKE_SINK_NODE(name, port) sink_node::sptr name(new sink_node(#name, port)); + +BOOST_AUTO_TEST_CASE(test_simple_connect) +{ + MAKE_SOURCE_NODE(node_A, 42); + MAKE_SINK_NODE(node_B, 23); + + size_t src_port = node_A->connect_downstream(node_B, 1); + size_t dst_port = node_B->connect_upstream(node_A, 2); + + BOOST_CHECK_EQUAL(src_port, 42); + BOOST_CHECK_EQUAL(dst_port, 23); + + node_A->set_downstream_port(src_port, dst_port); + node_B->set_upstream_port(dst_port, src_port); + BOOST_CHECK_EQUAL(node_A->get_downstream_port(src_port), dst_port); + BOOST_CHECK_EQUAL(node_B->get_upstream_port(dst_port), src_port); + + BOOST_REQUIRE_THROW(node_A->get_downstream_port(999), uhd::value_error); +} + +BOOST_AUTO_TEST_CASE(test_fail) +{ + MAKE_SOURCE_NODE(node_A, 42); + MAKE_SINK_NODE(node_B, ANY_PORT); + + node_A->connect_downstream(node_B, 1); + BOOST_REQUIRE_THROW(node_B->connect_upstream(node_A, 2), uhd::type_error); +} + +BOOST_AUTO_TEST_CASE(test_set_streamers) +{ + MAKE_SOURCE_NODE(node_A, 0); + MAKE_NODE(node_B); + MAKE_SINK_NODE(node_C, 0); + + size_t src_port_A = node_A->connect_downstream(node_B, 0); + size_t src_port_B = node_B->connect_downstream(node_C, 0); + size_t dst_port_B = node_B->connect_upstream(node_A, 0); + size_t dst_port_C = node_C->connect_upstream(node_B, 0); + + std::cout << "src_port_A: " << src_port_A << std::endl; + std::cout << "src_port_B: " << src_port_B << std::endl; + std::cout << "dst_port_B: " << dst_port_B << std::endl; + std::cout << "dst_port_C: " << dst_port_C << std::endl; + + node_A->set_downstream_port(src_port_A, dst_port_B); + node_B->set_upstream_port(dst_port_B, src_port_A); + node_B->set_downstream_port(src_port_B, dst_port_C); + node_C->set_upstream_port(dst_port_C, src_port_B); + + node_A->set_tx_streamer(true, 0); + node_C->set_rx_streamer(true, 0); + + BOOST_CHECK_EQUAL(node_A->active_rx_streamer_on_port, src_port_A); + BOOST_CHECK_EQUAL(node_C->active_tx_streamer_on_port, dst_port_C); +} diff --git a/host/tests/property_test.cpp b/host/tests/property_test.cpp index 00bb3c022..61d1de9a6 100644 --- a/host/tests/property_test.cpp +++ b/host/tests/property_test.cpp @@ -28,18 +28,26 @@ struct coercer_type{ }; struct setter_type{ + setter_type() : _count(0), _x(0) {} + void doit(int x){ + _count++; _x = x; } + int _count; int _x; }; struct getter_type{ + getter_type() : _count(0), _x(0) {} + int doit(void){ + _count++; return _x; } + int _count; int _x; }; @@ -57,29 +65,72 @@ BOOST_AUTO_TEST_CASE(test_prop_simple){ BOOST_CHECK_EQUAL(prop.get(), 34); } -BOOST_AUTO_TEST_CASE(test_prop_with_subscriber){ +BOOST_AUTO_TEST_CASE(test_prop_with_desired_subscriber){ uhd::property_tree::sptr tree = uhd::property_tree::make(); uhd::property<int> &prop = tree->create<int>("/"); setter_type setter; - prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + prop.add_desired_subscriber(boost::bind(&setter_type::doit, &setter, _1)); prop.set(42); + BOOST_CHECK_EQUAL(prop.get_desired(), 42); BOOST_CHECK_EQUAL(prop.get(), 42); BOOST_CHECK_EQUAL(setter._x, 42); prop.set(34); + BOOST_CHECK_EQUAL(prop.get_desired(), 34); BOOST_CHECK_EQUAL(prop.get(), 34); BOOST_CHECK_EQUAL(setter._x, 34); } +BOOST_AUTO_TEST_CASE(test_prop_with_coerced_subscriber){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/"); + + setter_type setter; + prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1)); + + prop.set(42); + BOOST_CHECK_EQUAL(prop.get_desired(), 42); + BOOST_CHECK_EQUAL(prop.get(), 42); + BOOST_CHECK_EQUAL(setter._x, 42); + + prop.set(34); + BOOST_CHECK_EQUAL(prop.get_desired(), 34); + BOOST_CHECK_EQUAL(prop.get(), 34); + BOOST_CHECK_EQUAL(setter._x, 34); +} + +BOOST_AUTO_TEST_CASE(test_prop_manual_coercion){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/", uhd::property_tree::MANUAL_COERCE); + + setter_type dsetter, csetter; + prop.add_desired_subscriber(boost::bind(&setter_type::doit, &dsetter, _1)); + prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &csetter, _1)); + + BOOST_CHECK_EQUAL(dsetter._x, 0); + BOOST_CHECK_EQUAL(csetter._x, 0); + + prop.set(42); + BOOST_CHECK_EQUAL(prop.get_desired(), 42); + BOOST_CHECK_EQUAL(dsetter._x, 42); + BOOST_CHECK_EQUAL(csetter._x, 0); + + prop.set_coerced(34); + BOOST_CHECK_EQUAL(prop.get_desired(), 42); + BOOST_CHECK_EQUAL(prop.get(), 34); + BOOST_CHECK_EQUAL(dsetter._x, 42); + BOOST_CHECK_EQUAL(csetter._x, 34); +} + BOOST_AUTO_TEST_CASE(test_prop_with_publisher){ uhd::property_tree::sptr tree = uhd::property_tree::make(); uhd::property<int> &prop = tree->create<int>("/"); BOOST_CHECK(prop.empty()); getter_type getter; - prop.publish(boost::bind(&getter_type::doit, &getter)); + prop.set_publisher(boost::bind(&getter_type::doit, &getter)); BOOST_CHECK(not prop.empty()); getter._x = 42; @@ -96,10 +147,10 @@ BOOST_AUTO_TEST_CASE(test_prop_with_publisher_and_subscriber){ uhd::property<int> &prop = tree->create<int>("/"); getter_type getter; - prop.publish(boost::bind(&getter_type::doit, &getter)); + prop.set_publisher(boost::bind(&getter_type::doit, &getter)); setter_type setter; - prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1)); getter._x = 42; prop.set(0); @@ -117,10 +168,10 @@ BOOST_AUTO_TEST_CASE(test_prop_with_coercion){ uhd::property<int> &prop = tree->create<int>("/"); setter_type setter; - prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1)); coercer_type coercer; - prop.coerce(boost::bind(&coercer_type::doit, &coercer, _1)); + prop.set_coercer(boost::bind(&coercer_type::doit, &coercer, _1)); prop.set(42); BOOST_CHECK_EQUAL(prop.get(), 40); diff --git a/host/tests/rate_node_test.cpp b/host/tests/rate_node_test.cpp new file mode 100644 index 000000000..2e17c02a5 --- /dev/null +++ b/host/tests/rate_node_test.cpp @@ -0,0 +1,139 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "graph.hpp" +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +// test class derived, knows about rates +class rate_aware_node : public test_node, public rate_node_ctrl +{ +public: + typedef boost::shared_ptr<rate_aware_node> sptr; + + rate_aware_node(const std::string &test_id) : test_node(test_id) {}; + +}; /* class rate_aware_node */ + +// test class derived, sets rates +class rate_setting_node : public test_node, public rate_node_ctrl +{ +public: + typedef boost::shared_ptr<rate_setting_node> sptr; + + rate_setting_node(const std::string &test_id, double samp_rate) : test_node(test_id), _samp_rate(samp_rate) {}; + + double get_input_samp_rate(size_t) { return _samp_rate; }; + double get_output_samp_rate(size_t) { return _samp_rate; }; + +private: + double _samp_rate; + +}; /* class rate_setting_node */ + +#define MAKE_RATE_NODE(name) rate_aware_node::sptr name(new rate_aware_node(#name)); +#define MAKE_RATE_SETTING_NODE(name, rate) rate_setting_node::sptr name(new rate_setting_node(#name, rate)); + +BOOST_AUTO_TEST_CASE(test_simplest_downstream_search) +{ + const double test_rate = 0.25; + MAKE_RATE_NODE(node_A); + MAKE_RATE_SETTING_NODE(node_B, test_rate); + + // Simplest possible scenario: Connect B downstream of A and let + // it find B + connect_nodes(node_A, node_B); + + double result_rate = node_A->get_input_samp_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_skip_downstream_search) +{ + const double test_rate = 0.25; + MAKE_RATE_NODE(node_A); + MAKE_NODE(node_B); + MAKE_RATE_SETTING_NODE(node_C, test_rate); + + // Slightly more elaborate: Add another block in between that has no + // clue about rates + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + double result_rate = node_A->get_input_samp_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_tree_downstream_search) +{ + const double test_rate = 0.25; + MAKE_RATE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_RATE_SETTING_NODE(node_B1, test_rate); + MAKE_RATE_SETTING_NODE(node_C0, test_rate); + MAKE_RATE_SETTING_NODE(node_C1, rate_node_ctrl::RATE_UNDEFINED); + + // Tree: Downstream of our first node are 3 rate setting blocks. + // Two set the same rate, the third does not care. + connect_nodes(node_A, node_B0); + connect_nodes(node_A, node_B1); + connect_nodes(node_B0, node_C0); + connect_nodes(node_B0, node_C1); + + double result_rate = node_A->get_input_samp_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_tree_downstream_search_throw) +{ + const double test_rate = 0.25; + MAKE_RATE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_RATE_SETTING_NODE(node_B1, test_rate); + MAKE_RATE_SETTING_NODE(node_C0, test_rate); + MAKE_RATE_SETTING_NODE(node_C1, test_rate * 2); + + // Tree: Downstream of our first node are 3 rate setting blocks. + // Two set the same rate, the third has a different rate. + // This will cause a throw. + connect_nodes(node_A, node_B0); + connect_nodes(node_A, node_B1); + connect_nodes(node_B0, node_C0); + connect_nodes(node_B0, node_C1); + + BOOST_CHECK_THROW(node_A->get_input_samp_rate(), uhd::runtime_error); +} + +BOOST_AUTO_TEST_CASE(test_skip_upstream_search) +{ + const double test_rate = 0.25; + MAKE_RATE_SETTING_NODE(node_A, test_rate); + MAKE_NODE(node_B); + MAKE_RATE_NODE(node_C); + + // Slightly more elaborate: Add another block in between that has no + // clue about rates + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + double result_rate = node_C->get_output_samp_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + diff --git a/host/tests/stream_sig_test.cpp b/host/tests/stream_sig_test.cpp new file mode 100644 index 000000000..904a14053 --- /dev/null +++ b/host/tests/stream_sig_test.cpp @@ -0,0 +1,82 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <iostream> +#include <boost/test/unit_test.hpp> +#include <uhd/exception.hpp> +#include <uhd/rfnoc/stream_sig.hpp> + +using namespace uhd::rfnoc; + +BOOST_AUTO_TEST_CASE(test_stream_sig) { + stream_sig_t stream_sig; + + BOOST_CHECK_EQUAL(stream_sig.item_type, ""); + BOOST_CHECK_EQUAL(stream_sig.vlen, 0); + BOOST_CHECK_EQUAL(stream_sig.packet_size, 0); + BOOST_CHECK_EQUAL(stream_sig.is_bursty, false); + + std::stringstream ss; + ss << stream_sig; + // Eventually actually test the contents + std::cout << ss.str() << std::endl; +} + +BOOST_AUTO_TEST_CASE(test_stream_sig_compat) { + stream_sig_t upstream_sig; + stream_sig_t downstream_sig; + + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.vlen = 32; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + downstream_sig.vlen = 32; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.vlen = 16; + BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.vlen = 32; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.packet_size = 8; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + downstream_sig.packet_size = 12; + BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.packet_size = 0; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + downstream_sig.item_type = ""; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.item_type = "sc16"; + downstream_sig.item_type = "s8"; + BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig)); +} + +BOOST_AUTO_TEST_CASE(test_stream_sig_types) { + stream_sig_t stream_sig; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 0); + stream_sig.item_type = "sc16"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 4); + stream_sig.item_type = "sc12"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 3); + stream_sig.item_type = "sc8"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 2); + stream_sig.item_type = "s16"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 2); + stream_sig.item_type = "s8"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 1); + stream_sig.item_type = "fc32"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 8); + stream_sig.item_type = "not_a_type"; + BOOST_REQUIRE_THROW(stream_sig.get_bytes_per_item(), uhd::key_error); +} diff --git a/host/tests/tick_node_test.cpp b/host/tests/tick_node_test.cpp new file mode 100644 index 000000000..fc1b8c544 --- /dev/null +++ b/host/tests/tick_node_test.cpp @@ -0,0 +1,110 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "graph.hpp" +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +// test class derived, knows about rates +class tick_aware_node : public test_node, public tick_node_ctrl +{ +public: + typedef boost::shared_ptr<tick_aware_node> sptr; + + tick_aware_node(const std::string &test_id) : test_node(test_id) {}; + +}; /* class tick_aware_node */ + +// test class derived, sets rates +class tick_setting_node : public test_node, public tick_node_ctrl +{ +public: + typedef boost::shared_ptr<tick_setting_node> sptr; + + tick_setting_node(const std::string &test_id, double tick_rate) : test_node(test_id), _tick_rate(tick_rate) {}; + +protected: + double _get_tick_rate() { return _tick_rate; }; + +private: + const double _tick_rate; + +}; /* class tick_setting_node */ + +#define MAKE_TICK_NODE(name) tick_aware_node::sptr name(new tick_aware_node(#name)); +#define MAKE_TICK_SETTING_NODE(name, rate) tick_setting_node::sptr name(new tick_setting_node(#name, rate)); + +BOOST_AUTO_TEST_CASE(test_simplest_downstream_search) +{ + const double test_rate = 0.25; + MAKE_TICK_NODE(node_A); + MAKE_TICK_SETTING_NODE(node_B, test_rate); + + // Simplest possible scenario: Connect B downstream of A and let + // it find B + connect_nodes(node_A, node_B); + + double result_rate = node_A->get_tick_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_both_ways_search) +{ + const double test_rate = 0.25; + MAKE_TICK_SETTING_NODE(node_A, tick_node_ctrl::RATE_UNDEFINED); + MAKE_TICK_NODE(node_B); + MAKE_TICK_SETTING_NODE(node_C, test_rate); + + std::cout << "a->b" << std::endl; + connect_nodes(node_A, node_B); + std::cout << "b->a" << std::endl; + connect_nodes(node_B, node_C); + + std::cout << "search" << std::endl; + double result_rate = node_B->get_tick_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_both_ways_search_reversed) +{ + const double test_rate = 0.25; + MAKE_TICK_SETTING_NODE(node_A, test_rate); + MAKE_TICK_NODE(node_B); + MAKE_TICK_SETTING_NODE(node_C, tick_node_ctrl::RATE_UNDEFINED); + + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + double result_rate = node_B->get_tick_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_both_ways_search_fail) +{ + const double test_rate = 0.25; + MAKE_TICK_SETTING_NODE(node_A, test_rate); + MAKE_TICK_NODE(node_B); + MAKE_TICK_SETTING_NODE(node_C, 2 * test_rate); + + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + BOOST_CHECK_THROW(node_B->get_tick_rate(), uhd::runtime_error); +} diff --git a/host/uhd.pc.in b/host/uhd.pc.in index 7ef3b7ebd..7ccd3e913 100644 --- a/host/uhd.pc.in +++ b/host/uhd.pc.in @@ -11,5 +11,5 @@ Requires: Requires.private: @UHD_PC_REQUIRES@ Conflicts: Cflags: -I${includedir} @UHD_PC_CFLAGS@ -Libs: -L${libdir} -luhd +Libs: -L${libdir} -luhd -lboost_system Libs.private: @UHD_PC_LIBS@ diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index 9f0a6afef..6f72c97bc 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -19,6 +19,7 @@ # Utilities that get installed into the runtime path ######################################################################## SET(util_runtime_sources + uhd_config_info.cpp uhd_find_devices.cpp uhd_usrp_probe.cpp uhd_image_loader.cpp @@ -30,7 +31,7 @@ SET(util_runtime_sources SET(x3xx_burner_sources usrp_x3xx_fpga_burner.cpp - cdecode.c + ${CMAKE_CURRENT_SOURCE_DIR}/../lib/usrp/x300/cdecode.c ) find_package(UDev) @@ -48,18 +49,25 @@ FOREACH(util_source ${util_runtime_sources}) UHD_INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) ENDFOREACH(util_source) -ADD_EXECUTABLE(usrp_x3xx_fpga_burner ${x3xx_burner_sources}) -TARGET_LINK_LIBRARIES(usrp_x3xx_fpga_burner uhd ${Boost_LIBRARIES}) -UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) +IF(ENABLE_X300) + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/usrp/x300) + ADD_EXECUTABLE(usrp_x3xx_fpga_burner ${x3xx_burner_sources}) + TARGET_LINK_LIBRARIES(usrp_x3xx_fpga_burner uhd ${Boost_LIBRARIES}) + UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) +ENDIF(ENABLE_X300) ######################################################################## # Utilities that get installed into the share path ######################################################################## SET(util_share_sources + converter_benchmark.cpp query_gpsdo_sensors.cpp usrp_burn_db_eeprom.cpp usrp_burn_mb_eeprom.cpp ) +SET(util_share_sources_py + converter_benchmark.py +) IF(ENABLE_USB) LIST(APPEND util_share_sources fx2_init_eeprom.cpp @@ -108,9 +116,20 @@ FOREACH(util_source ${util_share_sources}) TARGET_LINK_LIBRARIES(${util_name} uhd ${Boost_LIBRARIES}) UHD_INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) ENDFOREACH(util_source) +FOREACH(util_source ${util_share_sources_py}) + UHD_INSTALL(PROGRAMS + ${CMAKE_CURRENT_SOURCE_DIR}/${util_source} + DESTINATION ${PKG_LIB_DIR}/utils + COMPONENT utilities + ) +ENDFOREACH(util_source) -UHD_INSTALL(TARGETS usrp_n2xx_simple_net_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) -UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +IF(ENABLE_USRP2) + UHD_INSTALL(TARGETS usrp_n2xx_simple_net_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +ENDIF(ENABLE_USRP2) +IF(ENABLE_X300) + UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +ENDIF(ENABLE_X300) #UHD images downloader configuration CONFIGURE_FILE( diff --git a/host/utils/cdecode.c b/host/utils/cdecode.c deleted file mode 100644 index 1d09cbe22..000000000 --- a/host/utils/cdecode.c +++ /dev/null @@ -1,80 +0,0 @@ -/* -cdecoder.c - c source to a base64 decoding algorithm implementation - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#include "cdecode.h" - -int base64_decode_value(char value_in){ - static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; - static const char decoding_size = sizeof(decoding); - value_in -= 43; - if ((signed char)value_in < 0 || value_in > decoding_size) return -1; - return decoding[(int)value_in]; -} - -void base64_init_decodestate(base64_decodestate* state_in){ - state_in->step = step_a; - state_in->plainchar = 0; -} - -size_t base64_decode_block(const char* code_in, const size_t length_in, char* plaintext_out, base64_decodestate* state_in){ - const char* codechar = code_in; - char* plainchar = plaintext_out; - char fragment; - - *plainchar = state_in->plainchar; - - switch (state_in->step){ - while (1){ - case step_a: - do{ - if (codechar == code_in+length_in){ - state_in->step = step_a; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while ((signed char)fragment < 0); - *plainchar = (fragment & 0x03f) << 2; - - case step_b: - do{ - if (codechar == code_in+length_in){ - state_in->step = step_b; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while ((signed char)fragment < 0); - *plainchar++ |= (fragment & 0x030) >> 4; - *plainchar = (fragment & 0x00f) << 4; - case step_c: - do{ - if (codechar == code_in+length_in) - { - state_in->step = step_c; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while ((signed char)fragment < 0); - *plainchar++ |= (fragment & 0x03c) >> 2; - *plainchar = (fragment & 0x003) << 6; - case step_d: - do{ - if (codechar == code_in+length_in){ - state_in->step = step_d; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while ((signed char)fragment < 0); - *plainchar++ |= (fragment & 0x03f); - } - } - /* control should not reach here */ - return plainchar - plaintext_out; -} diff --git a/host/utils/cdecode.h b/host/utils/cdecode.h deleted file mode 100644 index e1eee301f..000000000 --- a/host/utils/cdecode.h +++ /dev/null @@ -1,28 +0,0 @@ -/* -cdecode.h - c header for a base64 decoding algorithm - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#ifndef BASE64_CDECODE_H -#define BASE64_CDECODE_H - -#include <stddef.h> - -typedef enum{ - step_a, step_b, step_c, step_d -} base64_decodestep; - -typedef struct{ - base64_decodestep step; - char plainchar; -} base64_decodestate; - -void base64_init_decodestate(base64_decodestate* state_in); - -int base64_decode_value(char value_in); - -size_t base64_decode_block(const char* code_in, const size_t length_in, char* plaintext_out, base64_decodestate* state_in); - -#endif /* BASE64_CDECODE_H */ diff --git a/host/utils/converter_benchmark.cpp b/host/utils/converter_benchmark.cpp new file mode 100644 index 000000000..ddbf50255 --- /dev/null +++ b/host/utils/converter_benchmark.cpp @@ -0,0 +1,434 @@ +// +// Copyright 2015-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/convert.hpp> +#include <uhd/exception.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/timer.hpp> +#include <boost/algorithm/string.hpp> +#include <iostream> +#include <iomanip> +#include <map> +#include <complex> +#include <stdint.h> + +namespace po = boost::program_options; +using namespace uhd::convert; + +enum buf_init_t { + RANDOM, INC +}; + +// Convert `sc16_item32_le' -> `sc16' +// Finds the first _ in format and returns the string +// until then. Returns the entire string if no _ is found. +std::string format_to_type(const std::string &format) +{ + std::string ret_val = ""; + for (size_t i = 0; i < format.length(); i++) { + if (format[i] == '_') { + return ret_val; + } + ret_val.append(1, format[i]); + } + + return ret_val; +} + +void configure_conv( + converter::sptr conv, + const std::string &in_type, + const std::string &out_type +) { + if (in_type == "sc16") { + if (out_type == "fc32") { + std::cout << "Setting scalar to 32767." << std::endl; + conv->set_scalar(32767.); + return; + } + } + + if (in_type == "fc32") { + if (out_type == "sc16") { + std::cout << "Setting scalar to 32767." << std::endl; + conv->set_scalar(32767.); + return; + } + } + + std::cout << "No configuration required." << std::endl; +} + +template <typename T> +void init_random_vector_complex_float(std::vector<char> &buf_ptr, const size_t n_items) +{ + std::complex<T> * const buf = reinterpret_cast<std::complex<T> * const>(&buf_ptr[0]); + for (size_t i = 0; i < n_items; i++) { + buf[i] = std::complex<T>( + T((std::rand()/double(RAND_MAX/2)) - 1), + T((std::rand()/double(RAND_MAX/2)) - 1) + ); + } +} + +template <typename T> +void init_random_vector_complex_int(std::vector<char> &buf_ptr, const size_t n_items) +{ + std::complex<T> * const buf = reinterpret_cast<std::complex<T> * const>(&buf_ptr[0]); + for (size_t i = 0; i < n_items; i++) { + buf[i] = std::complex<T>(T(std::rand()), T(std::rand())); + } +} + +template <typename T> +void init_random_vector_real_int(std::vector<char> &buf_ptr, size_t n_items) +{ + T * const buf = reinterpret_cast<T * const>(&buf_ptr[0]); + for (size_t i = 0; i < n_items; i++) { + buf[i] = T(std::rand()); + } +} + +// Fill a buffer with increasing numbers +template <typename T> +void init_inc_vector(std::vector<char> &buf_ptr, size_t n_items) +{ + T * const buf = reinterpret_cast<T * const>(&buf_ptr[0]); + for (size_t i = 0; i < n_items; i++) { + buf[i] = T(i); + } +} + +void init_buffers( + std::vector< std::vector<char> > &buf, + const std::string &type, + size_t bytes_per_item, + buf_init_t buf_seed_mode +) { + if (buf.empty()) { + return; + } + size_t n_items = buf[0].size() / bytes_per_item; + + /// Fill with incrementing integers + if (buf_seed_mode == INC) { + for (size_t i = 0; i < buf.size(); i++) { + if (type == "sc8") { + init_inc_vector< std::complex<boost::int8_t> >(buf[i], n_items); + } else if (type == "sc16") { + init_inc_vector< std::complex<boost::int16_t> >(buf[i], n_items); + } else if (type == "sc32") { + init_inc_vector< std::complex<boost::int32_t> >(buf[i], n_items); + } else if (type == "fc32") { + init_inc_vector< std::complex<float> >(buf[i], n_items); + } else if (type == "fc64") { + init_inc_vector< std::complex<double> >(buf[i], n_items); + } else if (type == "s8") { + init_inc_vector< boost::int8_t >(buf[i], n_items); + } else if (type == "s16") { + init_inc_vector< boost::int16_t >(buf[i], n_items); + } else if (type == "item32") { + init_inc_vector< boost::uint32_t >(buf[i], n_items); + init_random_vector_real_int<boost::uint32_t>(buf[i], n_items); + } else { + throw uhd::runtime_error(str( + boost::format("Cannot handle data type: %s") % type + )); + } + } + + return; + } + + assert(buf_seed_mode == RANDOM); + + /// Fill with random data + for (size_t i = 0; i < buf.size(); i++) { + if (type == "sc8") { + init_random_vector_complex_int<boost::int8_t>(buf[i], n_items); + } else if (type == "sc16") { + init_random_vector_complex_int<boost::int16_t>(buf[i], n_items); + } else if (type == "sc32") { + init_random_vector_complex_int<boost::int32_t>(buf[i], n_items); + } else if (type == "fc32") { + init_random_vector_complex_float<float>(buf[i], n_items); + } else if (type == "fc64") { + init_random_vector_complex_float<double>(buf[i], n_items); + } else if (type == "s8") { + init_random_vector_real_int<boost::int8_t>(buf[i], n_items); + } else if (type == "s16") { + init_random_vector_real_int<boost::int16_t>(buf[i], n_items); + } else if (type == "item32") { + init_random_vector_real_int<boost::uint32_t>(buf[i], n_items); + } else { + throw uhd::runtime_error(str( + boost::format("Cannot handle data type: %s") % type + )); + } + } +} + +// Returns time elapsed +double run_benchmark( + converter::sptr conv, + const std::vector<const void *> &input_buf_refs, + const std::vector<void *> &output_buf_refs, + size_t n_items, + size_t iterations +) { + boost::timer benchmark_timer; + for (size_t i = 0; i < iterations; i++) { + conv->conv(input_buf_refs, output_buf_refs, n_items); + } + return benchmark_timer.elapsed(); +} + +template <typename T> +std::string void_ptr_to_hexstring(const void *v_ptr, size_t index) +{ + const T *ptr = reinterpret_cast<const T *>(v_ptr); + return str(boost::format("%X") % ptr[index]); +} + +std::string item_to_hexstring( + const void *v_ptr, + size_t index, + const std::string &type +) { + if (type == "fc32") { + return void_ptr_to_hexstring<uint64_t>(v_ptr, index); + } + else if (type == "sc16" || type == "item32") { + return void_ptr_to_hexstring<uint32_t>(v_ptr, index); + } + else if (type == "sc8" || type == "s16") { + return void_ptr_to_hexstring<uint16_t>(v_ptr, index); + } + else if (type == "u8") { + return void_ptr_to_hexstring<uint8_t>(v_ptr, index); + } + else { + return str(boost::format("<unhandled data type: %s>") % type); + } +} + +std::string item_to_string( + const void *v_ptr, + size_t index, + const std::string &type, + const bool print_hex +) { + if (print_hex) { + return item_to_hexstring(v_ptr, index, type); + } + + if (type == "sc16") { + const std::complex<boost::int16_t> *ptr = reinterpret_cast<const std::complex<boost::int16_t> *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else if (type == "sc8") { + const std::complex<boost::int8_t> *ptr = reinterpret_cast<const std::complex<boost::int8_t> *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else if (type == "fc32") { + const std::complex<float> *ptr = reinterpret_cast<const std::complex<float> *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else if (type == "item32") { + const boost::uint32_t *ptr = reinterpret_cast<const boost::uint32_t *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else if (type == "s16") { + const boost::int16_t *ptr = reinterpret_cast<const boost::int16_t *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else { + return str(boost::format("<unhandled data type: %s>") % type); + } +} + +int UHD_SAFE_MAIN(int argc, char *argv[]) +{ + std::string in_format, out_format; + std::string priorities; + std::string seed_mode; + priority_type prio = -1, max_prio; + size_t iterations, n_samples; + size_t n_inputs, n_outputs; + buf_init_t buf_seed_mode = RANDOM; + + /// Command line arguments + po::options_description desc("Converter benchmark options:"); + desc.add_options() + ("help", "help message") + ("in", po::value<std::string>(&in_format), "Input format (e.g. 'sc16')") + ("out", po::value<std::string>(&out_format), "Output format (e.g. 'sc16')") + ("samples", po::value<size_t>(&n_samples)->default_value(1000000), "Number of samples per iteration") + ("iterations", po::value<size_t>(&iterations)->default_value(10000), "Number of iterations per benchmark") + ("priorities", po::value<std::string>(&priorities)->default_value("default"), "Converter priorities. Can be 'default', 'all', or a comma-separated list of priorities.") + ("max-prio", po::value<priority_type>(&max_prio)->default_value(4), "Largest available priority (advanced feature)") + ("n-inputs", po::value<size_t>(&n_inputs)->default_value(1), "Number of input vectors") + ("n-outputs", po::value<size_t>(&n_outputs)->default_value(1), "Number of output vectors") + ("debug-converter", "Skip benchmark and print conversion results. Implies iterations==1 and will only run on a single converter.") + ("seed-mode", po::value<std::string>(&seed_mode)->default_value("random"), "How to initialize the data: random, incremental") + ("hex", "When using debug mode, dump memory in hex") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD Converter Benchmark Tool %s") % desc << std::endl << std::endl; + std::cout << " Use this to benchmark or debug converters." << std::endl + << " When using as a benchmark tool, it will output the execution time\n" + " for every conversion run in CSV format to stdout. Every line between\n" + " the output delimiters {{{ }}} is of the format: <PRIO>,<TIME IN MILLISECONDS>\n" + " When using for converter debugging, every line is formatted as\n" + " <INPUT_VALUE>,<OUTPUT_VALUE>\n" << std::endl; + return EXIT_FAILURE; + } + + // Parse more arguments + if (seed_mode == "incremental") { + buf_seed_mode = INC; + } else if (seed_mode == "random") { + buf_seed_mode = RANDOM; + } else { + std::cout << "Invalid argument: --seed-mode must be either 'incremental' or 'random'." << std::endl; + } + + bool debug_mode = bool(vm.count("debug-converter")); + if (debug_mode) { + iterations = 1; + } + + /// Create the converter(s) ////////////////////////////////////////////// + id_type converter_id; + converter_id.input_format = in_format; + converter_id.output_format = out_format; + converter_id.num_inputs = n_inputs; + converter_id.num_outputs = n_outputs; + std::cout << "Requested converter format: " << converter_id.to_string() + << std::endl; + uhd::dict<priority_type, converter::sptr> conv_list; + if (priorities == "default" or priorities.empty()) { + try { + conv_list[prio] = get_converter(converter_id, prio)(); // Can throw a uhd::key_error + } catch(const uhd::key_error &e) { + std::cout << "No converters found." << std::endl; + return EXIT_FAILURE; + } + } else if (priorities == "all") { + for (priority_type i = 0; i < max_prio; i++) { + try { + // get_converter() returns a factory function, execute that immediately: + converter::sptr conv_for_prio = get_converter(converter_id, i)(); // Can throw a uhd::key_error + conv_list[i] = conv_for_prio; + } catch (...) { + continue; + } + } + } else { // Assume that priorities contains a list of prios (e.g. 0,2,3) + std::vector<std::string> prios_in_list; + boost::split( + prios_in_list, + priorities, + boost::is_any_of(","), // Split at , + boost::token_compress_on // Avoid empty results + ); + BOOST_FOREACH(const std::string &this_prio, prios_in_list) { + size_t prio_index = boost::lexical_cast<size_t>(this_prio); + converter::sptr conv_for_prio = get_converter(converter_id, prio_index)(); // Can throw a uhd::key_error + conv_list[prio_index] = conv_for_prio; + } + } + std::cout << "Found " << conv_list.size() << " converter(s)." << std::endl; + + /// Create input and output buffers /////////////////////////////////////// + // First, convert the types to plain types (e.g. sc16_item32_le -> sc16) + const std::string in_type = format_to_type(in_format); + const std::string out_type = format_to_type(out_format); + const size_t in_size = get_bytes_per_item(in_type); + const size_t out_size = get_bytes_per_item(out_type); + // Create the buffers and fill them with random data & zeros, respectively + std::vector< std::vector<char> > input_buffers(n_inputs, std::vector<char>(in_size * n_samples, 0)); + std::vector< std::vector<char> > output_buffers(n_outputs, std::vector<char>(out_size * n_samples, 0)); + init_buffers(input_buffers, in_type, in_size, buf_seed_mode); + // Create ref vectors for the converter: + std::vector<const void *> input_buf_refs(n_inputs); + std::vector<void *> output_buf_refs(n_outputs); + for (size_t i = 0; i < n_inputs; i++) { + input_buf_refs[i] = reinterpret_cast<const void *>(&input_buffers[i][0]); + } + for (size_t i = 0; i < n_outputs; i++) { + output_buf_refs[i] = reinterpret_cast<void *>(&output_buffers[i][0]); + } + + /// Final configurations to the converter: + std::cout << "Configuring converters:" << std::endl; + BOOST_FOREACH(priority_type prio_i, conv_list.keys()) { + std::cout << "* [" << prio_i << "]: "; + configure_conv(conv_list[prio_i], in_type, out_type); + } + + /// Run the benchmark for every converter //////////////////////////////// + std::cout << "{{{" << std::endl; + if (not debug_mode) { + std::cout << "prio,duration_ms,avg_duration_ms,n_samples,iterations" << std::endl; + BOOST_FOREACH(priority_type prio_i, conv_list.keys()) { + double duration = run_benchmark( + conv_list[prio_i], + input_buf_refs, + output_buf_refs, + n_samples, + iterations + ); + std::cout << boost::format("%i,%d,%d,%d,%d") + % prio_i + % (duration * 1000) + % (duration * 1000.0 / iterations) + % n_samples + % iterations + << std::endl; + } + } + + /// Or run debug mode, which runs one conversion and prints the results //// + if (debug_mode) { + // Only run on the first converter: + run_benchmark( + conv_list[conv_list.keys().at(0)], + input_buf_refs, + output_buf_refs, + n_samples, + iterations + ); + for (size_t i = 0; i < n_samples; i++) { + std::cout << item_to_string(input_buf_refs[0], i, in_type, vm.count("hex")) + << ";" + << item_to_string(reinterpret_cast< const void * >(output_buf_refs[0]), i, out_type, vm.count("hex")) + << std::endl; + } + } + std::cout << "}}}" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/host/utils/converter_benchmark.py b/host/utils/converter_benchmark.py new file mode 100644 index 000000000..c3cab8753 --- /dev/null +++ b/host/utils/converter_benchmark.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Wrap the converter_benchmark tool and produce prettier results. +""" + +from __future__ import print_function +import argparse +import csv +import subprocess + +INTRO_SETUP = { + 'n_samples': { + 'title': 'Samples per iteration', + }, + 'iterations': { + 'title': 'Number of iterations' + }, +} + +TABLE_SETUP = { + 'prio': { + 'title': 'Priority', + }, + 'duration_ms': { + 'title': 'Total Duration (ms)', + }, + 'avg_duration_ms': { + 'title': 'Avg. Duration (ms)', + }, +} + +def run_benchmark(args): + """ Run the tool with the given arguments, return the section in the {{{ }}} brackets """ + call_args = ['./converter_benchmark',] + for k, v in args.__dict__.iteritems(): + k = k.replace('_', '-') + if v is None: + continue + if k in ('debug-converter', 'hex'): + if v: + call_args.append('--{0}'.format(k)) + continue + call_args.append('--{0}'.format(k)) + call_args.append(str(v)) + print(call_args) + try: + output = subprocess.check_output(call_args) + except subprocess.CalledProcessError as ex: + print(ex.output) + exit(ex.returncode) + header_out, csv_output = output.split('{{{', 1) + csv_output = csv_output.split('}}}', 1) + assert len(csv_output) == 2 and csv_output[1].strip() == '' + return header_out, csv_output[0] + +def print_stats_table(args, csv_output): + """ + Print stats. + """ + reader = csv.reader(csv_output.strip().split('\n'), delimiter=',') + title_row = reader.next() + row_widths = [0,] * len(TABLE_SETUP) + for idx, row in enumerate(reader): + if idx == 0: + # Print intro: + for k, v in INTRO_SETUP.iteritems(): + print("{title}: {value}".format( + title=v['title'], + value=row[title_row.index(k)], + )) + print("") + # Print table header + for idx, item in enumerate(TABLE_SETUP): + print(" {title} ".format(title=TABLE_SETUP[item]['title']), end='') + row_widths[idx] = len(TABLE_SETUP[item]['title']) + if idx < len(TABLE_SETUP) - 1: + print("|", end='') + print("") + for idx, item in enumerate(TABLE_SETUP): + print("-" * (row_widths[idx] + 2), end='') + if idx < len(TABLE_SETUP) - 1: + print("+", end='') + print("") + # Print actual row data + for idx, item in enumerate(TABLE_SETUP): + format_str = " {{item:>{n}}} ".format(n=row_widths[idx]) + print(format_str.format(item=row[title_row.index(item)]), end='') + if idx < len(TABLE_SETUP) - 1: + print("|", end='') + print("") + +def print_debug_table(args, csv_output): + """ + Print debug output. + """ + reader = csv.reader(csv_output.strip().split('\n'), delimiter=';') + print_widths_hex = { + 'u8': 2, + 'sc16': 8, + 'fc32': 16, + 's16': 4, + } + if args.hex: + format_str = "{{0[0]:0>{n_in}}} => {{0[1]:0>{n_out}}}".format( + n_in=print_widths_hex[getattr(args, 'in').split('_', 1)[0]], + n_out=print_widths_hex[args.out.split('_', 1)[0]] + ) + else: + format_str = "{0[0]}\t=>\t{0[1]}" + for row in reader: + print(format_str.format(row)) + +def setup_argparse(): + """ Configure arg parser. """ + parser = argparse.ArgumentParser( + description="UHD Converter Benchmark + Debugging Utility.", + ) + parser.add_argument( + "-i", "--in", required=True, + help="Input format (e.g. 'sc16')" + ) + parser.add_argument( + "-o", "--out", required=True, + help="Output format (e.g. 'sc16')" + ) + parser.add_argument( + "-s", "--samples", type=int, + help="Number of samples per iteration" + ) + parser.add_argument( + "-N", "--iterations", type=int, + help="Number of iterations per benchmark", + ) + parser.add_argument( + "-p", "--priorities", + help="Converter priorities. Can be 'default', 'all', or a comma-separated list of priorities.", + ) + parser.add_argument( + "--max-prio", type=int, + help="Largest available priority (advanced feature)", + ) + parser.add_argument( + "--n-inputs", type=int, + help="Number of input vectors", + ) + parser.add_argument( + "--n-outputs", type=int, + help="Number of output vectors", + ) + parser.add_argument( + "--seed-mode", choices=('random', 'incremental'), + help="How to initialize the data: random, incremental", + ) + parser.add_argument( + "--debug-converter", action='store_true', + help="Skip benchmark and print conversion results. Implies iterations==1 and will only run on a single converter.", + ) + parser.add_argument( + "--hex", action='store_true', + help="In debug mode, display data as hex values.", + ) + return parser + +def main(): + """ Go, go, go! """ + args = setup_argparse().parse_args() + print("Running converter benchmark...") + header_out, csv_output = run_benchmark(args) + print(header_out) + if args.debug_converter: + print_debug_table(args, csv_output) + else: + print_stats_table(args, csv_output) + +if __name__ == "__main__": + main() + diff --git a/host/utils/fx2_init_eeprom.cpp b/host/utils/fx2_init_eeprom.cpp index 5711b73e0..cf7fb2de2 100644 --- a/host/utils/fx2_init_eeprom.cpp +++ b/host/utils/fx2_init_eeprom.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010,2014 Ettus Research LLC +// Copyright 2010,2014,2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,8 +20,17 @@ #include <uhd/property_tree.hpp> #include <boost/program_options.hpp> #include <boost/format.hpp> +#include <boost/algorithm/string/predicate.hpp> #include <iostream> -#include <cstdlib> +//#include <cstdlib> +#ifdef UHD_PLATFORM_LINUX +#include <fstream> +#include <unistd.h> // syscall constants +#include <fcntl.h> // O_NONBLOCK +#include <sys/syscall.h> +#include <cerrno> +#include <cstring> // for std::strerror +#endif //UHD_PLATFORM_LINUX const std::string FX2_VENDOR_ID("0x04b4"); const std::string FX2_PRODUCT_ID("0x8613"); @@ -49,10 +58,22 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ return EXIT_FAILURE; } - //cant find a uninitialized usrp with this mystery module in the way... - if (std::system("/sbin/rmmod usbtest") != 0){ - std::cerr << "Did not rmmod usbtest, this may be ok..." << std::endl; +#ifdef UHD_PLATFORM_LINUX + //can't find an uninitialized usrp with this mystery usbtest in the way... + std::string module("usbtest"); + std::ifstream modules("/proc/modules"); + bool module_found = false; + std::string module_line; + while(std::getline(modules, module_line) && (!module_found)) { + module_found = boost::starts_with(module_line, module); } + if(module_found) { + std::cout << boost::format("Found the '%s' module. Unloading it.\n" ) % module; + int fail = syscall(__NR_delete_module, module.c_str(), O_NONBLOCK); + if(fail) + std::cerr << ( boost::format("Removing the '%s' module failed with error '%s'.\n") % module % std::strerror(errno) ); + } +#endif //UHD_PLATFORM_LINUX //load the options into the address uhd::device_addr_t device_addr; diff --git a/host/utils/uhd_config_info.cpp b/host/utils/uhd_config_info.cpp new file mode 100644 index 000000000..78fcb201b --- /dev/null +++ b/host/utils/uhd_config_info.cpp @@ -0,0 +1,99 @@ +// +// Copyright 2015-2016 National Instruments Corp. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/build_info.hpp> +#include <uhd/version.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/safe_main.hpp> + +#include <boost/format.hpp> +#include <boost/program_options.hpp> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char* argv[]) { + // Program Options + po::options_description desc("Allowed Options"); + desc.add_options() + ("build-date", "Print build date") + ("c-compiler", "Print C compiler") + ("cxx-compiler", "Print C++ compiler") + ("c-flags", "Print C compiler flags") + ("cxx-flags", "Print C++ compiler flags") + ("enabled-components", "Print built-time enabled components") + ("install-prefix", "Print install prefix") + ("boost-version", "Print Boost version") + ("libusb-version", "Print libusb version") + ("pkg-path", "Print pkg path") + ("images-dir", "Print images dir") + ("print-all", "Print everything") + ("version", "Print this UHD build's version") + ("help", "Print help message") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + // Print the help message + if(vm.count("help") > 0) { + std::cout << boost::format("UHD Config Info - %s") % desc << std::endl; + return EXIT_FAILURE; + } + + bool print_all = (vm.count("print-all") > 0); + + if(vm.count("version") > 0 or print_all) { + std::cout << "UHD " << uhd::get_version_string() << std::endl; + } + if(vm.count("build-date") > 0 or print_all) { + std::cout << "Build date: " << uhd::build_info::build_date() << std::endl; + } + if(vm.count("c-compiler") > 0 or print_all) { + std::cout << "C compiler: " << uhd::build_info::c_compiler() << std::endl; + } + if(vm.count("cxx-compiler") > 0 or print_all) { + std::cout << "C++ compiler: " << uhd::build_info::cxx_compiler() << std::endl; + } + if(vm.count("c-flags") > 0 or print_all) { + std::cout << "C flags: " << uhd::build_info::c_flags() << std::endl; + } + if(vm.count("cxx-flags") > 0 or print_all) { + std::cout << "C++ flags: " << uhd::build_info::cxx_flags() << std::endl; + } + if(vm.count("enabled-components") > 0 or print_all) { + std::cout << "Enabled components: " << uhd::build_info::enabled_components() << std::endl; + } + if(vm.count("install-prefix") > 0 or print_all) { + std::cout << "Install prefix: " << uhd::build_info::install_prefix() << std::endl; + } + if(vm.count("boost-version") > 0 or print_all) { + std::cout << "Boost version: " << uhd::build_info::boost_version() << std::endl; + } + if(vm.count("libusb-version") > 0 or print_all) { + std::string _libusb_version = uhd::build_info::libusb_version(); + std::cout << "Libusb version: " << (_libusb_version.empty() ? "N/A" : _libusb_version) << std::endl; + } + if(vm.count("pkg-path") > 0 or print_all) { + std::cout << "Package path: " << uhd::get_pkg_path() << std::endl; + } + if(vm.count("images-dir") > 0 or print_all) { + std::cout << "Images directory: " << uhd::get_images_dir("") << std::endl; + } + + return EXIT_SUCCESS; +} diff --git a/host/utils/uhd_usrp_probe.cpp b/host/utils/uhd_usrp_probe.cpp index ae86b0dba..49e00d53a 100644 --- a/host/utils/uhd_usrp_probe.cpp +++ b/host/utils/uhd_usrp_probe.cpp @@ -18,12 +18,14 @@ #include <uhd/utils/safe_main.hpp> #include <uhd/version.hpp> #include <uhd/device.hpp> +#include <uhd/device3.hpp> #include <uhd/types/ranges.hpp> #include <uhd/property_tree.hpp> #include <boost/algorithm/string.hpp> //for split #include <uhd/usrp/dboard_id.hpp> #include <uhd/usrp/mboard_eeprom.hpp> #include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/types/sensors.hpp> #include <boost/program_options.hpp> #include <boost/format.hpp> #include <boost/foreach.hpp> @@ -52,7 +54,7 @@ static std::string make_border(const std::string &text){ static std::string get_dsp_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ std::stringstream ss; ss << boost::format("%s DSP: %s") % type % path.leaf() << std::endl; - //ss << std::endl; + ss << std::endl; meta_range_t freq_range = tree->access<meta_range_t>(path / "freq/range").get(); ss << boost::format("Freq range: %.3f to %.3f MHz") % (freq_range.start()/1e6) % (freq_range.stop()/1e6) << std::endl;; return ss.str(); @@ -134,6 +136,16 @@ static std::string get_dboard_pp_string(const std::string &type, property_tree:: return ss.str(); } + +static std::string get_rfnoc_pp_string(property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << "RFNoC blocks on this device:" << std::endl << std::endl; + BOOST_FOREACH(const std::string &name, tree->list(path)){ + ss << "* " << name << std::endl; + } + return ss.str(); +} + static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path &path){ std::stringstream ss; ss << boost::format("Mboard: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; @@ -148,6 +160,9 @@ static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path if (tree->exists(path / "fpga_version")){ ss << "FPGA Version: " << tree->access<std::string>(path / "fpga_version").get() << std::endl; } + if (tree->exists(path / "xbar")){ + ss << "RFNoC capable: Yes" << std::endl; + } ss << std::endl; try { if (tree->exists(path / "time_source" / "options")){ @@ -159,18 +174,23 @@ static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path ss << "Clock sources: " << prop_names_to_pp_string(clock_sources) << std::endl; } ss << "Sensors: " << prop_names_to_pp_string(tree->list(path / "sensors")) << std::endl; - BOOST_FOREACH(const std::string &name, tree->list(path / "rx_dsps")){ - ss << make_border(get_dsp_pp_string("RX", tree, path / "rx_dsps" / name)); + if (tree->exists(path / "rx_dsps")){ + BOOST_FOREACH(const std::string &name, tree->list(path / "rx_dsps")){ + ss << make_border(get_dsp_pp_string("RX", tree, path / "rx_dsps" / name)); + } } BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ ss << make_border(get_dboard_pp_string("RX", tree, path / "dboards" / name)); } - BOOST_FOREACH(const std::string &name, tree->list(path / "tx_dsps")){ - ss << make_border(get_dsp_pp_string("TX", tree, path / "tx_dsps" / name)); + if (tree->exists(path / "tx_dsps")){ + BOOST_FOREACH(const std::string &name, tree->list(path / "tx_dsps")){ + ss << make_border(get_dsp_pp_string("TX", tree, path / "tx_dsps" / name)); + } } BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ ss << make_border(get_dboard_pp_string("TX", tree, path / "dboards" / name)); } + ss << make_border(get_rfnoc_pp_string(tree, path / "xbar")); } catch (const uhd::lookup_error&) { /* nop */ @@ -206,6 +226,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("string", po::value<std::string>(), "query a string value from the property tree") ("double", po::value<std::string>(), "query a double precision floating point value from the property tree") ("int", po::value<std::string>(), "query a integer value from the property tree") + ("sensor", po::value<std::string>(), "query a sensor value from the property tree") ("range", po::value<std::string>(), "query a range (gain, bandwidth, frequency, ...) from the property tree") ("init-only", "skip all queries, only initialize device") ; @@ -243,6 +264,11 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ return EXIT_SUCCESS; } + if (vm.count("sensor")){ + std::cout << tree->access<uhd::sensor_value_t>(vm["sensor"].as<std::string>()).get().value << std::endl; + return EXIT_SUCCESS; + } + if (vm.count("range")){ meta_range_t range = tree->access<meta_range_t>(vm["range"].as<std::string>()).get(); std::cout << boost::format("%.1f:%.1f:%.1f") % range.start() % range.step() % range.stop() << std::endl; diff --git a/images/CMakeLists.txt b/images/CMakeLists.txt index 161b5130a..ab2ac8147 100644 --- a/images/CMakeLists.txt +++ b/images/CMakeLists.txt @@ -18,7 +18,12 @@ ######################################################################## # Setup Project ######################################################################## -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +#OPTION(UHD_PATCH_OVERRIDE "Use this string to force a patch level version string." OFF) +MESSAGE(STATUS ${UHD_PATCH_OVERRIDE}) +IF(DEFINED UHD_PATCH_OVERRIDE) + SET(UHD_VERSION_PATCH_OVERRIDE ${UHD_PATCH_OVERRIDE}) +ENDIF(DEFINED UHD_PATCH_OVERRIDE) +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) PROJECT(UHD-images NONE) LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/../host/cmake/Modules) INCLUDE(UHDVersion) #sets version variables (used below) @@ -41,6 +46,6 @@ MESSAGE(STATUS "Version: ${UHD_VERSION}") ######################################################################## #tag the images with a version number (something identifiable) -FILE(WRITE ${CMAKE_SOURCE_DIR}/images/${UHD_VERSION_MAJOR}.${UHD_VERSION_MINOR}.${UHD_VERSION_PATCH}.tag "${UHD_VERSION}\n${DATETIME_NOW}\n") +FILE(WRITE ${CMAKE_SOURCE_DIR}/images/${UHD_VERSION_MAJOR}.${UHD_VERSION_API}.${UHD_VERSION_ABI}.${UHD_VERSION_PATCH}.tag "${UHD_VERSION}\n${DATETIME_NOW}\n") INSTALL(DIRECTORY ${CMAKE_SOURCE_DIR}/images DESTINATION share/uhd) INSTALL(FILES ${CMAKE_SOURCE_DIR}/../host/LICENSE DESTINATION share/uhd/images) diff --git a/images/create_imgs_package.py b/images/create_imgs_package.py index bdd4e7644..82b7d65d4 100755 --- a/images/create_imgs_package.py +++ b/images/create_imgs_package.py @@ -47,12 +47,13 @@ def parse_args(): parser = argparse.ArgumentParser(description='Link the current set of images to this commit.') parser.add_argument('--commit', default=None, help='Supply a commit message to the changes to host/CMakeLists.txt.') - parser.add_argument('-r', '--release-mode', default=None, + parser.add_argument('-r', '--release-mode', default="", help='Specify UHD_RELEASE_MODE. Typically "release" or "rc1" or similar.') parser.add_argument('--skip-edit', default=False, action='store_true', help='Do not edit the CMakeLists.txt file.') parser.add_argument('--skip-move', default=False, action='store_true', help='Do not move the archives after creating them.') + parser.add_argument('--patch', help='Override patch version number.') return parser.parse_args() def move_zip_to_repo(base_url, zipfilename): @@ -75,8 +76,9 @@ def main(): clear_img_dir(img_root_dir) print "== Creating archives..." cpack_cmd = ["./make_zip.sh",] - if args.release_mode is not None: - cpack_cmd.append(args.release_mode) + cpack_cmd.append(args.release_mode) + if args.patch is not None: + cpack_cmd.append("-DUHD_PATCH_OVERRIDE={}".format(args.patch)) try: cpack_output = subprocess.check_output(cpack_cmd) except subprocess.CalledProcessError as e: diff --git a/images/make_zip.sh b/images/make_zip.sh index 19695ef3f..e9ef802f7 100755 --- a/images/make_zip.sh +++ b/images/make_zip.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Automatically run the make-zip-file process # Check we're in the right directory and all is set: if [ ! -e 'make_zip.sh' ]; then @@ -28,12 +28,12 @@ mkdir build cd build # Run the CPack process (ZIP file) -cmake .. -DCPACK_GENERATOR=ZIP -DUHD_RELEASE_MODE="$1" .. +cmake .. -DCPACK_GENERATOR=ZIP -DUHD_RELEASE_MODE="$1" $2 .. make package mv uhd-images*.zip .. # Run the CPack process (tarball) -cmake .. -DCPACK_GENERATOR=TGZ -DUHD_RELEASE_MODE="$1" .. +cmake .. -DCPACK_GENERATOR=TGZ -DUHD_RELEASE_MODE="$1" $2 .. make package mv uhd-images*.tar.gz .. diff --git a/tools/chdr-dissector/packet-chdr.c b/tools/chdr-dissector/packet-chdr.c index cce46bb84..079e6bb3b 100644 --- a/tools/chdr-dissector/packet-chdr.c +++ b/tools/chdr-dissector/packet-chdr.c @@ -1,5 +1,5 @@ /* - * Dissector for UHD CHDR packets + * Dissector for UHD CVITA (CHDR) packets * * Copyright 2010-2014 Ettus Research LLC * @@ -37,22 +37,45 @@ const unsigned int CHDR_PORT = X300_VITA_UDP_PORT; static int proto_chdr = -1; static int hf_chdr_hdr = -1; -static int hf_chdr_is_extension = -1; -static int hf_chdr_reserved = -1; +static int hf_chdr_type = -1; static int hf_chdr_has_time = -1; static int hf_chdr_eob = -1; +static int hf_chdr_error = -1; static int hf_chdr_sequence = -1; static int hf_chdr_packet_size = -1; static int hf_chdr_stream_id = -1; static int hf_chdr_src_dev = -1; static int hf_chdr_src_ep = -1; +static int hf_chdr_src_blockport = -1; static int hf_chdr_dst_dev = -1; static int hf_chdr_dst_ep = -1; +static int hf_chdr_dst_blockport = -1; static int hf_chdr_timestamp = -1; static int hf_chdr_payload = -1; static int hf_chdr_ext_response = -1; static int hf_chdr_ext_status_code = -1; static int hf_chdr_ext_seq_num = -1; +static int hf_chdr_cmd = -1; +static int hf_chdr_cmd_address = -1; +static int hf_chdr_cmd_value = -1; + +static const value_string CHDR_PACKET_TYPES[] = { + { 0, "Data" }, + { 1, "Data (End-of-Burst)" }, + { 4, "Flow Control" }, + { 8, "Command" }, + { 12, "Response" }, + { 13, "Error Response" }, +}; + +static const value_string CHDR_PACKET_TYPES_SHORT[] = { + { 0, "data" }, + { 1, "data" }, + { 4, "fc" }, + { 8, "cmd" }, + { 12, "resp" }, + { 13, "resp" }, +}; /* the heuristic dissector is called on every packet with payload. * The warning printed for this should only be printed once. @@ -64,6 +87,7 @@ static gint ett_chdr = -1; static gint ett_chdr_header = -1; static gint ett_chdr_id = -1; static gint ett_chdr_response = -1; +static gint ett_chdr_cmd = -1; /* Forward-declare the dissector functions */ void proto_register_chdr(void); @@ -71,7 +95,7 @@ void proto_reg_handoff_chdr(void); static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree); /* heuristic dissector call. Will always return. */ -static gboolean heur_dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +static gboolean heur_dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* whatislove) { if(heur_warning_printed < 1){ printf(LOG_HEADER"heuristic dissector always returns true!\n"); @@ -132,11 +156,21 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) proto_tree *stream_tree; proto_item *response_item; proto_tree *response_tree; + proto_item *cmd_item; + proto_tree *cmd_tree; gint len; gint flag_offset; guint8 *bytes; - gboolean flag_has_time; + guint8 hdr_bits = 0; + guint8 pkt_type = 0; + gboolean flag_has_time = 0; + gboolean flag_is_data = 0; + gboolean flag_is_fc = 0; + gboolean flag_is_cmd = 0; + gboolean flag_is_resp = 0; + gboolean flag_is_eob = 0; + gboolean flag_is_error = 0; unsigned long long timestamp; gboolean is_network; gint endianness; @@ -169,8 +203,16 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) if (len >= 4){ chdr_size = 8; - bytes = tvb_get_string(tvb, 0, 4); - flag_has_time = bytes[flag_offset] & 0x20; + bytes = tvb_get_string(wmem_packet_scope(), tvb, 0, 4); + hdr_bits = (bytes[flag_offset] & 0xF0) >> 4; + pkt_type = hdr_bits >> 2; + flag_is_data = (pkt_type == 0); + flag_is_fc = (pkt_type == 1); + flag_is_cmd = (pkt_type == 2); + flag_is_resp = (pkt_type == 3); + flag_is_eob = flag_is_data && (hdr_bits & 0x1); + flag_is_error = flag_is_resp && (hdr_bits & 0x1); + flag_has_time = hdr_bits & 0x2; if (flag_has_time) chdr_size += 8; // 64-bit timestamp } @@ -178,17 +220,28 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) /* Start with a top-level item to add everything else to */ item = proto_tree_add_item(tree, proto_chdr, tvb, 0, min(len, chdr_size), ENC_NA); - if (len >= 4){ + if (len >= 4) { chdr_tree = proto_item_add_subtree(item, ett_chdr); + /* Header info. First, a top-level header tree item: */ header_item = proto_tree_add_item(chdr_tree, hf_chdr_hdr, tvb, flag_offset, 1, endianness); header_tree = proto_item_add_subtree(header_item, ett_chdr_header); - - /* These lines add flag info to tree */ - proto_tree_add_item(header_tree, hf_chdr_is_extension, tvb, flag_offset, 1, ENC_NA); - proto_tree_add_item(header_tree, hf_chdr_reserved, tvb, flag_offset, 1, ENC_NA); - proto_tree_add_item(header_tree, hf_chdr_has_time, tvb, flag_offset, 1, ENC_NA); - proto_tree_add_item(header_tree, hf_chdr_eob, tvb, flag_offset, 1, ENC_NA); + proto_item_append_text(header_item, ", Packet type: %s", + val_to_str(hdr_bits & 0xD, CHDR_PACKET_TYPES, "Unknown (0x%x)") + ); + /* Let us query hdr.type */ + proto_tree_add_string( + header_tree, hf_chdr_type, tvb, flag_offset, 1, + val_to_str(hdr_bits & 0xD, CHDR_PACKET_TYPES_SHORT, "invalid") + ); + /* And other flags */ + proto_tree_add_boolean(header_tree, hf_chdr_has_time, tvb, flag_offset, 1, flag_has_time); + if (flag_is_data) { + proto_tree_add_boolean(header_tree, hf_chdr_eob, tvb, flag_offset, 1, flag_is_eob); + } + if (flag_is_resp) { + proto_tree_add_boolean(header_tree, hf_chdr_error, tvb, flag_offset, 1, flag_is_error); + } /* These lines add sequence, packet_size and stream ID */ proto_tree_add_item(chdr_tree, hf_chdr_sequence, tvb, (is_network ? 0:2), 2, endianness); @@ -199,16 +252,33 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) stream_item = proto_tree_add_item(chdr_tree, hf_chdr_stream_id, tvb, 4, 4, endianness); stream_tree = proto_item_add_subtree(stream_item, ett_chdr_id); proto_tree_add_item(stream_tree, hf_chdr_src_dev, tvb, id_pos[0], 1, ENC_NA); - proto_tree_add_item(stream_tree, hf_chdr_src_ep, tvb, id_pos[1], 1, ENC_NA); + proto_tree_add_item(stream_tree, hf_chdr_src_ep, tvb, id_pos[1], 1, ENC_NA); proto_tree_add_item(stream_tree, hf_chdr_dst_dev, tvb, id_pos[2], 1, ENC_NA); - proto_tree_add_item(stream_tree, hf_chdr_dst_ep, tvb, id_pos[3], 1, ENC_NA); + proto_tree_add_item(stream_tree, hf_chdr_dst_ep, tvb, id_pos[3], 1, ENC_NA); + + /* Block ports (only add them if address points to a device) */ + bytes = tvb_get_string(wmem_packet_scope(), tvb, 0, 8); + if (bytes[id_pos[0]] != 0) { + proto_tree_add_item(stream_tree, hf_chdr_src_blockport, tvb, id_pos[1], 1, ENC_NA); + } + if (bytes[id_pos[2]] != 0) { + proto_tree_add_item(stream_tree, hf_chdr_dst_blockport, tvb, id_pos[3], 1, ENC_NA); + } + + /* Append SID in sid_t hex format */ + proto_item_append_text(stream_item, " (%02X:%02X>%02X:%02X)", + bytes[id_pos[0]], + bytes[id_pos[1]], + bytes[id_pos[2]], + bytes[id_pos[3]] + ); /* if has_time flag is present interpret timestamp */ if ((flag_has_time) && (len >= 16)){ if (is_network) item = proto_tree_add_item(chdr_tree, hf_chdr_timestamp, tvb, 8, 8, endianness); else{ - bytes = (guint8*) tvb_get_string(tvb, 8, sizeof(unsigned long long)); + bytes = (guint8*) tvb_get_string(wmem_packet_scope(), tvb, 8, sizeof(unsigned long long)); timestamp = get_timestamp(bytes, sizeof(unsigned long long)); proto_tree_add_uint64(chdr_tree, hf_chdr_timestamp, tvb, 8, 8, timestamp); } @@ -217,19 +287,21 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) int remaining_bytes = (len - chdr_size); int show_raw_payload = (remaining_bytes > 0); - if (hf_chdr_is_extension){ - if (remaining_bytes == 8){ // Interpret this as a response packet - response_item = proto_tree_add_item(chdr_tree, hf_chdr_ext_response, tvb, chdr_size, 8, endianness); - response_tree = proto_item_add_subtree(response_item, ett_chdr_response); - - proto_tree_add_item(response_tree, hf_chdr_ext_status_code, tvb, chdr_size, 4, endianness); - /* This will show the 12-bits of sequence ID in the last 2 bytes */ - proto_tree_add_item(response_tree, hf_chdr_ext_seq_num, tvb, (chdr_size + 4 + (is_network ? 2 : 0)), 2, endianness); - } - } - - if (show_raw_payload) + if (flag_is_cmd && remaining_bytes == 8) { + cmd_item = proto_tree_add_item(chdr_tree, hf_chdr_cmd, tvb, chdr_size, 8, endianness); + cmd_tree = proto_item_add_subtree(cmd_item, ett_chdr_cmd); + proto_tree_add_item(cmd_tree, hf_chdr_cmd_address, tvb, chdr_size, 4, endianness); + proto_tree_add_item(cmd_tree, hf_chdr_cmd_value, tvb, chdr_size + 4, 4, endianness); + } else if (flag_is_resp) { + response_item = proto_tree_add_item(chdr_tree, hf_chdr_ext_response, tvb, chdr_size, 8, endianness); + response_tree = proto_item_add_subtree(response_item, ett_chdr_response); + + proto_tree_add_item(response_tree, hf_chdr_ext_status_code, tvb, chdr_size, 4, endianness); + /* This will show the 12-bits of sequence ID in the last 2 bytes */ + proto_tree_add_item(response_tree, hf_chdr_ext_seq_num, tvb, (chdr_size + 4 + (is_network ? 2 : 0)), 2, endianness); + } else if (show_raw_payload) { proto_tree_add_item(chdr_tree, hf_chdr_payload, tvb, chdr_size, -1, ENC_NA); + } } } } @@ -244,17 +316,11 @@ void proto_register_chdr(void) NULL, 0xF0, NULL, HFILL } }, - { &hf_chdr_is_extension, - { "Extension context packet", "chdr.hdr.ext", - FT_BOOLEAN, BASE_NONE, - NULL, 0x80, - NULL, HFILL } - }, - { &hf_chdr_reserved, - { "Reserved bit", "chdr.hdr.reserved", - FT_BOOLEAN, BASE_NONE, - NULL, 0x40, - NULL, HFILL } + { &hf_chdr_type, + { "Packet Type", "chdr.hdr.type", + FT_STRINGZ, BASE_NONE, + NULL, 0x00, + "Packet Type", HFILL } }, { &hf_chdr_has_time, { "Time present", "chdr.hdr.has_time", @@ -268,6 +334,12 @@ void proto_register_chdr(void) NULL, 0x10, NULL, HFILL } }, + { &hf_chdr_error, + { "Error Flag", "chdr.hdr.error", + FT_BOOLEAN, BASE_NONE, + NULL, 0x10, + NULL, HFILL } + }, { &hf_chdr_sequence, { "Sequence ID", "chdr.seq", FT_UINT16, BASE_DEC, @@ -298,6 +370,12 @@ void proto_register_chdr(void) NULL, 0x0, NULL, HFILL } }, + { &hf_chdr_src_blockport, + { "Source block port", "chdr.src_bp", + FT_UINT8, BASE_DEC, + NULL, 0xF, + NULL, HFILL } + }, { &hf_chdr_dst_dev, { "Destination device", "chdr.dst_dev", FT_UINT8, BASE_DEC, @@ -310,6 +388,12 @@ void proto_register_chdr(void) NULL, 0x0, NULL, HFILL } }, + { &hf_chdr_dst_blockport, + { "Destination block port", "chdr.dst_bp", + FT_UINT8, BASE_DEC, + NULL, 0xF, + NULL, HFILL } + }, { &hf_chdr_timestamp, { "Time", "chdr.time", FT_UINT64, BASE_DEC, @@ -341,13 +425,32 @@ void proto_register_chdr(void) NULL, 0x0FFF, NULL, HFILL } }, + { &hf_chdr_cmd, + { "Command", "chdr.cmd", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_chdr_cmd_address, + { "Register Address", "chdr.cmd.addr", + FT_UINT32, BASE_DEC, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_chdr_cmd_value, + { "Command Value", "chdr.cmd.val", + FT_UINT32, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, }; static gint *ett[] = { &ett_chdr, &ett_chdr_header, &ett_chdr_id, - &ett_chdr_response + &ett_chdr_response, + &ett_chdr_cmd }; proto_chdr = proto_register_protocol("UHD CHDR", "CHDR", "chdr"); diff --git a/tools/debs/upload_debs.sh b/tools/debs/upload_debs.sh index c14096997..831eeb248 100755 --- a/tools/debs/upload_debs.sh +++ b/tools/debs/upload_debs.sh @@ -45,7 +45,7 @@ ORIG_RELEASE=`head -1 host/cmake/debian/changelog | sed 's/.*) \(.*\);.*/\1/'` # Currently supported versions can be found here: # https://launchpad.net/ubuntu/+ppas # -RELEASES="precise trusty vivid wily xenial" +RELEASES="trusty vivid wily xenial" PPA=ppa:ettusresearch/uhd # @@ -67,7 +67,7 @@ fi # Generate the TAR file to be uploaded. echo "Creating UHD source archive." -tar --exclude='*git*' --exclude='./debian' --exclude='*.swp' --exclude='fpga-src' --exclude='build' --exclude='./images/*.pyc' --exclude='./images/uhd-*' --exclude='tags' -cJf ../uhd_${VERSION}.orig.tar.xz . +tar --exclude='.git*' --exclude='./debian' --exclude='*.swp' --exclude='fpga-src' --exclude='build' --exclude='./images/*.pyc' --exclude='./images/uhd-*' --exclude='tags' -cJf ../uhd_${VERSION}.orig.tar.xz . if [ $? != 0 ] then echo "Failed to create UHD source archive." |