diff --git a/build_examples.sh b/build_examples.sh index 4a6e0311c..c053c0cef 100644 --- a/build_examples.sh +++ b/build_examples.sh @@ -12,4 +12,5 @@ make CONFIG=examples/red-pitaya/laser-controller/config.yml MODE=$mode $target make CONFIG=examples/red-pitaya/pulse-generator/config.yml MODE=$mode $target make CONFIG=examples/red-pitaya/adc-dac/config.yml MODE=$mode $target make CONFIG=examples/red-pitaya/cluster/config.yml MODE=$mode $target -make CONFIG=examples/red-pitaya/dual-dds/config.yml MODE=$mode $target \ No newline at end of file +make CONFIG=examples/red-pitaya/dual-dds/config.yml MODE=$mode $target +make CONFIG=examples/red-pitaya/fft/config.yml MODE=$mode $target \ No newline at end of file diff --git a/examples/alpha250/fft/config.yml b/examples/alpha250/fft/config.yml index a13fe6d8e..75668f145 100644 --- a/examples/alpha250/fft/config.yml +++ b/examples/alpha250/fft/config.yml @@ -96,4 +96,3 @@ web: - ./web/clock-generator.ts - ./web/index.html - ./web/main.css - diff --git a/examples/red-pitaya/fft/block_design.tcl b/examples/red-pitaya/fft/block_design.tcl index f99a7365c..f22323432 100644 --- a/examples/red-pitaya/fft/block_design.tcl +++ b/examples/red-pitaya/fft/block_design.tcl @@ -1,48 +1,13 @@ -source $project_path/tcl/ports.tcl +source $board_path/config/ports.tcl +source $board_path/base_system.tcl -# Add PS and AXI Interconnect -set board_preset $board_path/config/board_preset.tcl -source $sdk_path/fpga/lib/starting_point.tcl +source $sdk_path/fpga/lib/laser_controller.tcl -# Add ADCs and DACs -source $sdk_path/fpga/lib/redp_adc_dac.tcl -set adc_dac_name adc_dac -add_redp_adc_dac $adc_dac_name - -# Rename clocks -set adc_clk $adc_dac_name/adc_clk - -# Add processor system reset synchronous to adc clock -set rst_adc_clk_name proc_sys_reset_adc_clk -cell xilinx.com:ip:proc_sys_reset:5.0 $rst_adc_clk_name {} { - ext_reset_in $ps_name/FCLK_RESET0_N - slowest_sync_clk $adc_clk -} - -# Add config and status registers -source $sdk_path/fpga/lib/ctl_sts.tcl -add_ctl_sts $adc_clk $rst_adc_clk_name/peripheral_aresetn - -# Connect LEDs -connect_port_pin led_o [get_slice_pin [ctl_pin led] 7 0] - -# Connect ADC to status register -for {set i 0} {$i < [get_parameter n_adc]} {incr i} { - connect_pins [sts_pin adc$i] adc_dac/adc[expr $i + 1] -} - -# Add XADC for monitoring of Zynq temperature - -create_bd_intf_port -mode Slave -vlnv xilinx.com:interface:diff_analog_io_rtl:1.0 Vp_Vn - -cell xilinx.com:ip:xadc_wiz:3.3 xadc_wiz_0 { -} { - Vp_Vn Vp_Vn - s_axi_lite axi_mem_intercon_0/M[add_master_interface 0]_AXI - s_axi_aclk ps_0/FCLK_CLK0 - s_axi_aresetn proc_sys_reset_0/peripheral_aresetn +# Connect raw ADC data to status register +connect_cell adc_dac { + adc1 [sts_pin adc0] + adc2 [sts_pin adc1] } -assign_bd_address [get_bd_addr_segs xadc_wiz_0/s_axi_lite/Reg] #################################### # Direct Digital Synthesis @@ -55,20 +20,21 @@ for {set i 0} {$i < 2} {incr i} { DDS_Clock_Rate [expr [get_parameter adc_clk] / 1000000] Parameter_Entry Hardware_Parameters Phase_Width 32 - Output_Width 14 + Output_Width 16 Phase_Increment Programmable + Latency_Configuration Configurable + Latency 9 } { aclk adc_dac/adc_clk } - connect_pins adc_dac/dac[expr $i+1] [get_slice_pin dds$i/m_axis_data_tdata 13 0] + connect_pins adc_dac/dac[expr $i+1] [get_slice_pin dds$i/m_axis_data_tdata 15 2] - cell pavel-demin:user:axis_variable:1.0 phase_increment$i { + cell pavel-demin:user:axis_constant:1.0 phase_increment$i { AXIS_TDATA_WIDTH 32 } { cfg_data [ctl_pin phase_incr$i] aclk adc_dac/adc_clk - aresetn $rst_adc_clk_name/peripheral_aresetn M_AXIS dds$i/S_AXIS_CONFIG } @@ -82,12 +48,12 @@ source $project_path/tcl/power_spectral_density.tcl source $sdk_path/fpga/modules/bram_accumulator/bram_accumulator.tcl source $sdk_path/fpga/lib/bram_recorder.tcl -power_spectral_density::create psd [get_parameter fft_size] [get_parameter adc_width] +power_spectral_density::create psd [get_parameter fft_size] cell koheron:user:latched_mux:1.0 mux_psd { N_INPUTS 2 SEL_WIDTH 1 - WIDTH 14 + WIDTH [get_parameter adc_width] } { clk adc_dac/adc_clk clken [get_constant_pin 1 1] @@ -96,11 +62,9 @@ cell koheron:user:latched_mux:1.0 mux_psd { } connect_cell psd { - adc1 mux_psd/dout - adc2 [get_constant_pin 0 [expr [get_parameter adc_width] - 1]] + data mux_psd/dout clk adc_dac/adc_clk tvalid [ctl_pin psd_valid] - ctl_sub [ctl_pin substract_mean] ctl_fft [ctl_pin ctl_fft] } @@ -132,8 +96,57 @@ connect_cell bram_accum { add_bram_recorder psd_bram psd connect_cell psd_bram { clk adc_dac/adc_clk - rst $rst_adc_clk_name/peripheral_reset + rst proc_sys_reset_adc_clk/peripheral_reset addr bram_accum/addr_out wen bram_accum/wen adc bram_accum/m_axis_tdata } + +#################################### +# Demodulation +#################################### + +source $project_path/tcl/demodulator.tcl + +demodulator::create demodulator + +connect_cell demodulator { + s_axis_data_a [get_concat_pin [list [get_constant_pin 0 2] adc_dac/adc1 [get_constant_pin 0 16]]] + s_axis_data_b dds0/m_axis_data_tdata + s_axis_tvalid dds0/m_axis_data_tvalid + aclk adc_dac/adc_clk + aresetn proc_sys_reset_adc_clk/peripheral_aresetn +} + +# Use AXI Stream clock converter (ADC clock -> FPGA clock) +set idx [add_master_interface 0] + +cell xilinx.com:ip:axis_clock_converter:1.1 adc_clock_converter { + TDATA_NUM_BYTES 4 +} { + s_axis_tdata demodulator/m_axis_tdata + s_axis_tvalid demodulator/m_axis_tvalid + s_axis_aresetn proc_sys_reset_adc_clk/peripheral_aresetn + m_axis_aresetn proc_sys_reset_0/peripheral_aresetn + s_axis_aclk adc_dac/adc_clk + m_axis_aclk ps_0/FCLK_CLK0 +} + +# Add AXI stream FIFO +cell xilinx.com:ip:axi_fifo_mm_s:4.1 adc_axis_fifo { + C_USE_TX_DATA 0 + C_USE_TX_CTRL 0 + C_USE_RX_CUT_THROUGH true + C_RX_FIFO_DEPTH 8192 + C_RX_FIFO_PF_THRESHOLD 4096 +} { + s_axi_aclk ps_0/FCLK_CLK0 + s_axi_aresetn proc_sys_reset_0/peripheral_aresetn + S_AXI axi_mem_intercon_0/M${idx}_AXI + AXI_STR_RXD adc_clock_converter/M_AXIS +} + +assign_bd_address [get_bd_addr_segs adc_axis_fifo/S_AXI/Mem0] +set memory_segment [get_bd_addr_segs /ps_0/Data/SEG_adc_axis_fifo_Mem0] +set_property offset [get_memory_offset adc_fifo] $memory_segment +set_property range [get_memory_range adc_fifo] $memory_segment diff --git a/examples/red-pitaya/fft/config.yml b/examples/red-pitaya/fft/config.yml index a6a079233..ea31205f1 100644 --- a/examples/red-pitaya/fft/config.yml +++ b/examples/red-pitaya/fft/config.yml @@ -3,14 +3,19 @@ name: fft board: boards/red-pitaya cores: - - fpga/cores/redp_adc_v1_0 - - fpga/cores/redp_dac_v1_0 - fpga/cores/axi_ctl_register_v1_0 - fpga/cores/axi_sts_register_v1_0 - fpga/cores/dna_reader_v1_0 - fpga/cores/latched_mux_v1_0 - - fpga/cores/axis_variable_v1_0 + - fpga/cores/axis_constant_v1_0 + - fpga/cores/redp_adc_v1_0 + - fpga/cores/redp_dac_v1_0 - fpga/cores/psd_counter_v1_0 + - fpga/cores/comparator_v1_0 + - fpga/cores/saturation_v1_0 + - fpga/cores/pdm_v1_0 + - fpga/cores/at93c46d_spi_v1_0 + - fpga/cores/bus_multiplexer_v1_0 memory: - name: control @@ -24,46 +29,66 @@ memory: range: 64K - name: demod offset: '0x60000000' - range: 16K + range: 8K - name: psd offset: '0x70000000' - range: 16K + range: 8K + - name: adc_fifo + offset: '0x43C10000' + range: 32K control_registers: - led - - substract_mean + - mmcm - ctl_fft - psd_valid - psd_input_sel - phase_incr[2] + - laser_current + - laser_control + - power_setpoint + - eeprom_ctl status_registers: - adc[n_adc] - cycle_index + - eeprom_sts + - pid_control parameters: - fclk0: 200000000 + fclk0: 187500000 adc_clk: 125000000 dac_width: 14 adc_width: 14 + pwm_width: 12 n_adc: 2 fft_size: 2048 - n_cycles: 2048 + n_cycles: 1023 + cic_differential_delay: 1 + cic_decimation_rate: 250 + cic_n_stages: 6 xdc: - - ./xdc/ports.xdc - - ./xdc/clocks.xdc + - boards/red-pitaya/config/ports.xdc + - boards/red-pitaya/config/clocks.xdc + - ./expansion_connector.xdc drivers: - server/drivers/common.hpp - - ./fft.hpp + - server/drivers/xadc.hpp + - server/drivers/laser.hpp + - server/drivers/eeprom.hpp + - ./drivers/fft.hpp + - ./drivers/demodulator.hpp + - ./drivers/redpitaya_adc_calibration.hpp web: - web/koheron.ts - web/jquery.flot.d.ts + - web/laser.ts - ./web/fft.ts - ./web/app.ts - ./web/control.ts - ./web/plot.ts - ./web/index.html - + - web/main.css diff --git a/examples/red-pitaya/fft/drivers/demodulator.hpp b/examples/red-pitaya/fft/drivers/demodulator.hpp new file mode 100644 index 000000000..26030f7f9 --- /dev/null +++ b/examples/red-pitaya/fft/drivers/demodulator.hpp @@ -0,0 +1,98 @@ +/// demodulator driver +/// +/// (c) Koheron + +#ifndef __DRIVERS_DEMODULATOR_HPP__ +#define __DRIVERS_DEMODULATOR_HPP__ + +#include + +#include + +// http://www.xilinx.com/support/documentation/ip_documentation/axi_fifo_mm_s/v4_1/pg080-axi-fifo-mm-s.pdf +namespace Fifo_regs { + constexpr uint32_t rdfr = 0x18; + constexpr uint32_t rdfo = 0x1C; + constexpr uint32_t rdfd = 0x20; + constexpr uint32_t rlr = 0x24; +} + +constexpr uint32_t fifo_buff_size = 8192 * 256; + +class Demodulator +{ + public: + Demodulator(Context& _ctx) + : ctx(_ctx) + , ctl(ctx.mm.get()) + , adc_fifo_map(_ctx.mm.get()) + { + start_fifo_acquisition(); + } + + ~Demodulator() { + fifo_acquisition_started = false; + fifo_thread.join(); + } + + void reset_fifo() {adc_fifo_map.write(0x000000A5);} + int32_t read_fifo() {return adc_fifo_map.read();} + uint32_t get_fifo_length() {return (adc_fifo_map.read() & 0x3FFFFF) >> 2;} + + std::vector& get_vector(uint32_t n_pts) { + last_buffer_vect.resize(n_pts); + std::lock_guard lock(mutex); + uint32_t start_idx = fifo_buff_idx - (fifo_buff_idx % 2); + for (uint32_t i = 0; i < n_pts; i++) { + last_buffer_vect[n_pts - 1 - i] = fifo_buffer[(start_idx - 1 - i) % fifo_buff_size]; + } + return last_buffer_vect; + } + + void start_fifo_acquisition(); + + private: + Context& ctx; + Memory& ctl; + Memory& adc_fifo_map; + + std::mutex mutex; + + std::atomic fifo_acquisition_started{false}; + std::atomic fifo_buff_idx{0}; + std::array fifo_buffer; + + std::vector last_buffer_vect; + std::thread fifo_thread; + void fifo_acquisition_thread(); + +}; + +inline void Demodulator::start_fifo_acquisition() { + if (! fifo_acquisition_started) { + fifo_buffer.fill(0); + fifo_thread = std::thread{&Demodulator::fifo_acquisition_thread, this}; + fifo_thread.detach(); + } +} + +inline void Demodulator::fifo_acquisition_thread() +{ + using namespace std::chrono_literals; + fifo_acquisition_started = true; + while (fifo_acquisition_started) { + { + std::lock_guard lock(mutex); + const uint32_t n_pts = get_fifo_length(); + ctx.log("fifo_length: %d \n", n_pts); + for (size_t i = 0; i < n_pts; i++) { + fifo_buffer[fifo_buff_idx] = read_fifo(); + fifo_buff_idx = (fifo_buff_idx + 1) % fifo_buff_size; + } + } + std::this_thread::sleep_for(10ms); + } +} + + +#endif // __DRIVERS_DEMODULATOR_HPP__ \ No newline at end of file diff --git a/examples/red-pitaya/fft/drivers/fft.hpp b/examples/red-pitaya/fft/drivers/fft.hpp new file mode 100644 index 000000000..aaff424d3 --- /dev/null +++ b/examples/red-pitaya/fft/drivers/fft.hpp @@ -0,0 +1,279 @@ +/// FFT driver +/// +/// (c) Koheron + +#ifndef __DRIVERS_FFT_HPP__ +#define __DRIVERS_FFT_HPP__ + +#include + +#include "redpitaya_adc_calibration.hpp" + +#include +#include +#include +#include +#include +#include +#include + +class FFT +{ + public: + FFT(Context& ctx_) + : ctx(ctx_) + , adc_calib(ctx.get()) + , ctl(ctx.mm.get()) + , sts(ctx.mm.get()) + , psd_map(ctx.mm.get()) + , demod_map(ctx.mm.get()) + { + set_in_channel(0); + set_scale_sch(0); + set_fft_window(0); + ctl.set_bit(); + start_psd_acquisition(); + } + + ////////////////////////////////////// + // Power Spectral Density + ////////////////////////////////////// + + void set_in_channel(uint32_t channel) { + if (channel >= 2) { + ctx.log("FFT::set_in_channel invalid channel\n"); + return; + } + + input_channel = channel; + ctl.write(channel); + } + + void set_scale_sch(uint32_t scale_sch) { + // LSB at 1 for forward FFT + ctl.write(1 + (scale_sch << 1)); + } + + void set_fft_window(uint32_t window_id) { + constexpr std::array, 4> window_coeffs = {{ + {1.0, 0, 0, 0, 0, 1.0}, // Rectangular + {0.5, 0.5, 0, 0, 0, 1.0}, // Hann + {1.0, 1.93, 1.29, 0.388, 0.028, 0.2}, // Flat top + {0.35875, 0.48829, 0.14128, 0.01168, 0, 1.0} // Blackman-Harris + }}; + + if (window_id >= 4) { + ctx.log("Invalid FFT window index \n"); + return; + } + + set_cosine_sum_window(window_coeffs[window_id]); + set_window_buffer(); + window_index = window_id; + } + + // Read averaged spectrum data + const auto& read_psd_raw() { + std::lock_guard lock(mutex); + return psd_buffer_raw; + } + + // Return the PSD in W/Hz + const auto& read_psd() { + std::lock_guard lock(mutex); + return psd_buffer; + } + + uint32_t get_number_averages() const { + return prm::n_cycles; + } + + uint32_t get_fft_size() const { + return prm::fft_size; + } + + // Return the raw input value of each ADC channel + // n_avg: number of averages + const std::array get_adc_raw_data(uint32_t n_avg) { + if (n_avg <= 1) { + return { ((static_cast(sts.read()) + 8192) % 16384) - 8192, + ((static_cast(sts.read()) + 8192) % 16384) - 8192 }; + } else { + int32_t adc0 = 0; + int32_t adc1 = 0; + + for (size_t i=0; i(sts.read()) + 8192) % 16384) - 8192; + adc1 += ((static_cast(sts.read()) + 8192) % 16384) - 8192; + } + + return { int32_t(std::round(adc0 / double(n_avg))), + int32_t(std::round(adc1 / double(n_avg))) }; + } + } + + ////////////////////////////////////// + // Direct Digital Synthesis + ////////////////////////////////////// + + void set_dds_freq(uint32_t channel, double freq_hz) { + if (channel >= 2) { + ctx.log("FFT::set_dds_freq invalid channel\n"); + return; + } + + if (std::isnan(freq_hz)) { + ctx.log("FFT::set_dds_freq Frequency is NaN\n"); + return; + } + + if (freq_hz > fs_adc / 2) { + freq_hz = fs_adc / 2; + } + + if (freq_hz < 0.0) { + freq_hz = 0.0; + } + + constexpr double factor = (uint64_t(1) << 32) / fs_adc; + ctl.write_reg(reg::phase_incr0 + 4 * channel, uint32_t(factor * freq_hz)); + dds_freq[channel] = freq_hz; + } + + auto get_control_parameters() { + return std::make_tuple(dds_freq[0], dds_freq[1], fs_adc, input_channel, W1, W2); + } + + const auto& get_window_index() const { + return window_index; + } + + private: + Context& ctx; + RedPitayaAdcCalibration& adc_calib; + Memory& ctl; + Memory& sts; + Memory& psd_map; + Memory& demod_map; + + static constexpr double fs_adc = prm::adc_clk; // ADC sampling rate (Hz) + std::array, 2> freq_calibration; // Conversion to W/Hz + + std::array window; + double W1, W2; // Window correction factors + uint32_t window_index; + + uint32_t input_channel = 0; + std::array dds_freq = {{0.0, 0.0}}; + + std::array psd_buffer_raw; + std::array psd_buffer; + std::thread psd_thread; + std::mutex mutex; + std::atomic psd_acquisition_started{false}; + std::atomic acq_cycle_index{0}; + void psd_acquisition_thread(); + void start_psd_acquisition(); + + // https://en.wikipedia.org/wiki/Window_function + void set_cosine_sum_window(const std::array& a) { + double sign; + + for (size_t i=0; i window_buffer; + double res1 = 0; + double res2 = 0; + + for (size_t i=0; i(); + } + + // Vectors to convert PSD raw data into W/Hz + // XXX Since Red Pitaya is 1 MOhm input, maybe V^2/Hz are better ... + void set_conversion_vectors() { + constexpr double load = 50.0; // Ohm + + auto Hinv = koheron::make_array( + adc_calib.get_inverse_transfer_function<0, prm::fft_size/2>(fs_adc), + adc_calib.get_inverse_transfer_function<1, prm::fft_size/2>(fs_adc) + ); + + std::array vin = { adc_calib.get_input_voltage_range(0), + adc_calib.get_input_voltage_range(1) }; + + float C0 = (vin[0] / (2 << 20)) * (vin[0] / (2 << 20)) / prm::n_cycles / fs_adc / load / W2; + float C1 = (vin[1] / (2 << 20)) * (vin[1] / (2 << 20)) / prm::n_cycles / fs_adc / load / W2; + + for (unsigned int i=0; i= previous_cycle_index) { + auto sleep_time = std::chrono::nanoseconds((prm::n_cycles - cycle_index) * 2048 * 8); + if (sleep_time > 1ms) { + std::this_thread::sleep_for(sleep_time); + } + previous_cycle_index = cycle_index; + cycle_index = get_cycle_index(); + } + + { + std::lock_guard lock(mutex); + psd_buffer_raw = psd_map.read_array(); + + for (unsigned int i=0; i + +#include +#include +#include + +// Calibration array: [gain, offset, p0, p1, p2, p3, p4, p5] +// gain in LSB / Volts +// offset in LSB +// pi: Coefficient of the polynomial fit of 1 / H(f) + +static constexpr std::array, 2> cal_coeffs = {{ + {7.25926074e+03, -1.25870003e+02, 3.49261787e-38, -4.39604832e-30, 1.90004015e-22, -3.35273936e-15, 2.54795065e-08, 9.48362052e-01}, + {7.26281201e+03, -7.75899963e+01, 4.63291048e-38, -5.88647924e-30, 2.58020095e-22, -4.58396909e-15, 3.54267726e-08, 9.28313494e-01} +}}; + +class RedPitayaAdcCalibration +{ + public: + RedPitayaAdcCalibration(Context& ctx_) + : ctx(ctx_) + {} + + const auto get_calibration(uint32_t channel) { + if (channel >= 2) { + ctx.log("RedPitayaAdcCalibration::get_calibration: Invalid channel\n"); + return std::array{}; + } + + return cal_coeffs[channel]; + } + + template + const auto get_inverse_transfer_function(float fs) { + static_assert(channel < 2, "Invalid channel"); + std::array res; + + for (unsigned int i=0; i::epsilon()) { + return NAN; + } + + return (1 << 14) / g; + } + + float get_gain(uint32_t channel) const { + if (channel >= 2) { + ctx.log("RedPitayaAdcCalibration::get_gain: Invalid channel\n"); + return NAN; + } + + return cal_coeffs[channel][0]; + } + + float get_offset(uint32_t channel) const { + if (channel >= 2) { + ctx.log("RedPitayaAdcCalibration::get_offset: Invalid channel\n"); + return NAN; + } + + return cal_coeffs[channel][1]; + } + + private: + Context& ctx; +}; + +#endif // __REDPITAYA_DRIVERS_ADC_CALIBRATION_HPP__ diff --git a/examples/red-pitaya/fft/expansion_connector.xdc b/examples/red-pitaya/fft/expansion_connector.xdc new file mode 100644 index 000000000..2d258d792 --- /dev/null +++ b/examples/red-pitaya/fft/expansion_connector.xdc @@ -0,0 +1,11 @@ +### Expansion connector + +set_property IOSTANDARD LVCMOS33 [get_ports laser_*] +set_property SLEW FAST [get_ports laser_*] + +set_property PACKAGE_PIN M14 [get_ports laser_shutdown] ;# DIO7_P +set_property PACKAGE_PIN M15 [get_ports laser_reset_overvoltage] ;# DIO7_N +set_property PACKAGE_PIN G18 [get_ports laser_eeprom_dout] +set_property PACKAGE_PIN H16 [get_ports laser_eeprom_din] +set_property PACKAGE_PIN H17 [get_ports laser_eeprom_sclk] +set_property PACKAGE_PIN J18 [get_ports laser_eeprom_cs] \ No newline at end of file diff --git a/examples/red-pitaya/fft/fft.hpp b/examples/red-pitaya/fft/fft.hpp deleted file mode 100644 index c2645bde8..000000000 --- a/examples/red-pitaya/fft/fft.hpp +++ /dev/null @@ -1,130 +0,0 @@ -/// FFT driver -/// -/// (c) Koheron - -#ifndef __DRIVERS_FFT_HPP__ -#define __DRIVERS_FFT_HPP__ - -#include -#include -#include -#include -#include - -class FFT -{ - public: - FFT(Context& ctx) - : ctl(ctx.mm.get()) - , sts(ctx.mm.get()) - , psd_map(ctx.mm.get()) - , demod_map(ctx.mm.get()) - { - std::array demod_buffer; - demod_buffer.fill(0xFFFF0000); - set_scale_sch(0); - set_demod_buffer(demod_buffer); - ctl.set_bit(); - start_psd_acquisition(); - } - - uint32_t get_fft_size() { - return prm::fft_size; - } - - uint32_t get_cycle_index() { - return sts.read(); - } - - ////////////////////////////////////// - // Power Spectral Density - ////////////////////////////////////// - - void set_scale_sch(uint32_t scale_sch) { - // LSB at 1 for forward FFT - ctl.write(1 + (scale_sch << 1)); - } - - void set_offset(uint32_t offset_real, uint32_t offset_imag) { - ctl.write(offset_real + (offset_imag << prm::adc_width)); - } - - void set_demod_buffer(const std::array& arr) { - demod_map.set_ptr(arr.data(), prm::fft_size, 0); - demod_map.set_ptr(arr.data(), prm::fft_size, 1); - } - - // Read averaged spectrum data - std::array& read_psd() { - std::lock_guard lock(mutex); - return psd_buffer; - } - - void start_psd_acquisition(); - - ////////////////////////////////////// - // Direct Digital Synthesis - ////////////////////////////////////// - - void set_dds_freq(uint32_t channel, double freq_hz) { - double factor = (uint64_t(1) << 32) / double(prm::adc_clk); - ctl.write_reg(reg::phase_incr0 + 4 * channel, uint32_t(factor * freq_hz)); - dds_freq[channel] = freq_hz; - } - - auto get_control_parameters() { - return std::make_tuple(dds_freq[0], dds_freq[1]); - } - - private: - Memory& ctl; - Memory& sts; - Memory& psd_map; - Memory& demod_map; - - std::array dds_freq = {{0.0, 0.0}}; - - std::array psd_buffer; - std::thread psd_thread; - std::mutex mutex; - std::atomic psd_acquisition_started{false}; - std::atomic acq_cycle_index{0}; - void psd_acquisition_thread(); -}; - -inline void FFT::start_psd_acquisition() { - if (! psd_acquisition_started) { - psd_buffer.fill(0); - psd_thread = std::thread{&FFT::psd_acquisition_thread, this}; - psd_thread.detach(); - } -} - -inline void FFT::psd_acquisition_thread() -{ - psd_acquisition_started = true; - - while (psd_acquisition_started) { - uint32_t cycle_index = get_cycle_index(); - uint32_t previous_cycle_index = cycle_index; - - // Wait for - while (cycle_index >= previous_cycle_index) { - uint32_t sleep_time_ns = (prm::n_cycles - cycle_index) * prm::fft_size * 1e9 / prm::adc_clk; - if (sleep_time_ns > 1'000'000) { - std::this_thread::sleep_for(std::chrono::nanoseconds(sleep_time_ns)); - } - previous_cycle_index = cycle_index; - cycle_index = get_cycle_index(); - } - { - std::lock_guard lock(mutex); - psd_buffer = psd_map.read_array(); - } - - - acq_cycle_index = get_cycle_index(); - } -} - -#endif // __DRIVERS_FFT_HPP__ diff --git a/examples/red-pitaya/fft/fft.py b/examples/red-pitaya/fft/fft.py deleted file mode 100644 index 3c4db4280..000000000 --- a/examples/red-pitaya/fft/fft.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import time -import math -import numpy as np - -from koheron import command - -class FFT(object): - def __init__(self, client): - self.client = client - self.n_pts = self.get_fft_size() - self.fs = 125e6 - - @command() - def get_fft_size(self): - return self.client.recv_uint32() - - @command() - def get_cycle_index(self): - return self.client.recv_tuple('II') - - def set_demod(self, data): - @command() - def set_demod_buffer(self, data): - pass - data1 = np.uint32(np.mod(np.floor(32768 * data[0, :]) + 32768, 65536) + 32768) - data2 = np.uint32(np.mod(np.floor(32768 * data[1, :]) + 32768, 65536) + 32768) - set_demod_buffer(self, data1 + (data2 << 16)) - - @command() - def read_psd(self): - return self.client.recv_array(self.n_pts/2, dtype='float32') - - # DDS - - @command() - def set_dds_freq(self, channel, freq): - pass \ No newline at end of file diff --git a/examples/red-pitaya/fft/python/adc_calibration.py b/examples/red-pitaya/fft/python/adc_calibration.py new file mode 100644 index 000000000..e83a854de --- /dev/null +++ b/examples/red-pitaya/fft/python/adc_calibration.py @@ -0,0 +1,121 @@ +import visa +import os +import numpy as np +import time +import matplotlib.pyplot as plt +from scipy import stats + +from fft import FFT, Window +from koheron import connect + +# Both the Koheron Alpha and the Keysight 33600A must be +# disciplined on the Tektronix MCA3027 OCXO 10 MHz output. + +class Keysight33600A: + def __init__(self): + import visa + rm = visa.ResourceManager('@py') + self.inst = rm.open_resource('TCPIP::A-33600-00000.local::inst0::INSTR') + self.inst.write('OUTP1:LOAD INF') # High impedance output + self.inst.write('OUTP2:LOAD INF') # High impedance output + self.channel = 1 + + def set_channel(self, channel): + self.channel = channel + + def set_sinus(self, freq, v_pp, offset): + self.inst.write('SOUR' + str(int(self.channel)) + ':APPLy:SIN ' + str(freq / 1E3) + ' KHZ, ' + + str(v_pp) + ' VPP, ' + + str(offset) + ' V\n') + + def set_dc_offset(self, offset): + self.inst.write('SOUR' + str(int(self.channel)) + ':APPLy:DC DEF, DEF, ' + str(offset) + ' V\n') + +def calibrate_transfer_function(gene, driver): + n_pts = driver.n_pts + fs = driver.get_fs() + print n_pts, fs + + freqs = np.linspace(1e6, 62e6, num=200) + freqs = np.round(freqs / fs * n_pts) * fs / n_pts + + peak_power = 0.0 * freqs + + for i, freq in enumerate(freqs): + n = np.uint32(freq / fs * n_pts) + gene.set_sinus(freq, 0.5, 0) + + time.sleep(0.5) + psd = driver.read_psd_raw() + peak_power[i] = psd[n-1] + #peak_power[i] = np.max(psd) + print i, freq, peak_power[i] + + H = peak_power / peak_power[0] # Power transfer function + + # We fit 1 / H(f) with a degree 5 polynomial + p = np.polyfit(freqs, 1 / H, 5) + + H_db = 10 * np.log10(H) + H_fit_db = 10 * np.log10(1 / np.polyval(p, freqs)) + residuals_db = H_db - H_fit_db + + f, axarr = plt.subplots(2, sharex=True) + axarr[1].set_xlabel('Frequency (MHz)') + axarr[0].set_ylabel('Transfer function (dB)') + axarr[1].set_ylabel('Residuals (dB)') + axarr[0].semilogx(freqs * 1e-6, H_db) + axarr[0].semilogx(freqs * 1e-6, H_fit_db) + axarr[1].semilogx(freqs * 1e-6, residuals_db) + plt.show() + + return p + +def calibrate_offset_gain(gene, driver, channel): + n_cal_pts = 100 + voltages = np.linspace(-1, 1, n_cal_pts) + off_raw = 0 * voltages + + for i, Voff in enumerate(voltages): + gene.set_dc_offset(Voff) + time.sleep(0.5) + off_raw[i] = driver.get_adc_raw_data(100)[channel] + print i, off_raw[i] + + gain_lsb, offset_lsb, r_value, p_value, std_err = stats.linregress(voltages, off_raw) + print gain_lsb, offset_lsb + + gain_volts, offset_volts, r_value, p_value, std_err = stats.linregress(off_raw, voltages) + print gain_volts, offset_volts + + residuals = off_raw - (gain_lsb * voltages + offset_lsb) + + f, axarr = plt.subplots(2, sharex=True) + axarr[0].plot(voltages, off_raw) + axarr[0].plot(voltages, gain_lsb * voltages + offset_lsb) + axarr[1].plot(voltages, residuals) + axarr[0].set_ylabel("ADC raw value") + axarr[1].set_ylabel("ADC INL") + axarr[1].set_xlabel("Input voltage (V)") + plt.show() + + return gain_lsb, offset_lsb + +if __name__=="__main__": + gene = Keysight33600A() + host = os.getenv('HOST', '192.168.1.18') + client = connect(host, 'fft', restart=False) + driver = FFT(client) + + driver.set_fft_window(Window.FLAT_TOP) + + for channel_under_test in range(2): + gene.set_channel(channel_under_test + 1) + driver.set_in_channel(channel_under_test) + time.sleep(1) + + gain, offset = calibrate_offset_gain(gene, driver, channel_under_test) + p = calibrate_transfer_function(gene, driver) + + cal_coeffs = np.float32(np.concatenate((np.array([gain, offset]), p))) + print cal_coeffs \ No newline at end of file diff --git a/examples/red-pitaya/fft/python/fft.py b/examples/red-pitaya/fft/python/fft.py new file mode 100644 index 000000000..0e01a1493 --- /dev/null +++ b/examples/red-pitaya/fft/python/fft.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import time +import math +import numpy as np +from enum import IntEnum + +from koheron import command + +class Window(IntEnum): + RECT = 0 + HANN = 1 + FLAT_TOP = 2 + BLACKMAN_HARRIS = 3 + +class FFT(object): + def __init__(self, client): + self.client = client + self.n_pts = self.get_fft_size() + + def get_fs(self): + return self.get_control_parameters()[2] + + @command() + def get_fft_size(self): + return self.client.recv_uint32() + + @command() + def get_cycle_index(self): + return self.client.recv_tuple('II') + + @command() + def set_offset(self, offset_real, offset_imag): + pass + + @command() + def set_in_channel(self, channel): + pass + + @command() + def set_fft_window(self, window_id): + pass + + @command() + def read_psd(self): + return self.client.recv_array(self.n_pts/2, dtype='float32') + + @command() + def read_psd_raw(self): + return self.client.recv_array(self.n_pts/2, dtype='float32') + + # DDS + + @command() + def set_dds_freq(self, channel, freq): + pass + + @command() + def get_control_parameters(self): + return self.client.recv_tuple('dddIdd') + + @command() + def get_adc_raw_data(self, n_avg): + return self.client.recv_array(2, dtype='int32') + + # Demodulation + + @command(classname='Demodulator') + def get_fifo_length(self): + return self.client.recv_uint32() + + @command(classname='Demodulator') + def get_vector(self, n_pts): + return self.client.recv_vector(dtype='int32') diff --git a/examples/red-pitaya/fft/python/fir.py b/examples/red-pitaya/fft/python/fir.py new file mode 100644 index 000000000..4580c02c4 --- /dev/null +++ b/examples/red-pitaya/fft/python/fir.py @@ -0,0 +1,54 @@ + +import sys +from scipy import signal +import numpy as np + +# https://www.altera.com/en_US/pdfs/literature/an/an455.pdf +# http://dsp.stackexchange.com/questions/160/fir-filter-compensator-when-using-a-cic-decimation-filter +# http://www.acasper.org/2011/10/02/my-sdr/ + +def get_taps(N, R, M, ntaps=128, cutoff=0.45): + """ + Find the coefficients of the half-band FIR filter that compensate the CIC filter from 0 to cutoff + N : number of CIC stages + R : decimation rate + M : differential delay in the comb section stages of the filter + """ + f = np.arange(2048) / 2047. + cic_response = lambda f : abs( M/R * (np.sin((f*R)/2)) / (np.sin((f*M)/2.)) )**N if f !=0 else 1 + + H = np.array(map(cic_response, f*np.pi)) + + # Define frequency reponse of ideal compensation filter + H = np.array(map(cic_response, f*np.pi / R)) + Hc = 1/H * (f < cutoff) + + beta = 8 + taps = signal.firwin2(ntaps, f, Hc, nfreqs = 1025, window=('kaiser', beta)) + taps /= np.sum(taps) + return taps, cic_response + +if __name__=="__main__": + + N = float(sys.argv[1]) # number of CIC stages + R = float(sys.argv[2]) # decimation rate + M = float(sys.argv[3]) # differential delay + + taps, cic_response = get_taps(N, R, M) + + if sys.argv[4] == 'print': + print(', '.join('%e' % f for f in taps)) + + if sys.argv[4] == 'plot': + import matplotlib.pyplot as plt + w, h = signal.freqz(taps, worN=16384) + hh = np.array(map(cic_response, w / R)) + hh[0] = 1 + plt.plot(w / np.pi, 20 * np.log10(abs(h)), label='FIR filter') + plt.plot(w / np.pi, 20 * np.log10(abs(h * hh)), label='Compensated filter') + plt.plot(w / np.pi, 20 * np.log10(abs(hh)), label='CIC filter') + plt.xlabel('Normalized frequency') + plt.ylabel('Filter gain (dB)') + plt.legend() + plt.ylim(-120, 20) + plt.show() \ No newline at end of file diff --git a/examples/red-pitaya/fft/test_fft.py b/examples/red-pitaya/fft/python/test.py similarity index 71% rename from examples/red-pitaya/fft/test_fft.py rename to examples/red-pitaya/fft/python/test.py index 0aeb9af9a..6bcce32fc 100644 --- a/examples/red-pitaya/fft/test_fft.py +++ b/examples/red-pitaya/fft/python/test.py @@ -9,14 +9,24 @@ from fft import FFT from koheron import connect -host = os.getenv('HOST', '192.168.1.10') +host = os.getenv('HOST', '192.168.1.11') client = connect(host, 'fft', restart=False) driver = FFT(client) +print('Start test.py') + n_pts = driver.n_pts -fs = driver.fs +fs = 250e6 + +psd = driver.read_psd() + +#plt.plot(10*np.log10(psd)) +plt.plot(psd) +plt.show() + +#freqs = np.array([0.01, 0.02, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 25, 27, 30, 33, 35, 37, 40, 43, 45, 47, 50, 53, 55, 57, 60, 63, 65, 67, 70, 73, 75, 77, 80, 83, 85, 87, 90, 93, 95, 97, 100, 103, 105, 107, 110, 113, 115, 117, 120, 123, 124]) * 1e6 -freqs = np.linspace(0.1e6, 10e6,num=100) +freqs = np.linspace(0.01e6, 40e6,num=200) freqs = np.round(freqs / fs * n_pts) * fs / n_pts hd1 = 0.0 * freqs diff --git a/examples/red-pitaya/fft/python/test_demod.py b/examples/red-pitaya/fft/python/test_demod.py new file mode 100644 index 000000000..02998a50f --- /dev/null +++ b/examples/red-pitaya/fft/python/test_demod.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import numpy as np +import matplotlib +matplotlib.use('GTKAgg') +from matplotlib import pyplot as plt + +from scipy import signal +import os, time +from koheron import connect +from fft import FFT + +if __name__=="__main__": + host = os.getenv('HOST', '192.168.1.16') + client = connect(host, 'fft') + driver = FFT(client) + + driver.set_dds_freq(0, 50e6) + + + n = 4096 + fs = 250e6 + cic_rate = 500 + + ffft = np.fft.fftfreq(n) * fs / (cic_rate * 2) + + # Dynamic plot + fig = plt.figure() + ax = fig.add_subplot(111) + x = np.arange(n) + y = np.zeros(n) + + li, = ax.plot(np.fft.fftshift(ffft), y) + ax.set_ylim((-1000, 1000)) + + fig.canvas.draw() + + window = 0.5 * (1 - np.cos(2*np.pi*np.arange(n)/n)) + + n_avg = 1 + dsp = np.zeros((n_avg, n)) + i = 0 + + while True: + try: + i = (i + 1) % n_avg + print driver.get_fifo_length() + data = driver.get_vector(2*n) + z = data[::2] + 1j*data[1::2] + + dsp[i,:] = np.abs(np.fft.fft(z))**2 + + mean_dsp = np.mean(dsp, axis=0) + + li.set_ydata(np.fft.fftshift(10*np.log10(mean_dsp))) + + fig.canvas.draw() + plt.pause(0.001) + + except KeyboardInterrupt: + break \ No newline at end of file diff --git a/examples/red-pitaya/fft/tcl/demodulator.tcl b/examples/red-pitaya/fft/tcl/demodulator.tcl new file mode 100644 index 000000000..f60fa5fd7 --- /dev/null +++ b/examples/red-pitaya/fft/tcl/demodulator.tcl @@ -0,0 +1,113 @@ +namespace eval demodulator { + +proc pins {cmd} { + $cmd -dir I -type clk aclk + $cmd -dir I -from 0 -to 0 aresetn + $cmd -dir I -from 31 -to 0 s_axis_data_a + $cmd -dir I -from 31 -to 0 s_axis_data_b + $cmd -dir I -from 0 -to 0 s_axis_tvalid + $cmd -dir O -from 31 -to 0 m_axis_tdata + $cmd -dir O -from 0 -to 0 m_axis_tvalid +} + +proc create {module_name} { + + set bd [current_bd_instance .] + current_bd_instance [create_bd_cell -type hier $module_name] + + pins create_bd_pin + + # Complex multiplier + + cell xilinx.com:ip:cmpy:6.0 complex_mult { + APortWidth 16 + BPortWidth 16 + OutputWidth 24 + } { + aclk aclk + s_axis_a_tdata s_axis_data_a + s_axis_a_tvalid s_axis_tvalid + s_axis_b_tdata s_axis_data_b + s_axis_b_tvalid s_axis_tvalid + } + + cell xilinx.com:ip:axis_broadcaster:1.1 axis_broadcaster_0 { + S_TDATA_NUM_BYTES 6 + M_TDATA_NUM_BYTES 3 + M00_TDATA_REMAP {tdata[23:0]} + M01_TDATA_REMAP {tdata[47:24]} + } { + S_AXIS complex_mult/M_AXIS_DOUT + aclk aclk + aresetn aresetn + } + + # Define CIC parameters + + set diff_delay [get_parameter cic_differential_delay] + set dec_rate [get_parameter cic_decimation_rate] + set n_stages [get_parameter cic_n_stages] + + for {set i 0} {$i < 2} {incr i} { + cell xilinx.com:ip:cic_compiler:4.0 cic_$i { + Filter_Type Decimation + Number_Of_Stages $n_stages + Fixed_Or_Initial_Rate $dec_rate + Differential_Delay $diff_delay + Input_Sample_Frequency [expr [get_parameter adc_clk] / 1000000.] + Clock_Frequency [expr [get_parameter adc_clk] / 1000000.] + Quantization Truncation + Input_Data_Width 24 + Output_Data_Width 32 + Use_Xtreme_DSP_Slice false + } { + aclk aclk + S_AXIS_DATA axis_broadcaster_0/M0${i}_AXIS + } + } + + # FIR + + cell xilinx.com:ip:axis_combiner:1.1 axis_combiner_0 { + TDATA_NUM_BYTES 4 + } { + aclk aclk + aresetn aresetn + S00_AXIS cic_0/M_AXIS_DATA + S01_AXIS cic_1/M_AXIS_DATA + } + + cell xilinx.com:ip:axis_dwidth_converter:1.1 axis_dwidth_converter_0 { + S_TDATA_NUM_BYTES 8 + M_TDATA_NUM_BYTES 4 + } { + S_AXIS axis_combiner_0/M_AXIS + aclk aclk + aresetn aresetn + } + + set fir_coeffs [exec python $::project_path/python/fir.py $n_stages $dec_rate $diff_delay print] + + cell xilinx.com:ip:fir_compiler:7.2 fir { + Filter_Type Decimation + Sample_Frequency [expr [get_parameter adc_clk] / 1000000. / $dec_rate] + Clock_Frequency [expr [get_parameter adc_clk] / 1000000.] + Coefficient_Width 32 + Data_Width 32 + Output_Rounding_Mode Convergent_Rounding_to_Even + Output_Width 32 + Decimation_Rate 2 + BestPrecision true + CoefficientVector [subst {{$fir_coeffs}}] + NUMBER_CHANNELS 2 + } { + aclk aclk + S_AXIS_DATA axis_dwidth_converter_0/M_AXIS + m_axis_data_tdata m_axis_tdata + m_axis_data_tvalid m_axis_tvalid + } + + current_bd_instance $bd +} + +} ;# end spectrum namespace diff --git a/examples/red-pitaya/fft/tcl/power_spectral_density.tcl b/examples/red-pitaya/fft/tcl/power_spectral_density.tcl index d35a98bf0..685f610ff 100644 --- a/examples/red-pitaya/fft/tcl/power_spectral_density.tcl +++ b/examples/red-pitaya/fft/tcl/power_spectral_density.tcl @@ -1,28 +1,28 @@ namespace eval power_spectral_density { -proc pins {cmd adc_width} { - $cmd -dir I -from [expr $adc_width - 1] -to 0 adc1 - $cmd -dir I -from [expr $adc_width - 1] -to 0 adc2 - $cmd -dir I -from 31 -to 0 ctl_sub - $cmd -dir I -from 31 -to 0 ctl_fft - $cmd -dir I -from 0 -to 0 tvalid - $cmd -dir O -from 31 -to 0 m_axis_result_tdata - $cmd -dir O -from 0 -to 0 m_axis_result_tvalid - $cmd -dir I -type clk clk +proc pins {cmd} { + $cmd -dir I -from 13 -to 0 data + $cmd -dir I -from 31 -to 0 ctl_fft + $cmd -dir I -from 0 -to 0 tvalid + $cmd -dir O -from 31 -to 0 m_axis_result_tdata + $cmd -dir O -from 0 -to 0 m_axis_result_tvalid + $cmd -dir I -type clk clk } -proc create {module_name fft_size adc_width} { +proc create {module_name fft_size} { set bd [current_bd_instance .] current_bd_instance [create_bd_cell -type hier $module_name] - pins create_bd_pin $adc_width + pins create_bd_pin cell xilinx.com:ip:c_counter_binary:12.0 demod_address { + CE true Output_Width [expr int(log([get_parameter fft_size])/log(2)) + 2] Increment_Value 4 } { CLK clk + CE tvalid } # Add demod BRAM @@ -35,37 +35,16 @@ proc create {module_name fft_size adc_width} { web [get_constant_pin 0 4] } - for {set i 1} {$i < 3} {incr i} { - cell xilinx.com:ip:c_addsub:12.0 subtract_$i { - A_Width $adc_width - B_Width $adc_width - Add_mode Subtract - CE false - Out_Width $adc_width - Latency 2 - } { - clk clk - A adc$i - B [get_slice_pin ctl_sub [expr $adc_width*$i-1] [expr $adc_width*($i-1)]] - } - } - - set left_zeros [get_constant_pin 0 [expr 16 - $adc_width]] - set shifted_tvalid [get_Q_pin tvalid 2] + set shifted_tvalid [get_Q_pin tvalid 3] cell xilinx.com:ip:cmpy:6.0 complex_mult { - APortWidth $adc_width - BPortWidth $adc_width - OutputWidth [expr 2*$adc_width + 1] - LatencyConfig Manual - MinimumLatency 6 + APortWidth 16 + BPortWidth 16 + OutputWidth 33 } { aclk clk - s_axis_a_tdata [get_concat_pin { - subtract_1/S $left_zeros - subtract_2/S $left_zeros - }] - s_axis_b_tdata $demod_bram_name/doutb + s_axis_a_tdata [get_concat_pin [list [get_constant_pin 0 2] [get_Q_pin data 3] [get_constant_pin 0 16]]] + s_axis_b_tdata [get_Q_pin $demod_bram_name/doutb 1] s_axis_a_tvalid $shifted_tvalid s_axis_b_tvalid $shifted_tvalid } @@ -74,12 +53,14 @@ proc create {module_name fft_size adc_width} { cell xilinx.com:ip:floating_point:7.1 float_$i { Operation_Type Fixed_to_float A_Precision_Type Custom - C_A_Exponent_Width [expr 2*$adc_width + 1] + C_A_Exponent_Width 17 + C_A_Fraction_Width 16 Flow_Control NonBlocking - Maximum_Latency True + Maximum_Latency False + C_Latency 3 } { aclk clk - s_axis_a_tdata [get_slice_pin complex_mult/m_axis_dout_tdata [expr 31+32*$i] [expr 32*$i]] + s_axis_a_tdata [get_slice_pin complex_mult/m_axis_dout_tdata [expr 32+40*$i] [expr 40*$i]] s_axis_a_tvalid complex_mult/m_axis_dout_tvalid } } @@ -92,9 +73,6 @@ proc create {module_name fft_size adc_width} { phase_factor_width 24 throttle_scheme realtime output_ordering natural_order - complex_mult_type use_mults_performance - number_of_stages_using_block_ram_for_data_and_phase_factors 5 - butterfly_type use_luts } { aclk clk s_axis_data_tdata [get_concat_pin { @@ -108,12 +86,12 @@ proc create {module_name fft_size adc_width} { } for {set i 0} {$i < 2} {incr i} { - set slice_tdata [get_slice_pin fft_0/m_axis_data_tdata [expr 31+32*$i] [expr 32*$i]] cell xilinx.com:ip:floating_point:7.1 mult_$i { Operation_Type Multiply Flow_Control NonBlocking - Maximum_Latency True + Maximum_Latency False + C_Latency 3 } { aclk clk s_axis_a_tdata $slice_tdata @@ -127,7 +105,8 @@ proc create {module_name fft_size adc_width} { Flow_Control NonBlocking Add_Sub_Value Add C_Mult_Usage No_Usage - Maximum_Latency True + Maximum_Latency False + C_Latency 3 } { aclk clk S_AXIS_A mult_0/M_AXIS_RESULT diff --git a/examples/red-pitaya/fft/web/app.ts b/examples/red-pitaya/fft/web/app.ts index a631c5553..49ce9ced1 100644 --- a/examples/red-pitaya/fft/web/app.ts +++ b/examples/red-pitaya/fft/web/app.ts @@ -1,7 +1,9 @@ class App { public control: Control; public plot: Plot; - private driver: FFT; + private fft: FFT; + public laserDriver: LaserDriver; + public laserControl: LaserControl; constructor(window: Window, document: Document, ip: string, plot_placeholder: JQuery) { @@ -9,9 +11,14 @@ class App { window.addEventListener('load', () => { client.init( () => { - this.driver = new FFT(client); - this.control = new Control(document, this.driver); - this.plot = new Plot(document, plot_placeholder, this.driver); + this.fft = new FFT(client); + + this.fft.init( () => { + this.control = new Control(document, this.fft); + this.plot = new Plot(document, plot_placeholder, this.fft, this.control); + this.laserDriver = new LaserDriver(client); + this.laserControl = new LaserControl(document, this.laserDriver); + }); }); }, false); @@ -20,6 +27,4 @@ class App { } - - let app = new App(window, document, location.hostname, $('#plot-placeholder')); \ No newline at end of file diff --git a/examples/red-pitaya/fft/web/control.ts b/examples/red-pitaya/fft/web/control.ts index 4fee0e42a..fd5050a5a 100644 --- a/examples/red-pitaya/fft/web/control.ts +++ b/examples/red-pitaya/fft/web/control.ts @@ -2,59 +2,94 @@ // (c) Koheron class Control { - private frequency_value: HTMLLinkElement; - private frequency_input: HTMLInputElement; - private frequency_save: HTMLLinkElement; - private frequency_slider: HTMLInputElement; - - private frequency: number; - - constructor(document: Document, private driver: FFT) { - this.frequency_value = document.getElementById('frequency-value'); - this.frequency_input = document.getElementById('frequency-input'); - this.frequency_save = document.getElementById('frequency-save'); - this.frequency_slider = document.getElementById('frequency-slider'); - this.update(); + private channelNum: number; + + private InChannelInputs: HTMLInputElement[]; + + public fftWindowIndex: number; + private fftWindowSelect: HTMLSelectElement; + + private frequencies: Array; + private frequencyInputs: HTMLInputElement[]; + private frequencySliders: HTMLInputElement[]; + + constructor(document: Document, private fft: FFT) { + this.channelNum = 2; + + this.InChannelInputs = []; + this.frequencyInputs = []; + this.frequencySliders = []; + + for (let i: number = 0; i < this.channelNum; i++) { + this.InChannelInputs[i] = document.getElementById('in-channel-' + i.toString()); + this.frequencyInputs[i] = document.getElementById('frequency-input-' + i.toString()); + this.frequencySliders[i] = document.getElementById('frequency-slider-' + i.toString()); + } + + this.frequencies = new Array(this.channelNum); + + this.fftWindowIndex = 1; + this.fftWindowSelect = document.getElementById("fft-window"); + this.init(); } - update() { - this.driver.getControlParameters( (sts: IFFTStatus) => { - this.frequency_value.innerHTML = (sts.dds_freq[0]/1e6).toFixed(6); - this.frequency_slider.value = (sts.dds_freq[0]/1e6).toFixed(4); - requestAnimationFrame( () => { this.update(); } ) + init() { + this.fft.getControlParameters( (sts: IFFTStatus) => { + for (let i: number = 0; i < this.channelNum; i++) { + this.frequencyInputs[i].value = (sts.dds_freq[0] / 1e6).toFixed(6); + this.frequencySliders[i].value = (sts.dds_freq[0] / 1e6).toFixed(4); + } + + this.fft.getFFTWindowIndex( (windowIndex: number) => { + this.fftWindowIndex = windowIndex; + this.fftWindowSelect.value = windowIndex.toString(); + + requestAnimationFrame( () => {this.update();} ) + }); }); } - editFrequency() { - this.frequency_value.style.display = 'none'; - this.frequency_input.style.display = 'inline'; - this.frequency_save.style.display = 'inline'; - this.frequency_input.value = this.frequency_value.innerHTML; - } + private update() { + this.fft.getControlParameters( (sts: IFFTStatus) => { + for (let i: number = 0; i < this.channelNum; i++) { + if (document.activeElement !== this.frequencyInputs[i]) { + this.frequencyInputs[i].value = (sts.dds_freq[i] / 1e6).toFixed(6); + } + + if (document.activeElement !== this.frequencySliders[i]) { + this.frequencySliders[i].value = (sts.dds_freq[i] / 1e6).toFixed(6); + this.frequencySliders[i].max = (sts.fs / 1e6 / 2).toFixed(1); + } + + this.InChannelInputs[sts.channel].checked = true; + + } - saveFrequency() { - this.frequency_value.style.display = 'inline'; - this.frequency_input.style.display = 'none'; - this.frequency_save.style.display = 'none'; - this.frequency = Math.min(parseFloat(this.frequency_input.value), 250); - this.driver.setDDSFreq(0, 1e6 * this.frequency); + this.fft.getFFTWindowIndex( (windowIndex: number) => { + this.fftWindowSelect.value = windowIndex.toString(); + requestAnimationFrame( () => { this.update(); } ) + }); + }); } - saveFrequencyKey(event: KeyboardEvent) { - if (event.keyCode == 13) { - this.saveFrequency(); + updateFrequency(channel: number, event) { + let frequencyValue = event.value; + + if (event.type === 'number') { + this.frequencySliders[channel].value = frequencyValue; + } else if (event.type === 'range') { + this.frequencyInputs[channel].value = frequencyValue; } + + this.fft.setDDSFreq(channel, 1e6 * parseFloat(frequencyValue)); } - slideFrequency() { - if (this.frequency_input.style.display == 'inline') { - this.frequency_value.style.display = 'inline'; - this.frequency_input.style.display = 'none'; - this.frequency_save.style.display = 'none'; - } + setInChannel(channel: number) { + this.fft.setInChannel(channel); + } - this.frequency = parseFloat(this.frequency_slider.value); - this.driver.setDDSFreq(0, 1e6 * this.frequency); + setFFTWindow(windowIndex: number) { + this.fft.setFFTWindow(windowIndex); } -} +} \ No newline at end of file diff --git a/examples/red-pitaya/fft/web/fft.ts b/examples/red-pitaya/fft/web/fft.ts index 810767e1f..5de6aa10c 100644 --- a/examples/red-pitaya/fft/web/fft.ts +++ b/examples/red-pitaya/fft/web/fft.ts @@ -1,25 +1,43 @@ -// Interface for the DDS driver +// Interface for the FFT driver // (c) Koheron interface IFFTStatus { dds_freq: number[]; + fs: number; // Sampling frequency (Hz) + channel: number; // Input channel + W1: number; // FFT window correction (sum w)^2 + W2: number; // FFT window correction (sum w^2) } class FFT { private driver: Driver; private id: number; - private cmds: HashTable; + private cmds: Commands; + + public fft_size: number; + public status: IFFTStatus; constructor (private client: Client) { this.driver = this.client.getDriver('FFT'); this.id = this.driver.id; this.cmds = this.driver.getCmds(); //this.monitor(1000); + + this.status = {}; + this.status.dds_freq = []; + } + + init(cb: () => void): void { + this.getFFTSize( (size: number) => { + this.fft_size = size; + this.getControlParameters( () => { + cb(); + }); + }); } monitor(timeout: number): void { this.getCycleIndex( (i) => { - console.log('Cycle index ' + i.toString()); setTimeout( () => { this.monitor(timeout); }, timeout); @@ -31,6 +49,11 @@ class FFT { (i) => {cb(i)}); } + getFFTSize(cb: (size: number) => void): void { + this.client.readUint32(Command(this.id, this.cmds['get_fft_size']), + (size) => {cb(size)}); + } + read_psd(cb: (psd: Float32Array) => void): void { this.client.readFloat32Array(Command(this.id, this.cmds['read_psd']), (psd: Float32Array) => { cb(psd); @@ -41,18 +64,31 @@ class FFT { this.client.send(Command(this.id, this.cmds['set_dds_freq'], channel, freq_hz)); } - // selectPsdInput(psd_input_sel: number): void { - // this.client.send(Command(this.id, this.cmds['select_psd_input'], psd_input_sel)); - // } + setInChannel(channel: number): void { + this.client.send(Command(this.id, this.cmds['set_in_channel'], channel)); + } + + setFFTWindow(windowIndex: number): void { + this.client.send(Command(this.id, this.cmds['set_fft_window'], windowIndex)); + } getControlParameters(cb: (status: IFFTStatus) => void): void { - this.client.readTuple(Command(this.id, this.cmds['get_control_parameters']), 'dd', - (tup: [number, number, number]) => { - let status: IFFTStatus = {}; - status.dds_freq = []; - status.dds_freq[0] = tup[0]; - status.dds_freq[1] = tup[1]; - cb(status); + this.client.readTuple(Command(this.id, this.cmds['get_control_parameters']), 'dddIdd', + (tup: [number, number, number, number]) => { + this.status.dds_freq[0] = tup[0]; + this.status.dds_freq[1] = tup[1]; + this.status.fs = tup[2]; + this.status.channel = tup[3]; + this.status.W1 = tup[4]; + this.status.W2 = tup[5]; + cb(this.status); }); } -} + + getFFTWindowIndex(cb: (windowIndex: number) => void): void { + this.client.readUint32(Command(this.id, this.cmds['get_window_index']), + (windowIndex: number) => { + cb(windowIndex); + }); + } +} \ No newline at end of file diff --git a/examples/red-pitaya/fft/web/index.html b/examples/red-pitaya/fft/web/index.html index 21d3036b7..9701311d5 100644 --- a/examples/red-pitaya/fft/web/index.html +++ b/examples/red-pitaya/fft/web/index.html @@ -2,7 +2,7 @@ - Koheron + Koheron | FFT @@ -15,6 +15,7 @@ + @@ -22,58 +23,251 @@ - + -
+
-

DDS FFT

+
-

+
-
-
-
Frequency (MHz)
-
+
+
Frequency (MHz)
+ + + + + +
+ +
+ +
+ + + +
+ + + +
+ dBm / Hz +
+ +
+ dBm +
+ +
+ nV / rtHz +
+ +
+ + + +
+ + + + + +
+ + + +
+ + + +
+ 0 +
+ +
+ 1 +
-
+
-
+
+ + +
+ +
+ + + +
+ +
+ + DDS Frequency (MHz) + +
+ + + + + + + + + + + + +
+ OUT 0 + + + + +
+ OUT 1 + + + + +
+ +
+ +
+ +
+ File +
+ +
+ + + + + + + + + + +
+ +
+ +
+ +
+ Laser +
+ + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + +
Power (µW) + + + +
+ +
-
-       DDS frequency (MHz) - ? : - 0 - - -
- +
-

+
-

- This instrument was built with Koheron Software Development Kit. -

+

+ See available instruments.
+ This instrument was built with Koheron Software Development Kit. +

-
+ +
+ +
+ + \ No newline at end of file diff --git a/examples/red-pitaya/fft/web/plot.ts b/examples/red-pitaya/fft/web/plot.ts index 1f6997c12..a5422ce52 100644 --- a/examples/red-pitaya/fft/web/plot.ts +++ b/examples/red-pitaya/fft/web/plot.ts @@ -2,10 +2,8 @@ // (c) Koheron class Plot { + private n_pts: number; - private n_pts : number = 1024; - - private plot_x_max: number = 62.5; private min_y: number = -200; private max_y: number = 170; @@ -16,24 +14,61 @@ class Plot { private options: jquery.flot.plotOptions; private plot: jquery.flot.plot; - constructor(document: Document, - private plot_placeholder: JQuery, - private driver: FFT) { + private yUnit: string; + private yLabel: string; + + private plot_data: Array>; + + private isMeasure: boolean = true; + + private isPeakDetection: boolean = true; + private peakDatapointSpan: HTMLSpanElement; + private peakDatapoint: number[]; + + private hoverDatapointSpan: HTMLSpanElement; + private hoverDatapoint: number[]; + + private clickDatapointSpan: HTMLSpanElement; + private clickDatapoint: number[]; + + private exportDataFilename: HTMLLinkElement; + private exportPlotFilename: HTMLLinkElement; + + constructor(document: Document, private plot_placeholder: JQuery, private fft: FFT, private control: Control) { this.setPlot(); this.range_x = {}; this.range_x.from = 0; - this.range_x.to = this.plot_x_max; + this.range_x.to = this.fft.status.fs / 1E6 / 2; this.range_y = {}; this.range_y.from = this.min_y; this.range_y.to = this.max_y; + this.n_pts = this.fft.fft_size / 2; + + this.yUnit = "dBm-Hz"; + this.yLabel = "Power Spectral Density"; + + this.hoverDatapointSpan = document.getElementById("hover-datapoint"); + this.hoverDatapoint = []; + + this.clickDatapointSpan = document.getElementById("click-datapoint"); + this.clickDatapoint = []; + + this.peakDatapointSpan = document.getElementById("peak-datapoint"); + this.peakDatapoint = []; + + this.exportDataFilename = document.getElementById("export-data-filename"); + this.exportPlotFilename = document.getElementById("export-plot-filename"); + + this.plot_data = []; + this.update_plot(); } update_plot() { - this.driver.read_psd( (psd: Float32Array) => { + this.fft.read_psd( (psd: Float32Array) => { this.redraw(psd, () => { requestAnimationFrame( () => { this.update_plot(); } ); }); @@ -43,7 +78,11 @@ class Plot { setPlot() { this.reset_range = false; + let labelAttribute: string = ""; + labelAttribute += " style='font-size: 16px; color: #333'"; + this.options = { + canvas: true, series: { shadowSize: 0 // Drawing is faster without shadows }, @@ -53,22 +92,30 @@ class Plot { }, xaxis: { min: 0, - max: this.plot_x_max, + max: this.fft.status.fs / 1E6 / 2, show: true }, grid: { margin: { - left: 15 - } + top: 0, + left: 0, + }, + borderColor: "#d5d5d5", + borderWidth: 1, + clickable: this.isMeasure, + hoverable: this.isMeasure, + autoHighlight: true }, selection: { mode: "xy" }, - colors: ["#0022FF", "#006400"], + colors: ["#019cd5", "#006400"], legend: { show: true, noColumns: 0, - labelFormatter: (label: string, series: any): string => {return '' + label + '\t'}, + labelFormatter: (label: string, series: any): string => { + return "" + label + "\t" + }, margin: 0, position: "ne", } @@ -76,6 +123,10 @@ class Plot { this.rangeSelect(); this.dblClick(); + this.onWheel(); + this.showHoverPoint(); + this.showClickPoint(); + this.plotLeave(); this.reset_range = true; } @@ -105,7 +156,7 @@ class Plot { dblClick() { this.plot_placeholder.bind("dblclick", (evt: JQueryEventObject) => { this.range_x.from = 0; - this.range_x.to = this.plot_x_max; + this.range_x.to = this.fft.status.fs / 1E6 / 2; this.range_y = {}; this.resetRange(); @@ -116,39 +167,262 @@ class Plot { this.reset_range = true; } + convertValue(inValue: number, outUnit: string): number { + // inValue in W / Hz + let outValue: number = 0; + + if (outUnit === "dBm-Hz") { + outValue = 10 * Math.log(inValue / 1E-3) / Math.LN10; + } else if (outUnit === "dBm") { + outValue = 10 * Math.log(inValue * (this.fft.status.W2 / this.fft.status.W1) * this.fft.status.fs / this.fft.fft_size / 1E-3) / Math.LN10; + } else if (outUnit === "nv-rtHz") { + outValue = Math.sqrt(50 * inValue) * 1E9; + } + + return outValue; + } + + updateDatapointSpan(datapoint: number[], datapointSpan: HTMLSpanElement): void { + let positionX: number = (this.plot.pointOffset({x: datapoint[0], y: datapoint[1] })).left; + let positionY: number = (this.plot.pointOffset({x: datapoint[0], y: datapoint[1] })).top; + + datapointSpan.innerHTML = "(" + (datapoint[0].toFixed(2)).toString() + "," + datapoint[1].toFixed(2).toString() + ")"; + + if (datapoint[0] < (this.range_x.from + this.range_x.to) / 2) { + datapointSpan.style.left = (positionX + 5).toString() + "px"; + } else { + datapointSpan.style.left = (positionX - 140).toString() + "px"; + } + + if (datapoint[1] < ( (this.range_y.from + this.range_y.to) / 2 ) ) { + datapointSpan.style.top = (positionY - 50).toString() + "px"; + } else { + datapointSpan.style.top = (positionY + 5).toString() + "px"; + } + } + redraw(psd: Float32Array, callback: () => void) { - let plot_data: Array> = []; - - for (var i: number = 0; i <= this.n_pts; i++) { - let navg: number = 2048; - let fft_size = 2048; - let fs: number = 125E6; - let lpsd_volts: number = Math.sqrt((1 / fs) * psd[i] / navg) * 16 / 8192 / 2048 * 2.12; - let psd_watt = lpsd_volts * lpsd_volts / 50; // W / Hz - plot_data[i] = [i * this.plot_x_max / this.n_pts, lpsd_volts * 1E9 ]; // nV/rtHz - //plot_data[i] = [i * this.plot_x_max / this.n_pts, 10 * Math.log(psd_watt / 1E-3) / Math.LN10 ]; // dBm/Hz - //plot_data[i] = [i * this.plot_x_max / this.n_pts, 10 * Math.log(psd_watt * fs / fft_size / 1E-3) / Math.LN10 ]; // dBm - - //plot_data[i] = [i * this.plot_x_max / this.n_pts, 10 * Math.log(psd[i]) / Math.LN10 ]; + // let plot_data: Array> = []; + + this.peakDatapoint = [ this.fft.status.fs / 1E6 / 2 / this.n_pts , this.convertValue(psd[0], this.yUnit)]; + + for (let i: number = 0; i <= this.n_pts; i++) { + + let freq: number = (i + 1) * this.fft.status.fs / 1E6 / 2 / this.n_pts; // MHz + let convertedPsd: number = this.convertValue(psd[i], this.yUnit); + this.plot_data[i] = [freq, convertedPsd]; + + if (this.peakDatapoint[1] < this.plot_data[i][1]) { + this.peakDatapoint[0] = this.plot_data[i][0]; + this.peakDatapoint[1] = this.plot_data[i][1]; + } + } - const plt_data: jquery.flot.dataSeries[] - = [{label: "Power spectral density (dBm/Hz)", data: plot_data}]; + const plt_data: jquery.flot.dataSeries[] = [{label: this.yLabel, data: this.plot_data}]; if (this.reset_range) { this.options.xaxis.min = this.range_x.from; this.options.xaxis.max = this.range_x.to; this.options.yaxis.min = this.range_y.from; this.options.yaxis.max = this.range_y.to; - this.plot = $.plot(this.plot_placeholder, plt_data, this.options); this.plot.setupGrid(); + + this.range_y.from = this.plot.getAxes().yaxis.min; + this.range_y.to = this.plot.getAxes().yaxis.max; + this.reset_range = false; } else { this.plot.setData(plt_data); this.plot.draw(); } + let localData: jquery.flot.dataSeries[] = this.plot.getData(); + + setTimeout(this.plot.unhighlight(), 100); + + if (this.clickDatapoint.length > 0) { + + for (var i: number = 0; i < this.n_pts; i++) { + if ( localData[0]['data'][i][0] > this.clickDatapoint[0] ) { + break; + } + } + + var p1 = localData[0]['data'][i-1]; + var p2 = localData[0]['data'][i]; + + if (p1 == null) { + this.clickDatapoint[1] = p2[1]; + } else if (p2 == null) { + this.clickDatapoint[1] = p1[1]; + } else { + this.clickDatapoint[1] = p1[1] + (p2[1] - p1[1]) * (this.clickDatapoint[0] - p1[0]) / (p2[0] - p1[0]); + } + + if ( this.range_x.from < this.clickDatapoint[0] && this.clickDatapoint[0] < this.range_x.to && + this.range_y.from < this.clickDatapoint[1] && this.clickDatapoint[1] < this.range_y.to ) { + this.updateDatapointSpan(this.clickDatapoint, this.clickDatapointSpan); + this.clickDatapointSpan.style.display = "inline-block"; + this.plot.highlight(localData[0], this.clickDatapoint); + } else { + this.clickDatapointSpan.style.display = "none"; + } + + } + + if (this.isPeakDetection) { + + this.plot.unhighlight(localData[0], this.peakDatapoint); + + if (this.range_x.from < this.peakDatapoint[0] && this.peakDatapoint[0] < this.range_x.to && + this.range_y.from < this.peakDatapoint[1] && this.peakDatapoint[1] < this.range_y.to ) { + this.updateDatapointSpan(this.peakDatapoint, this.peakDatapointSpan); + this.plot.highlight(localData[0], this.peakDatapoint); + this.peakDatapointSpan.style.display = "inline-block"; + } else { + + this.plot.unhighlight(localData[0], this.peakDatapoint); + this.peakDatapointSpan.style.display = "none"; + } + + } else { + + this.plot.unhighlight(localData[0], this.peakDatapoint); + this.peakDatapointSpan.style.display = "none"; + + } + callback(); } + + onWheel(): void { + this.plot_placeholder.bind("wheel", (evt: JQueryEventObject) => { + let delta: number = (evt.originalEvent).deltaX + + (evt.originalEvent).deltaY; + delta /= Math.abs(delta); + + const zoomRatio: number = 0.2; + + if ((evt.originalEvent).shiftKey) { // Zoom Y + const positionY: number = (evt.originalEvent).pageY - this.plot.offset().top; + const y0: any = this.plot.getAxes().yaxis.c2p(positionY); + + this.range_y = { + from: y0 - (1 + zoomRatio * delta) * (y0 - this.plot.getAxes().yaxis.min), + to: y0 - (1 + zoomRatio * delta) * (y0 - this.plot.getAxes().yaxis.max) + }; + + this.resetRange(); + return false; + } else if ((evt.originalEvent).altKey) { // Zoom X + const positionX: number = (evt.originalEvent).pageX - this.plot.offset().left; + const x0: any = this.plot.getAxes().xaxis.c2p(positionX); + + if (x0 < 0 || x0 > this.fft.status.fs / 1E6 / 2) { + return; + } + + this.range_x = { + from: Math.max(x0 - (1 + zoomRatio * delta) * (x0 - this.plot.getAxes().xaxis.min), 0), + to: Math.min(x0 - (1 + zoomRatio * delta) * (x0 - this.plot.getAxes().xaxis.max), this.fft.status.fs / 1E6 / 2) + }; + + this.resetRange(); + return false; + } + + return true + }); + } + + changeYUnit(yUnit: string): void { + this.yUnit = yUnit; + this.resetRange(); + } + + detectPeak(): void { + if (this.isPeakDetection) { + this.isPeakDetection = false; + } else { + this.isPeakDetection = true; + } + } + + showHoverPoint(): void { + this.plot_placeholder.bind("plothover", (event: JQueryEventObject, pos, item) => { + if (item) { + + this.hoverDatapoint[0] = item.datapoint[0]; + this.hoverDatapoint[1] = item.datapoint[1]; + + this.hoverDatapointSpan.style.display = "inline-block"; + this.updateDatapointSpan(this.hoverDatapoint, this.hoverDatapointSpan); + + } else { + this.hoverDatapointSpan.style.display = "none"; + } + + }); + } + + showClickPoint(): void { + this.plot_placeholder.bind("plotclick", (event: JQueryEventObject, pos, item) => { + if (item) { + + this.clickDatapoint[0] = item.datapoint[0]; + this.clickDatapoint[1] = item.datapoint[1]; + + this.clickDatapointSpan.style.display = "inline-block"; + this.updateDatapointSpan(this.clickDatapoint, this.clickDatapointSpan); + + this.plot.unhighlight(); + this.plot.highlight(item.series, this.clickDatapoint); + + } + }); + } + + plotLeave(): void { + this.plot_placeholder.bind("mouseleave", (event: JQueryEventObject, pos, item) => { + this.hoverDatapointSpan.style.display = "none"; + }); + } + + exportData() { + let csvContent = "data:text/csv;charset=utf-8,"; + + csvContent += "Koheron Alpha \n"; + + var dateTime = new Date(); + csvContent += dateTime.getDate() + "/" + (dateTime.getMonth()+1) + "/" + dateTime.getFullYear() + " " ; + csvContent += dateTime.getHours() + ":" + dateTime.getMinutes() + ":" + dateTime.getSeconds() + "\n"; + + csvContent += "\n"; + csvContent += '"Window",' + (this.control.fftWindowIndex).toString() + "\n"; + csvContent += '"Input channel",' + (this.fft.status.channel).toString() + "\n"; + csvContent += '"Channel 0 DDS frequency (MHz)",' + (this.fft.status.dds_freq[0] / 1e6).toString() + "\n"; + csvContent += '"Channel 1 DDS frequency (MHz)",' + (this.fft.status.dds_freq[1] / 1e6).toString() + "\n"; + + csvContent += "\n\n"; + + csvContent += '"Frequency (MHz)","Power spectral density (' + this.yUnit.replace("-", "/") + ')" \n'; + + this.plot_data.forEach((rowArray) => { + let row = rowArray.join(","); + csvContent += row + "\n"; + }); + + this.exportDataFilename.href = encodeURI(csvContent); + this.exportDataFilename.click(); + } + + exportPlot(): void { + var canvas = this.plot.getCanvas(); + var imagePng = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); + + this.exportPlotFilename.href = imagePng; + this.exportPlotFilename.click(); + } } \ No newline at end of file diff --git a/examples/red-pitaya/fft/xdc/clocks.xdc b/examples/red-pitaya/fft/xdc/clocks.xdc deleted file mode 100644 index 573efddfa..000000000 --- a/examples/red-pitaya/fft/xdc/clocks.xdc +++ /dev/null @@ -1,5 +0,0 @@ -set_false_path -from [get_clocks clk_out1_system_pll_0] -to [get_clocks clk_out2_system_pll_0] -set_false_path -from [get_clocks clk_fpga_0] -to [get_clocks clk_out3_system_pll_0] -set_false_path -from [get_clocks clk_fpga_0] -to [get_clocks clk_out1_system_pll_0] -set_false_path -from [get_clocks clk_out2_system_pll_0] -to [get_clocks clk_out3_system_pll_0] -set_false_path -from [get_clocks clk_out1_system_pll_0] -to [get_clocks clk_out4_system_pll_0] \ No newline at end of file diff --git a/examples/red-pitaya/fft/xdc/ports.xdc b/examples/red-pitaya/fft/xdc/ports.xdc deleted file mode 100644 index e7a732f96..000000000 --- a/examples/red-pitaya/fft/xdc/ports.xdc +++ /dev/null @@ -1,114 +0,0 @@ -set_property CFGBVS VCCO [current_design] -set_property CONFIG_VOLTAGE 3.3 [current_design] - -### ADC - -# data - -set_property IOSTANDARD LVCMOS18 [get_ports {adc_dat_a_i[*]}] -set_property IOB TRUE [get_ports {adc_dat_a_i[*]}] - -set_property PACKAGE_PIN Y17 [get_ports {adc_dat_a_i[0]}] -set_property PACKAGE_PIN W16 [get_ports {adc_dat_a_i[1]}] -set_property PACKAGE_PIN Y16 [get_ports {adc_dat_a_i[2]}] -set_property PACKAGE_PIN W15 [get_ports {adc_dat_a_i[3]}] -set_property PACKAGE_PIN W14 [get_ports {adc_dat_a_i[4]}] -set_property PACKAGE_PIN Y14 [get_ports {adc_dat_a_i[5]}] -set_property PACKAGE_PIN W13 [get_ports {adc_dat_a_i[6]}] -set_property PACKAGE_PIN V12 [get_ports {adc_dat_a_i[7]}] -set_property PACKAGE_PIN V13 [get_ports {adc_dat_a_i[8]}] -set_property PACKAGE_PIN T14 [get_ports {adc_dat_a_i[9]}] -set_property PACKAGE_PIN T15 [get_ports {adc_dat_a_i[10]}] -set_property PACKAGE_PIN V15 [get_ports {adc_dat_a_i[11]}] -set_property PACKAGE_PIN T16 [get_ports {adc_dat_a_i[12]}] -set_property PACKAGE_PIN V16 [get_ports {adc_dat_a_i[13]}] - -set_property IOSTANDARD LVCMOS18 [get_ports {adc_dat_b_i[*]}] -set_property IOB TRUE [get_ports {adc_dat_b_i[*]}] - -set_property PACKAGE_PIN R18 [get_ports {adc_dat_b_i[0]}] -set_property PACKAGE_PIN P16 [get_ports {adc_dat_b_i[1]}] -set_property PACKAGE_PIN P18 [get_ports {adc_dat_b_i[2]}] -set_property PACKAGE_PIN N17 [get_ports {adc_dat_b_i[3]}] -set_property PACKAGE_PIN R19 [get_ports {adc_dat_b_i[4]}] -set_property PACKAGE_PIN T20 [get_ports {adc_dat_b_i[5]}] -set_property PACKAGE_PIN T19 [get_ports {adc_dat_b_i[6]}] -set_property PACKAGE_PIN U20 [get_ports {adc_dat_b_i[7]}] -set_property PACKAGE_PIN V20 [get_ports {adc_dat_b_i[8]}] -set_property PACKAGE_PIN W20 [get_ports {adc_dat_b_i[9]}] -set_property PACKAGE_PIN W19 [get_ports {adc_dat_b_i[10]}] -set_property PACKAGE_PIN Y19 [get_ports {adc_dat_b_i[11]}] -set_property PACKAGE_PIN W18 [get_ports {adc_dat_b_i[12]}] -set_property PACKAGE_PIN Y18 [get_ports {adc_dat_b_i[13]}] - -# clock input - -set_property IOSTANDARD DIFF_HSTL_I_18 [get_ports adc_clk_p_i] -set_property IOSTANDARD DIFF_HSTL_I_18 [get_ports adc_clk_n_i] -set_property PACKAGE_PIN U18 [get_ports adc_clk_p_i] -set_property PACKAGE_PIN U19 [get_ports adc_clk_n_i] - -# clock output - -set_property IOSTANDARD LVCMOS18 [get_ports {adc_clk_source[*]}] -set_property SLEW FAST [get_ports {adc_clk_source[*]}] -set_property DRIVE 8 [get_ports {adc_clk_source[*]}] - -set_property PACKAGE_PIN N20 [get_ports {adc_clk_source[0]}] -set_property PACKAGE_PIN P20 [get_ports {adc_clk_source[1]}] - -# clock duty cycle stabilizer (CSn) - -set_property IOSTANDARD LVCMOS18 [get_ports adc_cdcs_o] -set_property PACKAGE_PIN V18 [get_ports adc_cdcs_o] -set_property SLEW FAST [get_ports adc_cdcs_o] -set_property DRIVE 8 [get_ports adc_cdcs_o] - -### DAC - -# data - -set_property IOSTANDARD LVCMOS33 [get_ports {dac_dat_o[*]}] -set_property SLEW SLOW [get_ports {dac_dat_o[*]}] -set_property DRIVE 4 [get_ports {dac_dat_o[*]}] - -set_property PACKAGE_PIN M19 [get_ports {dac_dat_o[0]}] -set_property PACKAGE_PIN M20 [get_ports {dac_dat_o[1]}] -set_property PACKAGE_PIN L19 [get_ports {dac_dat_o[2]}] -set_property PACKAGE_PIN L20 [get_ports {dac_dat_o[3]}] -set_property PACKAGE_PIN K19 [get_ports {dac_dat_o[4]}] -set_property PACKAGE_PIN J19 [get_ports {dac_dat_o[5]}] -set_property PACKAGE_PIN J20 [get_ports {dac_dat_o[6]}] -set_property PACKAGE_PIN H20 [get_ports {dac_dat_o[7]}] -set_property PACKAGE_PIN G19 [get_ports {dac_dat_o[8]}] -set_property PACKAGE_PIN G20 [get_ports {dac_dat_o[9]}] -set_property PACKAGE_PIN F19 [get_ports {dac_dat_o[10]}] -set_property PACKAGE_PIN F20 [get_ports {dac_dat_o[11]}] -set_property PACKAGE_PIN D20 [get_ports {dac_dat_o[12]}] -set_property PACKAGE_PIN D19 [get_ports {dac_dat_o[13]}] - -# control - -set_property IOSTANDARD LVCMOS33 [get_ports dac_*_o] -set_property SLEW FAST [get_ports dac_*_o] -set_property DRIVE 8 [get_ports dac_*_o] - -set_property PACKAGE_PIN M17 [get_ports dac_wrt_o] -set_property PACKAGE_PIN N16 [get_ports dac_sel_o] -set_property PACKAGE_PIN M18 [get_ports dac_clk_o] -set_property PACKAGE_PIN N15 [get_ports dac_rst_o] - -### LED - -set_property IOSTANDARD LVCMOS33 [get_ports {led_o[*]}] -set_property SLEW SLOW [get_ports {led_o[*]}] -set_property DRIVE 4 [get_ports {led_o[*]}] - -set_property PACKAGE_PIN F16 [get_ports {led_o[0]}] -set_property PACKAGE_PIN F17 [get_ports {led_o[1]}] -set_property PACKAGE_PIN G15 [get_ports {led_o[2]}] -set_property PACKAGE_PIN H15 [get_ports {led_o[3]}] -set_property PACKAGE_PIN K14 [get_ports {led_o[4]}] -set_property PACKAGE_PIN G14 [get_ports {led_o[5]}] -set_property PACKAGE_PIN J15 [get_ports {led_o[6]}] -set_property PACKAGE_PIN J14 [get_ports {led_o[7]}] \ No newline at end of file diff --git a/web/downloads.mk b/web/downloads.mk index 06d3fdb88..ce1fbab3c 100644 --- a/web/downloads.mk +++ b/web/downloads.mk @@ -5,10 +5,10 @@ WEB_DOWNLOADS += $(TMP_WEB_PATH)/jquery.flot.resize.js WEB_DOWNLOADS += $(TMP_WEB_PATH)/jquery.flot.selection.js WEB_DOWNLOADS += $(TMP_WEB_PATH)/jquery.flot.time.js WEB_DOWNLOADS += $(TMP_WEB_PATH)/jquery.flot.axislabels.js +WEB_DOWNLOADS += $(TMP_WEB_PATH)/jquery.flot.canvas.js WEB_DOWNLOADS += $(TMP_WEB_PATH)/bootstrap.min.js WEB_DOWNLOADS += $(TMP_WEB_PATH)/bootstrap.min.css WEB_DOWNLOADS += $(TMP_WEB_PATH)/jquery.min.js -WEB_DOWNLOADS += $(TMP_WEB_PATH)/_koheron.svg WEB_DOWNLOADS += $(TMP_WEB_PATH)/_koheron_logo.svg WEB_DOWNLOADS += $(TMP_WEB_PATH)/_koheron.png WEB_DOWNLOADS += $(TMP_WEB_PATH)/kbird.ico @@ -41,6 +41,10 @@ $(TMP_WEB_PATH)/jquery.flot.axislabels.js: mkdir -p $(@D) curl https://raw.githubusercontent.com/markrcote/flot-axislabels/master/jquery.flot.axislabels.js -o $@ +$(TMP_WEB_PATH)/jquery.flot.canvas.js: + mkdir -p $(@D) + curl https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.canvas.js -o $@ + $(TMP_WEB_PATH)/bootstrap.min.js: mkdir -p $(@D) curl http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js -o $@ @@ -53,10 +57,6 @@ $(TMP_WEB_PATH)/jquery.min.js: mkdir -p $(@D) curl https://code.jquery.com/jquery-3.2.0.min.js -o $@ -$(TMP_WEB_PATH)/_koheron.svg: - mkdir -p $(@D) - curl https://www.koheron.com/static/images/website/koheron.svg -o $@ - $(TMP_WEB_PATH)/_koheron.png: mkdir -p $(@D) curl https://www.koheron.com/static/images/website/koheron.png -o $@