/* *************************************************************************
* Copyright 2020
* ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
* P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* *********************************************************************** */

/* *************************************************************************
* Author:
* . Leon Hiemstra
* . Pieter Donker
* Purpose:
* . opc-ua to ucp translator
* Description:
* . class for fpga registers (peripherals) that need some more actions
* *********************************************************************** */

#ifndef _REENTRANT
#error ACK! You need to compile with _REENTRANT defined since this uses threads
#endif

#include <string>
#include <cstdint>
#include <algorithm>
#include <cstdlib>
#include <cmath>

#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <exception>
#include <unistd.h>

#include <sys/time.h>
#include <arpa/inet.h> // htons, inet_pton

#include "fpga.h"
#include "../tools/mmap.h"
#include "../tools/util.h"

#include<fstream>

using namespace std;

extern int debug;

#define R_MEM 0  // read mem value
#define R_UCP 1  // read value from hardware

Periph_fpga::Periph_fpga(uint global_nr, string ipaddr, uint n_beamsets):
    GlobalNr(global_nr),
    nBeamsets(n_beamsets),
    Masked(false),
    Online(false),
    my_current_design_name("-"),
    my_current_hw_version(0),
    my_current_fw_version("-.-"),
    my_bsn_input_sync_timeout(false),
    my_bsn_input_bsn(0),
    my_xst_input_bsn_at_sync(0),
    my_xst_output_sync_bsn(0),
    my_bsn_input_nof_packets(0),
    my_bsn_input_nof_valid(0),
    my_bsn_input_nof_err(0),
    my_jesd_csr_rbd_count {0},
    my_jesd_csr_dev_syncn {0},
    my_jesd_rx_err0 {0},
    my_jesd_rx_err1 {0},
    my_signal_input_mean {0.0},
    my_signal_input_rms {0.0},
    my_pps_offset_cnt(0),
    my_pps_expected_cnt(0),
    my_pps_present(false),
    my_pps_capture_cnt(0)
{
    ucp = new UCP(ipaddr);
    mmap = new CMMap();

    // flash settings
    Flash_fact_sector_start = 0;
    Flash_fact_sector_end   = 159;
    Flash_user_sector_start = 160;
    Flash_user_sector_end   = 319;

    Flash_pages_per_sector  = 1024;
    Flash_page_size_bytes   = 256;

    Flash_page_start = Flash_user_sector_start * Flash_pages_per_sector;
    Flash_select = 1; // default is user

    #define FLASH_EPCS_REG_ADDR         0
    #define FLASH_EPCS_REG_RDEN         1
    #define FLASH_EPCS_REG_READ         2
    #define FLASH_EPCS_REG_WRITE        3
    #define FLASH_EPCS_REG_SECTOR_ERASE 4
    #define FLASH_EPCS_REG_BUSY         5
    #define FLASH_EPCS_REG_UNPROTECT    6
}

Periph_fpga::~Periph_fpga()
{
//    rbf_wf.close();
    if (ucp != NULL) delete ucp;
    if (mmap != NULL) delete mmap;
}

/*
* read(), this function is called from Node::worker() thread.
*/
bool Periph_fpga::read(TermOutput& termout, const string addr,
                       const string type, char *data, const uint nvalues,
                       const int format)
{
    // cout << "node " << GlobalNr << ", Online=" << Online << ", Masked=" << Masked << endl;
    bool retval = false;

    termout.datatype = format;
    termout.nof_vals = 0;

    if (addr == "fpga/global_node_index") {
        retval = read_global_node_index(termout, format);
        return retval;
    }

    if (!Masked) {  // Not selected
        return false;
    }
    if (!Online) {  // Selected but not possible
        cout << "read() error node " << GlobalNr << " not enabled or not online" << endl;
        return false;
    }
    if (mmap->empty()) {
        cout << "read() error node " << GlobalNr << " empty mmap" << endl;
        Online = false;
        return false;
    }

    if (type == "mm") {
        uint32_t *data_ptr = (uint32_t *)data;
        retval = Read(addr, data_ptr, true);
    }
    else { // "fpga/..."

        if (addr == "fpga/system") {
            retval = read_system_info(termout);
        }
        else if (addr == "fpga/name") {
            termout.nof_vals = 1; //my_current_design_name.size();
            strcpy(termout.val, my_current_design_name.c_str());
            retval = true;
        }
        else if (addr == "fpga/stamps") {
            retval = read_stamps(termout, format);
        }
        else if (addr == "fpga/note") {
            retval = true;
        }
        else if (addr == "fpga/firmware_version") {
            retval = read_firmware_version(termout, format);
        }
        else if (addr == "fpga/hardware_version") {
            retval = read_hardware_version(termout, format);
        }
        else if (addr == "fpga/temp") {
            retval = read_fpga_temperature(termout, format);
        }
        else if (addr == "fpga/epcs_wait_busy") {
            retval = wait_while_epcs_busy(1000);
        }
        else if (addr == "fpga/scrap") {
            retval = read_fpga_scrap(termout, format);
        }
        else if (addr == "fpga/weights") {
            retval = read_fpga_weights(termout, format);
        }
        else if (addr == "fpga/sst_offload_weighted_subbands") {
            retval = read_all_from_port(termout, "REG_DP_SELECTOR", "input_select", format);
        }
        else if (addr == "fpga/sst_offload_enable") {
            retval = read_all_from_port(termout, "REG_STAT_ENABLE_SST", "enable", format);
        }
        else if (addr == "fpga/sst_offload_hdr_eth_destination_mac") {
            retval = read_eth_destination_mac(termout, "REG_STAT_HDR_DAT_SST", 1, format);
        }
        else if (addr == "fpga/sst_offload_hdr_ip_destination_address") {
            retval = read_ip_destination_address(termout, "REG_STAT_HDR_DAT_SST", 1, format);
        }
        else if (addr == "fpga/sst_offload_hdr_udp_destination_port") {
            retval = read_all_from_port(termout, "REG_STAT_HDR_DAT_SST", "udp_destination_port", format);
        }
        else if (addr == "fpga/bst_offload_enable") {
            retval = read_all_from_port(termout, "REG_STAT_ENABLE_BST", "enable", format);
        }
        else if (addr == "fpga/bst_offload_hdr_eth_destination_mac") {
            retval = read_eth_destination_mac(termout, "REG_STAT_HDR_DAT_BST", nBeamsets, format);
        }
        else if (addr == "fpga/bst_offload_hdr_ip_destination_address") {
            retval = read_ip_destination_address(termout, "REG_STAT_HDR_DAT_BST", nBeamsets, format);
        }
        else if (addr == "fpga/bst_offload_hdr_udp_destination_port") {
            retval = read_all_from_port(termout,"REG_STAT_HDR_DAT_BST", "udp_destination_port", format);
            termout.nof_vals = nBeamsets;
        }
        else if (addr == "fpga/xst_offload_enable") {
            retval = read_all_from_port(termout, "REG_STAT_ENABLE_XST", "enable", format);
        }
        else if (addr == "fpga/xst_offload_nof_crosslets") {
            retval = read_all_from_port(termout, "REG_NOF_CROSSLETS", "nof_crosslets", format);
        }
        else if (addr == "fpga/xst_offload_hdr_eth_destination_mac") {
            retval = read_eth_destination_mac(termout, "REG_STAT_HDR_DAT_XST", 1, format);
        }
        else if (addr == "fpga/xst_offload_hdr_ip_destination_address") {
            retval = read_ip_destination_address(termout, "REG_STAT_HDR_DAT_XST", 1, format);
        }
        else if (addr == "fpga/xst_offload_hdr_udp_destination_port") {
            retval = read_all_from_port(termout, "REG_STAT_HDR_DAT_XST", "udp_destination_port", format);
        }
        else if (addr == "fpga/xst_processing_enable") {
            retval = read_xst_processing_enable(termout, format);
        }
        else if (addr == "fpga/xst_integration_interval") {
            retval = read_xst_integration_interval(termout, format);
        }
        else if (addr == "fpga/xst_subband_select") {
            retval = read_xst_subband_select(termout, format);
        }
        else if (addr == "fpga/xst_input_sync_at_bsn") {
            retval = read_xst_input_sync_at_bsn(termout, format, R_MEM);
        }
        else if (addr == "fpga/xst_output_sync_bsn") {
            retval = read_xst_output_sync_bsn(termout, format, R_MEM);
        }
        else if (addr == "fpga/beamlet_output_enable") {
            retval = read_all_from_port(termout, "REG_DP_XONOFF", "enable_stream", format);
            termout.nof_vals = nBeamsets;
        }
        else if (addr == "fpga/beamlet_output_scale") {
            retval = read_beamlet_output_scale(termout, format);
        }
        else if (addr == "fpga/beamlet_output_nof_beamlets") {
            retval = read_beamlet_output_nof_beamlets(termout, format);
        }
        else if (addr == "fpga/beamlet_output_hdr_eth_destination_mac") {
            retval = read_eth_destination_mac(termout, "REG_HDR_DAT", nBeamsets, format);
        }
        else if (addr == "fpga/beamlet_output_hdr_ip_destination_address") {
            retval = read_ip_destination_address(termout, "REG_HDR_DAT", nBeamsets, format);
        }
        else if (addr == "fpga/beamlet_output_hdr_udp_destination_port") {
            retval = read_all_from_port(termout, "REG_HDR_DAT", "udp_destination_port", format);
            termout.nof_vals = nBeamsets;
        }
        else if (addr == "fpga/processing_enable") {
            retval = read_all_from_port(termout, "REG_BSN_SOURCE_V2", "dp_on", format);
        }
        else if (addr == "fpga/sdp_info_station_id") {
            retval = read_all_from_port(termout, "REG_SDP_INFO", "station_id", format);
        }
        else if (addr == "fpga/sdp_info_observation_id") {
            retval = read_all_from_port(termout, "REG_SDP_INFO", "observation_id", format);
        }
        else if (addr == "fpga/sdp_info_nyquist_sampling_zone_index") {
            retval = read_all_from_port(termout,"REG_SDP_INFO", "nyquist_zone_index", format);
        }
        else if (addr == "fpga/sdp_info_antenna_band_index") {
            retval = read_all_from_port(termout, "REG_SDP_INFO", "antenna_band_index", format);
        }
        else if (addr == "fpga/sdp_info_f_adc") {
            retval = read_all_from_port(termout, "REG_SDP_INFO", "f_adc", format);
        }
        else if (addr == "fpga/sdp_info_fsub_type") {
            retval = read_all_from_port(termout, "REG_SDP_INFO", "fsub_type", format);
        }
        else if (addr == "fpga/sdp_info_block_period") {
            retval = read_all_from_port(termout, "REG_SDP_INFO", "block_period", format);
        }
        else if (addr == "fpga/wg_enable") {
            retval = read_all_from_port(termout, "REG_WG", "mode", format);
        }
        else if (addr == "fpga/wg_amplitude") {
            retval = read_wg_amplitude(termout, format);
        }
        else if (addr == "fpga/wg_phase") {
            retval = read_wg_phase(termout, format);
        }
        else if (addr == "fpga/wg_frequency") {
            retval = read_wg_frequency(termout, format);
        }
        else if (addr == "fpga/bsn_monitor_input_bsn") {
            retval = read_bsn_monitor_input_bsn(termout, format, R_MEM);
        }
        else if (addr == "fpga/bsn_monitor_input_nof_packets") {
            retval = read_bsn_monitor_input_nof_packets(termout, format, R_MEM);
        }
        else if (addr == "fpga/bsn_monitor_input_nof_valid") {
            retval = read_bsn_monitor_input_nof_valid(termout, format, R_MEM);
        }
        else if (addr == "fpga/bsn_monitor_input_nof_err") {
            retval = read_bsn_monitor_input_nof_err(termout, format, R_MEM);
        }
        else if (addr == "fpga/jesd204b_csr_rbd_count") {
            retval = read_jesd204b_csr_rbd_count(termout, format, R_MEM);
        }
        else if (addr == "fpga/jesd204b_csr_dev_syncn") {
            retval = read_jesd204b_csr_dev_syncn(termout, format, R_MEM);
        }
        else if (addr == "fpga/jesd204b_rx_err0") {
            retval = read_jesd204b_rx_err0(termout, format, R_MEM);
        }
        else if (addr == "fpga/jesd204b_rx_err1") {
            retval = read_jesd204b_rx_err1(termout, format, R_MEM);
        }
        else if (addr == "fpga/signal_input_mean") {
            retval = read_signal_input_mean(termout, format, R_MEM);
        }
        else if (addr == "fpga/signal_input_rms") {
            retval = read_signal_input_rms(termout, format, R_MEM);
        }
        else if (addr == "fpga/signal_input_samples_delay") {
            retval = read_all_from_port(termout, "REG_DP_SHIFTRAM", "shift", format);
        }
        else if (addr == "fpga/subband_weights") {
            retval = read_all_from_port(termout, "RAM_EQUALIZER_GAINS", "data", format);
        }
        else if (addr == "fpga/signal_input_data_buffer") {
            // retval = read_all_from_port(termout, "RAM_DIAG_DATA_BUFFER_BSN", "data", format);
            retval = read_signal_input_data_buffer(termout, format);
        }
        else if (addr == "fpga/signal_input_histogram") {
            retval = read_all_from_port(termout, "RAM_ST_HISTOGRAM", "data", format);
        }
        else if (addr == "fpga/subband_spectral_inversion") {
            retval = read_all_from_port(termout, "REG_SI", "enable", format);
        }
        else if (addr == "fpga/pps_expected_cnt") {
            retval = read_pps_expected_cnt(termout, format);
        }
        else if (addr == "fpga/pps_present") {
            retval = read_pps_present(termout, format, R_MEM);
        }
        else if (addr == "fpga/pps_capture_cnt") {
            retval = read_pps_capture_cnt(termout, format, R_MEM);
        }
        else if (addr == "fpga/time_since_last_pps") {
            retval = read_time_since_last_pps(termout, format, R_MEM);
        }
        else {
            throw runtime_error("address " + addr + " not found!");
        }
    }
    //cout << "Periph_fpga::read, addr=" << addr << ", datatype=" << format << "/" << termout.datatype << endl;
    return retval;
}

/*
* write(), this function is called from Node::worker() thread.
*/
bool Periph_fpga::write(TermOutput& termout, const string addr, const string type,
                        char *data, const uint nvalues, const int format)
{
    // cout << "node " << GlobalNr << " write: Online=" << Online << ", Masked=" << Masked << endl;
    bool retval = false;

    if (!Masked) {  // Not selected
        return false;
    }
    if (!Online) {  // Selected but not possible
        cout << "write() error node " << GlobalNr << " not enabled or not online" << endl;
        return false;
    }
    if (mmap->empty()) {
        cout << "write() error node " << GlobalNr << " empty mmap" << endl;
        Online = false;
        return false;
    }
    termout.nof_vals = nvalues;
    termout.datatype = format;

    // uint32_t *data_ptr = (uint32_t *)data;

    if (type == "mm") {
        uint32_t *data_ptr = (uint32_t *)data;
        retval = Write(addr, data_ptr, true);
    }
    else { // "fpga/..."
    //    if (addr == "fpga/rbf") {
    //        char *data_ptr=(char *)data;
    //        cout << "writing: " << len*sizeof(uint32_t) << " bytes" << endl;
    //        rbf_wf.write(data_ptr, len*sizeof(uint32_t));
    //        retval = true;
    //    } else
        if (addr == "fpga/flash_init") {
            uint32_t *data_ptr = (uint32_t *)data;
            Flash_select = data_ptr[0];
            if (Flash_select == 0) { // factory
                Flash_page_start = Flash_fact_sector_start * Flash_pages_per_sector;
            }
            else { // user
                Flash_page_start = Flash_user_sector_start * Flash_pages_per_sector;
            }
        }
        else if (addr == "fpga/flash_erase") {
            flash_erase();
        }
        else if (addr == "fpga/flash_pages") {
            retval = flash_pages(data, nvalues);
        }
        else if (addr == "fpga/flash_page") {
            retval = flash_page(data);
        }
        else if (addr == "fpga/flash_prot") {
            retval = flash_prot(data);
        }
        else if (addr == "fpga/epcs_mmdp_data") {
            // write to FIFO
            uint32_t *data_ptr = (uint32_t *)data;
            retval = Write("mm/0/REG_MMDP_DATA/0/data", data_ptr, true);
        }
        else if (addr == "fpga/scrap") {
            retval = write_fpga_scrap(data);
        }
        else if (addr == "fpga/weights") {
            retval = write_fpga_weights(data);
        }
        else if (addr == "fpga/sst_offload_weighted_subbands") {
            retval = write_sst_offload_weighted_subbands(data);
        }
        else if (addr == "fpga/sst_offload_enable") {
            retval = write_sst_offload_enable(data);
        }
        else if (addr == "fpga/sst_offload_hdr_eth_destination_mac") {
            retval = write_eth_destination_mac(data, "REG_STAT_HDR_DAT_SST", 1);
        }
        else if (addr == "fpga/sst_offload_hdr_ip_destination_address") {
            retval = write_ip_destination_address(data, "REG_STAT_HDR_DAT_SST", 1);
        }
        else if (addr == "fpga/sst_offload_hdr_udp_destination_port") {
            retval = write_udp_destination_port(data, "REG_STAT_HDR_DAT_SST", 1);
        }
        else if (addr == "fpga/bst_offload_enable") {
            retval = write_bst_offload_enable(data);
        }
        else if (addr == "fpga/bst_offload_hdr_eth_destination_mac") {
            retval = write_eth_destination_mac(data, "REG_STAT_HDR_DAT_BST", nBeamsets);
        }
        else if (addr == "fpga/bst_offload_hdr_ip_destination_address") {
            retval = write_ip_destination_address(data, "REG_STAT_HDR_DAT_BST", nBeamsets);
        }
        else if (addr == "fpga/bst_offload_hdr_udp_destination_port") {
            retval = write_udp_destination_port(data, "REG_STAT_HDR_DAT_BST", nBeamsets);
        }
        else if (addr == "fpga/xst_offload_enable") {
            retval = write_xst_offload_enable(data);
        }
        else if (addr == "fpga/xst_offload_nof_crosslets") {
            retval = write_xst_offload_nof_crosslets(data);
        }
        else if (addr == "fpga/xst_offload_hdr_eth_destination_mac") {
            retval = write_eth_destination_mac(data, "REG_STAT_HDR_DAT_XST", 1);
        }
        else if (addr == "fpga/xst_offload_hdr_ip_destination_address") {
            retval = write_ip_destination_address(data, "REG_STAT_HDR_DAT_XST", 1);
        }
        else if (addr == "fpga/xst_offload_hdr_udp_destination_port") {
            retval = write_udp_destination_port(data, "REG_STAT_HDR_DAT_XST", 1);
        }
        else if (addr == "fpga/xst_processing_enable") {
            retval = write_xst_processing_enable(data);
        }
        else if (addr == "fpga/xst_integration_interval") {
            retval = write_xst_integration_interval(data);
        }
        else if (addr == "fpga/xst_subband_select") {
            retval = write_xst_subband_select(data);
        }
        else if (addr == "fpga/beamlet_output_enable") {
            retval = write_beamlet_output_enable(data);
        }
        else if (addr == "fpga/beamlet_output_scale") {
            retval = write_beamlet_output_scale(data);
        }
        else if (addr == "fpga/beamlet_output_nof_beamlets") {
            retval = write_beamlet_output_nof_beamlets(data);
        }
        else if (addr == "fpga/beamlet_output_hdr_eth_destination_mac") {
            retval = write_eth_destination_mac(data, "REG_HDR_DAT", nBeamsets);
        }
        else if (addr == "fpga/beamlet_output_hdr_ip_destination_address") {
            retval = write_ip_destination_address(data, "REG_HDR_DAT", nBeamsets);
        }
        else if (addr == "fpga/beamlet_output_hdr_udp_destination_port") {
            retval = write_udp_destination_port(data, "REG_HDR_DAT", nBeamsets);
        }
        else if (addr == "fpga/processing_enable") {
            retval = write_processing_enable(data);
        }
        else if (addr == "fpga/sdp_info_station_id") {
            retval = write_sdp_info_station_id(data);
        }
        else if (addr == "fpga/sdp_info_observation_id") {
            retval = write_sdp_info_observation_id(data);
        }
        else if (addr == "fpga/sdp_info_nyquist_sampling_zone_index") {
            retval = write_sdp_info_nyquist_sampling_zone_index(data);
        }
        else if (addr == "fpga/wg_enable") {
            retval = write_wg_enable(data);
        }
        else if (addr == "fpga/wg_amplitude") {
            retval = write_wg_amplitude(data);
        }
        else if (addr == "fpga/wg_phase") {
            retval = write_wg_phase(data);
        }
        else if (addr == "fpga/wg_frequency") {
            retval = write_wg_frequency(data);
        }
        else if (addr == "fpga/signal_input_samples_delay") {
            retval = write_signal_input_samples_delay(data);
        }
        else if (addr == "fpga/subband_weights") {
            retval = write_subband_weights(data);
        }
        else if (addr == "fpga/subband_spectral_inversion") {
            retval = write_subband_spectral_inversion(data);
        }
        else if (addr == "fpga/pps_expected_cnt") {
            retval = write_pps_expected_cnt(data);
        }
        else {
            throw runtime_error("address " + addr + " not found!");
        }
    }
    return retval;
}

/*
* monitor(), this function is called from Node::worker() thread.
*/
bool Periph_fpga::monitor(TermOutput& termout)
{
    // use tictoc, tic(start) toc(stop) to see how long this function takes
    // first 9 read functions on 4 nodes will take 6 msec.
    string name = "periph.fpga.monitor node " + to_string(GlobalNr);
    tictoc.tic(name.c_str());
    read_system_info(termout);  // read_system_info() will set Online state.
    if (Online) {
        if (my_pps_expected_cnt == 0) {
            read_pps_expected_cnt(termout, REG_FORMAT_UINT32);
        }
        read_time_since_last_pps(termout, REG_FORMAT_INT64, R_UCP);
        read_pps_capture_cnt(termout, REG_FORMAT_UINT32, R_UCP);
        read_bsn_monitor_input_sync_timeout(termout, REG_FORMAT_INT64, R_UCP);
        read_bsn_monitor_input_bsn(termout, REG_FORMAT_INT64, R_UCP);
        read_xst_input_sync_at_bsn(termout, REG_FORMAT_INT64, R_UCP);
        read_xst_output_sync_bsn(termout, REG_FORMAT_INT64, R_UCP);
        read_bsn_monitor_input_nof_packets(termout, REG_FORMAT_INT32, R_UCP);
        read_bsn_monitor_input_nof_valid(termout, REG_FORMAT_INT32, R_UCP);
        read_bsn_monitor_input_nof_err(termout, REG_FORMAT_INT32, R_UCP);
        read_jesd204b_csr_rbd_count(termout, REG_FORMAT_UINT32, R_UCP);
        read_jesd204b_csr_dev_syncn(termout, REG_FORMAT_UINT32, R_UCP);
        read_jesd204b_rx_err0(termout, REG_FORMAT_UINT32, R_UCP);
        read_jesd204b_rx_err1(termout, REG_FORMAT_UINT32, R_UCP);
        read_signal_input_mean(termout, REG_FORMAT_DOUBLE, R_UCP);
        read_signal_input_rms(termout, REG_FORMAT_DOUBLE, R_UCP);
    }
    termout.clear();
    tictoc.toc();
    return true;
}


/************************************************
 from here only intern (private) used functions
************************************************/

/*
* Read(), do a read to an UCP register
* - check if addr_str is available.
* - get information for reading.
* - put read data in 'data_ptr' (masked and shifted on request).
* - return true on succes else false.
*/
bool Periph_fpga::Read(const string addr_str, uint32_t *data_ptr, bool use_mask_shift=true)
{
    bool ret;
    if (mmap->empty()) {
        return false;
    }
    if (!mmap->find_register(addr_str)) {
        cout << addr_str << " not found" << endl;
        return false;
    }

    uint32_t addr = mmap->getAddr((addr_str));

    mmap->getReadPermission((addr_str));

    uint32_t nvalues = mmap->getSpan((addr_str));
    bool isfifo = mmap->type_isfifo((addr_str));

    ret = ucp->readRegister(addr, nvalues, data_ptr, isfifo);
    if (use_mask_shift) {
        uint32_t shift = mmap->getShift((addr_str));
        uint32_t mask = mmap->getMask((addr_str));
        for (uint32_t i=0; i<nvalues; i++) {
            data_ptr[i] = mask_shift(shift, mask, data_ptr[i]);
        }
    }
    return ret;
}

/*
* Write(), do a write to an UCP register
* - check if addr_str is available.
* - get information for writing.
* - write data from 'data_ptr' (shifted and masked on request)
* - return true on succes else false.
*/
bool Periph_fpga::Write(const string addr_str, uint32_t *data_ptr, bool use_shift_mask=true)
{
    if (mmap->empty()) {
        return false;
    }
    if (!mmap->find_register(addr_str)) {
        cout << addr_str << " not found" << endl;
        return false;
    }
    uint32_t addr = mmap->getValidAddr((addr_str), 1);
    uint32_t span = mmap->getSpan((addr_str));
    //cout << "addr=" << addr_str << " span=" << to_string(span) << endl;
    mmap->getWritePermission((addr_str));

    bool isfifo = mmap->type_isfifo((addr_str));
    if (use_shift_mask) {
        uint32_t shift = mmap->getShift((addr_str));
        uint32_t mask = mmap->getMask((addr_str));
        for (uint32_t i=0; i<span; i++) {
            data_ptr[i] = shift_mask(shift, mask, data_ptr[i]);
        }
    }
    return ucp->writeRegister(addr, span, data_ptr, isfifo);
}

/*
* mask_shift (for reading)
* mask data and shift bits using information from the mmap.
*/
uint32_t Periph_fpga::mask_shift(const uint32_t shift, const uint32_t mask, uint32_t data)
{
    return (uint32_t)((data & mask) >> shift);
}

/*
* mask_shift (for reading)
* mask data and shift bits using information from the mmap.
*/
uint32_t Periph_fpga::mask_shift(const string addr_str, uint32_t data)
{
    uint32_t shift = mmap->getShift((addr_str));
    uint32_t mask = mmap->getMask((addr_str));
    uint32_t _data = data;

    if (shift != 0 || mask != 0xffffffff) {
        _data &= mask;
        _data = _data >> shift;
    }
    return _data;
}

/*
* shift_mask (for writing)
* shift data and mask bits using information from the mmap.
*/
uint32_t Periph_fpga::shift_mask(const uint32_t shift, const uint32_t mask, uint32_t data)
{
    return ((uint32_t)(data << shift) & mask);
}

/*
* shift_mask (for writing)
* shift data and mask bits using information from the mmap.
*/
uint32_t Periph_fpga::shift_mask(const string addr_str, uint32_t data)
{
    uint32_t shift = mmap->getShift((addr_str));
    uint32_t mask = mmap->getMask((addr_str));
    uint32_t _data = data;

    if (shift != 0 || mask != 0xffffffff) {
        _data = _data << shift;
        _data &= mask;
    }
    return _data;
}


/*
"""Peripheral system_info
   Register map:
    31             24 23             16 15              8 7               0  wi
   |-----------------|-----------------|-----------------|-----------------|
   |                                                          use_phy[7:0] |  1
   |-----------------------------------------------------------------------|
   |                           system_info[31:0]                           |  0
   |-----------------------------------------------------------------------|

    system_info[23:20] = firmware version high[3:0]
    system_info[19:16] = firmware version low[3:0]
    system_info[10]    = cs_sim (= g_sim, 0 on HW, 1 in VHDL simulation)
    system_info[9:8]   = hardware version [1:0] (= 0 for UniBoard 1A and 1B)
    system_info[7:0]   = node id[7;0]
*/
bool Periph_fpga::read_system_info(TermOutput& termout)
{
    // mask and shift values for getting version info from SYSTEM_INFO
    #define FW_VERSION_MASK     0x00F00000
    #define FW_VERSION_SHIFT    20
    #define FW_SUBVERSION_MASK  0x000F0000
    #define FW_SUBVERSION_SHIFT 16
    #define HW_VERSION_MASK     0x0000300
    #define HW_VERSION_SHIFT    8

    bool retval = false;
    uint32_t nvalues = REG_ADDR_SYSTEM_INFO_SPAN;
    uint32_t addr = REG_ADDR_SYSTEM_INFO;
    uint32_t *data = new uint32_t[nvalues * sizeof(uint32_t)];
    if (data == NULL) {
        cerr << "malloc error" << endl;
    }
    memset((void *)data, 0, nvalues * sizeof(uint32_t));

    if (!ucp->readRegister(addr, nvalues, data, false, 2)) {
        if (data != NULL) {
            delete[] data;
        }
        cout << "node " << GlobalNr << " no response" << endl;
        Online = false;
        Masked = false;
        mmap->clear();
        return false;
    }

    // Register can be read, now get mmap if empty.
    if (mmap->empty()) {
        if (mmap != NULL) { delete mmap; }
        mmap = new CMMap(read_reg_map());
        if (!mmap->empty()) {
            cout << "new mmap for node " << GlobalNr << endl;
            mmap->print_screen();
            Online = true;
            Masked = true;
            my_current_design_name = read_design_name();
            cout << "node " << GlobalNr << " now active design_name = " << my_current_design_name << endl;
        }
        else {
            return false;
        }
    }

    // If the design name is changed clear the mmap.
    string design_name = read_design_name();
    if (design_name != my_current_design_name) {
        mmap->clear();
    }
    my_current_design_name = design_name;
    // cout << "node " << GlobalNr << " design_name= " << my_current_design_name << endl;

    uint32_t firmware_version = (data[0] & FW_VERSION_MASK) >> FW_VERSION_SHIFT;
    uint firmware_subversion = (data[0] & FW_SUBVERSION_MASK) >> FW_SUBVERSION_SHIFT;
    my_current_fw_version = to_string(firmware_version) + "." + to_string(firmware_subversion);
    my_current_hw_version = (data[0] & HW_VERSION_MASK) >> HW_VERSION_SHIFT;
    retval = true;

    return retval;
}

bool Periph_fpga::flash_prot(const char *data)
{
    bool retval = false;
    uint32_t passphrase_protect = 0;
    uint32_t passphrase_unprotect = 0xBEDA221E;
    uint32_t *_ptr = (uint32_t *)data;

    if (*_ptr == 0) { // unprotect
        retval = Write("mm/0/REG_EPCS/0/unprotect", &passphrase_unprotect);
    }
    else { // protect
        retval = Write("mm/0/REG_EPCS/0/unprotect", &passphrase_protect);
    }
    return retval;
}

bool Periph_fpga::flash_page(const char *data)
{
    bool retval = false;
    uint32_t *_ptr = (uint32_t *)data;

    wait_while_epcs_busy(1);
    // write address
    uint32_t addr = Flash_page_start * Flash_page_size_bytes;
    retval = Write("mm/0/REG_EPCS/0/addr", &addr);

    // write to FIFO
    retval = Write("mm/0/REG_MMDP_DATA/0/data", _ptr);

    // write_write
    uint32_t d = 1;
    retval = Write("mm/0/REG_EPCS/0/write", &d);

    return retval;
}

bool Periph_fpga::flash_pages(const char *data, const uint len)
{
    bool retval = false;
    uint page_size_words = Flash_page_size_bytes / sizeof(uint32_t);
    uint nof_pages = ceil_div(len, page_size_words);

    cout << "Periph_fpga::flash_pages nof_pages=" << nof_pages << endl;

    for (uint p=0; p<nof_pages; p++) {
        retval = flash_page(&data[(p*Flash_page_size_bytes)]);
        Flash_page_start++;
    }
    return retval;
}

bool Periph_fpga::wait_while_epcs_busy(uint sleeptime)
{
    uint32_t data;
    bool retval;
    //cout << "wait_while_epcs_busy:";
    for (int i=0; i<100; i++) {
        retval = Read("mm/0/REG_EPCS/0/busy", &data);
        if (!retval) break;
        if (data == 0) break;
        usleep(sleeptime);
        //cout << "." << flush;
    }
    //cout << endl;
    if (data != 0) {
        retval = false; // still busy
    }
    return retval;
}

bool Periph_fpga::flash_erase_sector(uint32_t sector)
{
    // We need to write any address in the target sector's address range to select that sector for erase.
    // We'll use the base (lowest) address of the sectors for this: sector 0 starts at 0x0, sector 1 starts
    // at 0x40000 etc.

    bool retval = false;
    uint32_t s,d;
    vector<uint32_t> s_list = {0,0x10000,0x20000,0x30000};

    cout << "erase flash sector: " << sector << endl;
    for (uint i=0; i<s_list.size(); i++) {
        s = sector * 0x40000 + s_list[i];
        // write address
        retval = Write("mm/0/REG_EPCS/0/addr", &s);
        // sector erase
        d = 1;
        retval = Write("mm/0/REG_EPCS/0/sector_erase", &d);
        wait_while_epcs_busy(50000);
    }
    return retval;
}

bool Periph_fpga::flash_erase()
{
    bool retval = false;
    uint32_t start, end;
    cout << "erase flash for bank: " << Flash_select << endl;
    if (Flash_select == 0) { // factory
        start = Flash_fact_sector_start;
        end = Flash_fact_sector_end + 1;
    }
    else { // user
        start = Flash_user_sector_start;
        end = Flash_user_sector_end + 1;
    }
    for (uint32_t s=start; s<end; s++) {
        retval = flash_erase_sector(s);
    }
    return retval;
}

string Periph_fpga::read_design_name()
{
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    if (Read("mm/0/PIO_SYSTEM_INFO/0/design_name", data)) {
        char *str_ptr = (char *)data;
        string name = string(str_ptr);
        //if (data != NULL) delete[] data;
        return name;
    }
    return "? (error)";
}

string Periph_fpga::read_design_note()
{
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    if (Read("mm/0/PIO_SYSTEM_INFO/0/design_note", data)) {
        char *str_ptr = (char *)data;
        string note = string(str_ptr);
        //if (data != NULL) delete[] data;
        return note;
    }
    return "? (error)";
}

bool Periph_fpga::read_hardware_version(TermOutput& termout, int format)
{
    bool retval = true;
    uint hw_version_nr;
    string version;
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    retval = Read("mm/0/PIO_SYSTEM_INFO/0/info_hw_version", data);
    if (retval == false) { return false; }
    hw_version_nr = data[0];
    if (hw_version_nr == 1) {
        version = "UniBoard2b";
    }
    else if (hw_version_nr == 2) {
        version = "UniBoard2c";
    }
    else {
        version = "Unknown";
    }
    termout.nof_vals = 1;
    termout.datatype = format;
    strcpy(termout.val, version.c_str());
    return retval;
}

bool Periph_fpga::read_firmware_version(TermOutput& termout, int format)
{
    bool retval = true;
    string version;
    uint32_t data[20];

    memset((void *)data, 0, sizeof(data));
    retval = Read("mm/0/PIO_SYSTEM_INFO/0/design_name", data);
    if (retval == false) { return retval; }
    char *str_ptr = (char *)data;
    string design_name = string(str_ptr);

    memset((void *)data, 0, sizeof(data));
    retval = Read("mm/0/PIO_SYSTEM_INFO/0/stamp_date", data);
    if (retval == false) { return retval; }
    string date = to_string(data[0]);

    memset((void *)data, 0, sizeof(data));
    retval = Read("mm/0/PIO_SYSTEM_INFO/0/stamp_time", data);
    if (retval == false) { return retval; }
    string time = to_string(data[0]);

    memset((void *)data, 0, sizeof(data));
    retval = Read("mm/0/PIO_SYSTEM_INFO/0/stamp_commit", data);
    if (retval == false) { return retval; }
    string revision = to_string(data[0]);

    version = date.substr(0, 4) + "-" + date.substr(4, 2) + "-" + date.substr(6, 2)
            + "T"
            + time.substr(0, 2) + "." + time.substr(2, 2) + "." + time.substr(4, 2)
            + "_"
            + revision + "_" + design_name;

    termout.nof_vals = 1;
    termout.datatype = format;
    strcpy(termout.val, version.c_str());
    return retval;
}

bool Periph_fpga::read_stamps(TermOutput& termout, int format)
{
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    bool retval = Read("mm/0/PIO_SYSTEM_INFO/0/stamp_date", data);

    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_global_node_index(TermOutput& termout, int format)
{
    bool retval = true;

    uint32_t *_ptr = (uint32_t *)termout.val;
    *_ptr = GlobalNr;
    termout.nof_vals = 1;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_fpga_temperature(TermOutput& termout, int format)
{
    bool retval = true;
    double temp;
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    retval = Read("mm/0/REG_FPGA_TEMP_SENS/0/temp", data);
    if (retval == false) { return false; }
    // ADC to engineering
    // see the constants: https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_alttemp_sense.pdf
    // page 10
    temp = ((693. * (double)data[0]) / 1024.) - 265;

    double *temp_ptr = (double *)termout.val;
    *temp_ptr = temp;
    termout.nof_vals = 1;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_fpga_scrap(TermOutput& termout, int format)
{
    bool retval = true;
    uint32_t *ptr = (uint32_t *)termout.val;
    uint32_t nvalues = mmap->getSpan("mm/0/RAM_SCRAP/0/data");
    retval = Read("mm/0/RAM_SCRAP/0/data", ptr);
    termout.nof_vals = nvalues;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_fpga_scrap(const char *data)
{
    uint32_t *_ptr = (uint32_t *)data;
    uint32_t nvalues = mmap->getSpan("mm/0/RAM_SCRAP/0/data");
    cout << "Periph_fpga::write_fpga_scrap " << nvalues << " values" << endl;
    for (uint i=0; i<nvalues; i++) {
        cout << _ptr[i] << " ";
    }
    cout << endl;
    bool retval = Write("mm/0/RAM_SCRAP/0/data", _ptr);
    return retval;
}

bool Periph_fpga::read_fpga_weights(TermOutput& termout, int format)
{
    bool retval = true;
    int16_t *ptr = (int16_t *)termout.val;
    uint32_t nvalues = mmap->getSpan("mm/0/RAM_SCRAP/0/data");
    uint nblocks = 48; // 11712/244=48

    uint32_t *data_scrap = new uint32_t[nvalues];
    for (uint i=0; i<nblocks; i++) {
        retval = Read("mm/0/RAM_SCRAP/0/data", data_scrap);

        for (uint j=0; j<nvalues; j+=2) {
            uint32_t ds = data_scrap[j];
            *ptr++ = (int16_t)(ds & 0xffff);
            *ptr++ = (int16_t)(ds >> 16);
        }
    }
    termout.nof_vals = mmap->getSpan("mm/0/RAM_BF_WEIGHTS/0/data");
    delete[] data_scrap;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_fpga_weights(const char *data)
{
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = false;
    uint32_t nvalues_scrap = mmap->getSpan("mm/0/RAM_SCRAP/0/data");
    uint32_t nvalues = mmap->getSpan("mm/0/RAM_BF_WEIGHTS/0/data");
    uint nblocks = 48; // 11712/244=48

    uint32_t *data_scrap = new uint32_t[nvalues_scrap];

    uint si=0;
    for (uint i=0; i<nblocks; i++) {
        for (uint j=0; j<nvalues_scrap; j++) {
            uint32_t ds;

            if (si >= nvalues) {
                break;
            }
            ds = *_ptr++;
            si++;
            if (si >= nvalues) {
                break;
            }
            ds |= ((*_ptr++) << 16);
            si++;
            data_scrap[j] = ds;
        }
        retval = Write("mm/0/RAM_SCRAP/0/data", data_scrap);
    }

    delete[] data_scrap;
    return retval;
}

bool Periph_fpga::write_sst_offload_weighted_subbands(const char *data)
{
    uint32_t _data[1];
    _data[0] = (uint32_t)data[0];
    bool retval = Write("mm/0/REG_DP_SELECTOR/0/input_select", _data);
    return retval;
}

bool Periph_fpga::write_sst_offload_enable(const char *data)
{
    uint32_t _data[1];
    _data[0] = (uint32_t)data[0];
    return Write("mm/0/REG_STAT_ENABLE_SST/0/enable", _data);
}


bool Periph_fpga::write_bst_offload_enable(const char *data)
{
    bool *_ptr = (bool *)data;
    bool retval = true;
    string regname;
    uint32_t _data[1];
    for (uint32_t i=0; i<nBeamsets; i++) {
        regname = "mm/" + to_string(i) + "/REG_STAT_ENABLE_BST/0/enable";
        _data[0] = (uint32_t)_ptr[i];
        retval &= Write(regname, _data);
    }
    return retval;
}


bool Periph_fpga::write_beamlet_output_enable(const char *data)
{
    bool *_ptr = (bool *)data;
    bool retval = true;
    string regname;
    uint32_t _data[1];
    for (uint32_t i=0; i<nBeamsets; i++) {
        regname = "mm/" + to_string(i) + "/REG_DP_XONOFF/0/enable_stream";
        _data[0] = (uint32_t)_ptr[i];
        retval &= Write(regname, _data);
    }
    return retval;
}

bool Periph_fpga::read_beamlet_output_scale(TermOutput& termout, int format)
{
    bool retval = true;
    string regname;
    uint32_t W_beamlet_scale = 16;
    double scale_factor = 1.0 / pow(2.0, (W_beamlet_scale-1));
    uint32_t data[1];
    char *_ptr = termout.val;
    for (uint32_t i=0; i<nBeamsets; i++) {
        memset((void *)data, 0, sizeof(data));
        regname = "mm/" + to_string(i) + "/REG_BF_SCALE/0/scale";
        retval &= Read(regname, data);
        double scale = (double(data[0]) * scale_factor);
        memcpy(_ptr, &scale, sizeof(double));
        _ptr += reg_format_size_in_bytes(format);
    }
    termout.nof_vals = nBeamsets;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_beamlet_output_scale(const char *data)
{
    double *_ptr = (double *)data;
    bool retval = true;
    string regname;
    uint32_t W_beamlet_scale = 16;
    double scale_factor = pow(2.0, (W_beamlet_scale-1));
    uint32_t _data[1];
    for (uint32_t i=0; i<nBeamsets; i++) {
        regname = "mm/" + to_string(i) + "/REG_BF_SCALE/0/scale";
        _data[0] = (uint32_t)(_ptr[i] * scale_factor);
        retval &= Write(regname, _data);
    }
    return retval;
}

bool Periph_fpga::read_beamlet_output_nof_beamlets(TermOutput& termout, int format)
{
    bool retval = true;
    string regname;
    uint32_t data[2];
    memset((void *)data, 0, sizeof(data));
    uint32_t n_beamsets = 0;
    for (uint32_t i=0; i<nBeamsets; i++) {
        memset((void *)data, 0, sizeof(data));
        regname = "mm/" + to_string(i) + "/REG_DP_BLOCK_RESIZE/0/eop_index";
        retval &= Read(regname, data);
        n_beamsets += (data[0] + 1) / C_N_pol_bf;
    }
    memcpy(termout.val, &n_beamsets, reg_format_size_in_bytes(format));
    termout.nof_vals = nBeamsets;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_beamlet_output_nof_beamlets(const char *data)
{
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = true;
    string regname;
    uint32_t nof_beamlets = _ptr[0];
    uint32_t _data[1] = {0};
    int32_t bset_data;
    for (uint32_t i=0; i<nBeamsets; i++) {
        regname = "mm/" + to_string(i) + "/REG_DP_BLOCK_RESIZE/0/eop_index";
        // (0+1) * 488 = 488
        // (1+1) * 488 = 976
        if (((i + 1) * C_S_sub_bf) < nof_beamlets) {
            bset_data = (uint32_t)(C_N_pol_bf * C_S_sub_bf - 1);  // 2*488-1 = 975
        }
        // (0 * 488) = 0
        // (1 * 488) = 488
        else if ((i * C_S_sub_bf) < nof_beamlets) {
            bset_data = (uint32_t)(C_N_pol_bf * (nof_beamlets - i * C_S_sub_bf) - 1);  // 2*()
        }
        else {
            bset_data = -1;
        }
        memcpy(_data, &bset_data, sizeof(int32_t));
        retval &= Write(regname, _data);
    }
    return retval;
}


bool Periph_fpga::write_xst_offload_enable(const char *data)
{
    uint32_t _data[1];
    _data[0] = (uint32_t)data[0];
    return Write("mm/0/REG_STAT_ENABLE_XST/0/enable", _data);
}

bool Periph_fpga::write_xst_offload_nof_crosslets(const char *data)
{
    uint32_t *_ptr = (uint32_t *)data;
    return Write("mm/0/REG_NOF_CROSSLETS/0/nof_crosslets", _ptr);
}


bool Periph_fpga::read_eth_destination_mac(TermOutput& termout, const string& port_name, const int n_instances, const int format)
{
     bool retval = true;
    string regname;

    uint32_t data[20];
    // uint64_t mac;
    stringstream mac_ss;
    string mac_str;
    for (int i=0; i<n_instances; i++) {
        memset((void *)data, 0, sizeof(data));
        regname = "mm/" + to_string(i) + "/" + port_name + "/0/eth_destination_mac";
        retval &= Read(regname, data);
        uint64_t mac = (uint64_t)data[1] << 32 | data[0];

        mac_ss.clear();
        mac_ss.str("");
        for (int j=5; j>=0; j--) {
            mac_ss << setfill('0') << setw(2) << right << hex << ((mac >> (j * 8)) & 0xff);
            if (j > 0) {
                mac_ss << ":";
            }
        }
        mac_str = mac_ss.str();
        // cout << "eth_destination_mac: " << regname << ", data[0]=" << to_string(data[0]) << ", str=" << mac_str << ", retval=" << retval << endl;
        strcpy(&(termout.val[i*SIZE1STRING]), mac_str.c_str());
    }
    termout.nof_vals = n_instances;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_eth_destination_mac(const char *data, const string& port_name, const int n_instances)
{
    // cout << "data=" << string(data) << endl;
    bool retval = true;
    string regname;
    char sep;
    uint32_t m0, m1, m2, m3, m4, m5;
    uint32_t mac[2] {0, 0};
    string ds;
    stringstream ss;
    for (int i=0; i<n_instances; i++) {
        ss.clear();
        ss.str("");
        regname = "mm/" + to_string(i) + "/" + port_name + "/0/eth_destination_mac";
        ds = &data[i*SIZE1STRING];
        cout << ds << endl;
        ss << ds;
        ss >> setbase(16) >> m0 >> sep >> m1 >> sep >> m2 >> sep >> m3 >> sep >> m4 >> sep >> m5;
        if (ss.fail() || ss.bad()) {
            cout << "parse error in write_eth_destination_mac (" << ds << ")" << endl;
            return false;
        }
        mac[1] = (m0 << 8) + (m1 << 0);
        mac[0] = (m2 << 24) + (m3 << 16) + (m4 << 8) + m5;
        // cout << "mac=" << mac[1] << ", " << mac[0] << endl;
        retval &= Write(regname, mac);
    }
    return retval;
}

bool Periph_fpga::read_ip_destination_address(TermOutput& termout, const string& port_name, const int n_instances, const int format)
{
    bool retval = true;
    string regname;

    uint32_t data[20];
    stringstream ip_ss;
    string ip_str;
    for (int i=0; i<n_instances; i++) {
        memset((void *)data, 0, sizeof(data));
        regname = "mm/" + to_string(i) + "/" + port_name + "/0/ip_destination_address";
        retval &= Read(regname, data);
        ip_ss.clear();
        ip_ss.str("");
        for (int j=3; j>=0; j--) {
            ip_ss << dec << ((data[0] >> (j * 8)) & 0xff);
            if (j > 0) {
                ip_ss << ".";
            }
        }
        ip_str = ip_ss.str();
        // cout << "ip_destination_address: " << regname << ", data[0]=" << to_string(data[0]) << ", str=" << ip_str << ", retval=" << retval << endl;
        strcpy(&termout.val[i*SIZE1STRING], ip_str.c_str());
    }
    termout.nof_vals = n_instances;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_ip_destination_address(const char *data, const string& port_name, const int n_instances)
{
    bool retval = true;
    string regname;
    char sep;
    uint ip0, ip1, ip2, ip3;
    uint32_t ip[1] = {0};
    string ds;
    stringstream ss;
    for (int i=0; i<n_instances; i++) {
        ss.clear();
        ss.str("");
        regname = "mm/" + to_string(i) + "/" + port_name + "/0/ip_destination_address";
        // cout << "write_ip=[" << ds << "]" << endl;
        ds = &data[i*SIZE1STRING];
        cout << ds << endl;
        ss << ds;
        ss >> setbase(10) >> ip0;
        ss >> sep;
        ss >> setbase(10) >> ip1;
        ss >> sep;
        ss >> setbase(10) >> ip2;
        ss >> sep;
        ss >> setbase(10) >> ip3;
        // cout << "ip0=" << to_string(ip0) << " ip1=" << to_string(ip1) << " ip2=" << to_string(ip2) << " ip3=" << to_string(ip3) << endl;
        if (ss.fail() || ss.bad()) {
            cout << "parse error in write_ip_destination_address (" << ds << ")" << endl;
            return false;
        }
        ip[0]  = (ip0 & 0xff) << 24;
        ip[0] += (ip1 & 0xff) << 16;
        ip[0] += (ip2 & 0xff) << 8;
        ip[0] += ip3 & 0xff;
        // cout << "portname=" << port_name << ", ip=" << hex << ip[0] << endl;
        if (Write(regname, ip) == false) {
            retval = false;
        }
    }
    return retval;
}

bool Periph_fpga::write_udp_destination_port(const char *data, const string& port_name, const int n_instances)
{
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = true;
    string regname;
    for (int i=0; i<n_instances; i++) {
        regname = "mm/" + to_string(i) + "/" + port_name + "/0/udp_destination_port";
        retval &= Write(regname, &_ptr[i]);
    }
    return retval;
}



bool Periph_fpga::read_xst_processing_enable(TermOutput& termout, int format)
{
    bool retval = true;

    uint32_t data[1];
    memset((void *)data, 0, sizeof(data));
    retval = Read("mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/mon_output_enable", data);

    bool *_ptr = (bool *)termout.val;
    *_ptr = (bool)data[0];;
    termout.nof_vals = 1;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_xst_processing_enable(const char *data)
{
    bool retval = true;
    uint32_t *reg = new uint32_t[2];
    bool processing_enable = (bool)data[0];
    uint64_t start_bsn;
    if (processing_enable == true) {
        // get bsn and add latency
        retval &= Read("mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/mon_input_bsn_at_sync", reg);
        if (retval == true) {
            start_bsn = (((uint64_t)reg[1] << 32) + reg[0]);
            cout << "bsn=" << to_string(start_bsn) << endl;
            start_bsn = start_bsn + (2 * C_F_adc) / C_N_fft;
            cout << "new bsn=" << to_string(start_bsn) << endl;
            reg[0] = (uint32_t)(start_bsn & 0xffffffff);
            reg[1] = (uint32_t)((start_bsn >> 32) & 0xffffffff);
            // write sheduled bsn
            retval &= Write("mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/ctrl_start_bsn", reg);
            reg[0] = 1;
            // write ctrl_enable = 1
            retval &= Write("mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/ctrl_enable", reg);
        }
    }
    else {
        reg[0] = 0;
        // write ctrl_enable = 0
        retval &= Write("mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/ctrl_enable", reg);
    }
    return retval;
}

bool Periph_fpga::read_xst_integration_interval(TermOutput& termout, int format)
{
    bool retval = true;

    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    retval = Read("mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/ctrl_interval_size", data);
    double interval = (double)data[0] * C_T_adc;

    double *_ptr = (double *)termout.val;
    *_ptr = interval;
    termout.nof_vals = 1;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_xst_integration_interval(const char *data)
{
    double *_ptr = (double *)data;
    uint32_t *reg = new uint32_t[1];
    reg[0] = (uint32_t)round(_ptr[0] * C_F_adc);
    return Write("mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/ctrl_interval_size", reg);
}

bool Periph_fpga::read_xst_subband_select(TermOutput& termout, int format)
{
    bool retval = true;

    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    retval &= Read("mm/0/REG_CROSSLETS_INFO/0/step", data);
    retval &= Read("mm/0/REG_CROSSLETS_INFO/0/offset", &data[1]);

    uint32_t *_ptr = (uint32_t *)termout.val;
    for (uint i=0; i<(C_N_step+C_N_crosslets_max); i++) {
        *_ptr = data[i];
        _ptr++;
    }
    termout.nof_vals = C_N_step + C_N_crosslets_max;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_xst_input_sync_at_bsn(TermOutput& termout, int format, int mode) {
    bool retval = true;
    int64_t bsn = my_xst_input_bsn_at_sync;
    if (mode == R_UCP) {
        uint32_t data[2];
        memset((void *)data, 0, sizeof(data));
        string regname;
        regname = "mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/mon_input_bsn_at_sync";
        retval = Read(regname, data);
        bsn = (((int64_t)data[1] << 32) + data[0]);
    }
    int64_t *_ptr = (int64_t *)termout.val;
    *_ptr = bsn;
    termout.nof_vals = 1;
    termout.datatype = format;
    my_xst_input_bsn_at_sync = bsn;
    return retval;
}

bool Periph_fpga::read_xst_output_sync_bsn(TermOutput& termout, int format, int mode) {
    bool retval = true;
    int64_t bsn = my_xst_output_sync_bsn;
    if (mode == R_UCP) {
        uint32_t data[2];
        memset((void *)data, 0, sizeof(data));
        string regname;
        regname = "mm/0/REG_BSN_SYNC_SCHEDULER_XSUB/0/mon_output_sync_bsn";
        retval = Read(regname, data);
        bsn = (((int64_t)data[1] << 32) + data[0]);
    }
    int64_t *_ptr = (int64_t *)termout.val;
    *_ptr = bsn;
    termout.nof_vals = 1;
    termout.datatype = format;
    my_xst_output_sync_bsn = bsn;
    return retval;
}

bool Periph_fpga::write_xst_subband_select(const char *data)
{
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = true;
    retval &= Write("mm/0/REG_CROSSLETS_INFO/0/offset", &_ptr[1]);
    retval &= Write("mm/0/REG_CROSSLETS_INFO/0/step", &_ptr[0]);
    return retval;
}






bool Periph_fpga::write_processing_enable(const char *data)
{
    // write dp_on 0
    // write bsn_init [calc val for next sec]
    // write bsn_time_offset [calc val if odd]
    // write dp_on_pps 1
    // write dp_on 1

    uint32_t *reg = new uint32_t[2];
    reg[0] = 0;
    reg[1] = 0;
    bool processing_enable = (bool)data[0];
    if (processing_enable) {
        cout << "turn on bsn" << endl;
        // reg[0] = 0;
        // if (Write("mm/0/REG_BSN_SOURCE_V2/0/dp_on", reg) == false) {
        //     cout << "Write error (mm/0/REG_BSN_SOURCE_V2/0/dp_on) 0" << endl;
        //     return false;
        // }

        struct timeval now;
        uint64_t bsn;
        uint32_t offset = 0;
        gettimeofday(&now, NULL);
        while (now.tv_usec > 800000) {
            gettimeofday(&now, NULL);
        }
        bsn = (uint64_t)((now.tv_sec + 1) / 5.12e-6);

        if (now.tv_sec % 2 == 1) {
            offset = 512;
        }
        reg[0] = (uint32_t)(bsn & 0xffffffff);
        reg[1] = (uint32_t)((bsn >> 32) & 0xffffffff);
        if (Write("mm/0/REG_BSN_SOURCE_V2/0/bsn_init", reg) == false) {
            cout << "Write error (mm/0/REG_BSN_SOURCE_V2/0/bsn_init)" << endl;
            return false;
        }
        reg[0] = offset;
        if (Write("mm/0/REG_BSN_SOURCE_V2/0/bsn_time_offset", reg) == false) {
            cout << "Write error (mm/0/REG_BSN_SOURCE_V2/0/bsn_time_offset)" << endl;
            return false;
        }
        reg[0] = shift_mask("mm/0/REG_BSN_SOURCE_V2/0/dp_on", 0) | shift_mask("mm/0/REG_BSN_SOURCE_V2/0/dp_on_pps", 1);
        if (Write("mm/0/REG_BSN_SOURCE_V2/0/dp_on", reg, false) == false) {
            cout << "Write error (mm/0/REG_BSN_SOURCE_V2/0/dp_on_pps)" << endl;
            return false;
        }
        reg[0] = shift_mask("mm/0/REG_BSN_SOURCE_V2/0/dp_on", 1) | shift_mask("mm/0/REG_BSN_SOURCE_V2/0/dp_on_pps", 1);
        if (Write("mm/0/REG_BSN_SOURCE_V2/0/dp_on", reg, false) == false) {
            cout << "Write error (mm/0/REG_BSN_SOURCE_V2/0/dp_on) 1" << endl;
            return false;
        }
    }
    else {
        reg[0] = 0;
        if (Write("mm/0/REG_BSN_SOURCE_V2/0/dp_on", reg) == false) {
            cout << "Write error (mm/0/REG_BSN_SOURCE_V2/0/dp_on) 0" << endl;
            return false;
        }
    }
    return true;
}

bool Periph_fpga::write_sdp_info_station_id(const char *data) {
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = Write("mm/0/REG_SDP_INFO/0/station_id", _ptr);
    return retval;
}

bool Periph_fpga::write_sdp_info_observation_id(const char *data) {
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = Write("mm/0/REG_SDP_INFO/0/observation_id", _ptr);
    return retval;
}

bool Periph_fpga::write_sdp_info_nyquist_sampling_zone_index(const char *data) {
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = Write("mm/0/REG_SDP_INFO/0/nyquist_zone_index", _ptr);
    return retval;
}

// Waveform generator functions "_wg_"
/*
When FPGA_wg_enable_RW is set False, then disable the WG via mode = c_mode_off = 0.
When FPGA_wg_enable_RW is set True, then enable the WG via mode = c_mode_calc = 1. Once enabled, the
WG starts or restarts when it gets a trigger from the BSN scheduler. The trigger has to occur at the
same BSN for all WG that are enabled, to ensure that they start synchronously. Any WG that are not
enabled will ignore the trigger. The exact BSN at which the WG start is don't care. The trigger is
scheduled via start_bsn in REG_BSN_SCHEDULER. The current BSN can be read from the REG_BSN_SCHEDULER
on one of the FPGAs. Assume the communication to write the start_bsn in all FPGAs will take
less than 1 ms, then a margin of 10 - 100 ms is sufficient. The BSN period corresponds to 5.12 μs, so
a c_bsn_latency = 20000 (≈ 100 ms) is sufficient for start_bsn = current_bsn + c_bsn_latency.
The MP reports False when mode = c_mode_off = 0, else True.
Note:
  The nof_samples field and mode field share an address in REG_DIAG_WG. The nof_samples = 2**W_wg_buf = 1024.
*/

bool Periph_fpga::write_wg_enable(const char *data) {
    bool *_ptr = (bool *)data;
    bool retval = true;
    uint32_t *reg = new uint32_t[4];
    uint64_t scheduled_bsn;
    bool wg_enable;

    string regname1;
    string regname2;

    // turn off waveform
    for (uint i=0; i< C_S_pn; i++) {
        wg_enable = _ptr[i];
        if (wg_enable == false) {
            regname1 = "mm/0/REG_WG/" + to_string(i) + "/mode";
            regname2 = "mm/0/REG_WG/" + to_string(i) + "/nof_samples";
            reg[0] = shift_mask(regname1, C_WG_MODE_OFF) | shift_mask(regname2, 1024);  // TODO: make constant
            retval &= Write(regname1, reg, false);
        }
    }
    // turn on waveform
    for (uint i=0; i< C_S_pn; i++) {
        wg_enable = _ptr[i];
        if (wg_enable == true) {
            regname1 = "mm/0/REG_WG/" + to_string(i) + "/mode";
            regname2 = "mm/0/REG_WG/" + to_string(i) + "/nof_samples";
            reg[0] = shift_mask(regname1, C_WG_MODE_CALC) | shift_mask(regname2, 1024);  // TODO: make constant
            retval &= Write(regname1, reg, false);
        }
    }
    // get bsn and add latency
    wg_enable = _ptr[0];
    if (wg_enable == true) {
        regname1 = "mm/0/REG_BSN_SCHEDULER/0/scheduled_bsn";
        retval &= Read(regname1, reg);
        scheduled_bsn = (((uint64_t)reg[1] << 32) + reg[0]);
        cout << "bsn=" << to_string(scheduled_bsn) << endl;
        scheduled_bsn += C_BSN_LATENCY;
        cout << "new bsn=" << to_string(scheduled_bsn) << endl;
        reg[0] = (uint32_t)(scheduled_bsn & 0xffffffff);
        reg[1] = (uint32_t)((scheduled_bsn >> 32) & 0xffffffff);
        retval &= Write(regname1, reg);
    }

    delete[] reg;
    return retval;
}

/*
ampl = FPGA_wg_amplitude_RW * c_ampl_unit
  where c_ampl_unit = 2^16.
  MP converts ampl into FPGA_wg_amplitude_R.
*/
bool Periph_fpga::read_wg_amplitude(TermOutput& termout, int format) {
    bool retval = true;
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    string regname;
    for (uint i=0; i< C_S_pn; i++) {
        regname = "mm/0/REG_WG/" + to_string(i) + "/ampl";
        retval = Read(regname, &data[i]);
    }

    double ampl;
    double *_ptr = (double *)termout.val ;
    for (uint i=0; i< C_S_pn; i++) {
        ampl = ((double)data[i]) / C_WG_AMPL_UNIT;
        *_ptr = ampl;
        _ptr++;
    }

    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_wg_amplitude(const char *data) {
    double *_ptr = (double *)data;
    bool retval = true;
    uint32_t *reg = new uint32_t[2];

    double ampl;
    string regname;
    for (uint i=0; i< C_S_pn; i++) {
        regname = "mm/0/REG_WG/" + to_string(i) + "/ampl";
        ampl = (*_ptr) * C_WG_AMPL_UNIT;
        reg[0] = (uint32_t)ampl;
        retval = Write(regname, reg);
        _ptr++;
    }
    delete[] reg;
    return retval;
}


/*
phase = FPGA_wg_phase_RW * c_phase_unit
  where c_phase_unit = 2^16 / 360.
  MP converts phase into FPGA_wg_phase_R.
*/
bool Periph_fpga::read_wg_phase(TermOutput& termout, int format) {
    bool retval = true;
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    string regname;
    for (uint i=0; i< C_S_pn; i++) {
        regname = "mm/0/REG_WG/" + to_string(i) + "/phase";
        retval = Read(regname, &data[i]);
    }

    double phase;
    double *_ptr = (double *)termout.val ;
    for (uint i=0; i< C_S_pn; i++) {
        phase = ((double)data[i]) / C_WG_PHASE_UNIT;
        *_ptr = phase;
        _ptr++;
    }

    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_wg_phase (const char *data) {
    double *_ptr = (double *)data;
    bool retval = true;
    uint32_t *reg = new uint32_t[2];

    float phase;
    string regname;
    for (uint i=0; i< C_S_pn; i++) {
        regname = "mm/0/REG_WG/" + to_string(i) + "/phase";
        phase = (*_ptr) * C_WG_PHASE_UNIT;
        reg[0] = (uint32_t)phase;
        retval = Write(regname, reg);
        _ptr++;
    }
    delete[] reg;
    return retval;
}

/*
freq = FPGA_wg_frequency_RW / f_adc
freq = freq % 1  # to wrap freq in [0, 1> interval
freq = freq * c_freq_unit
  where f_adc = 200e6 and c_freq_unit = 2^31.
  MP converts freq into FPGA_wg_frequency_R.
*/
bool Periph_fpga::read_wg_frequency(TermOutput& termout, int format) {
    bool retval = true;
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    string regname;
    for (uint i=0; i<C_S_pn; i++) {
        regname = "mm/0/REG_WG/" + to_string(i) + "/freq";
        retval = Read(regname, &data[i]);
    }

    double freq;
    double *_ptr = (double *)termout.val ;
    for (uint i=0; i<C_S_pn; i++) {
        freq = (((double)data[i]) / C_WG_FREQ_UNIT) * C_F_adc;
        *_ptr = freq;
        _ptr++;
    }

    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_wg_frequency(const char *data) {
    double *_ptr = (double *)data;
    bool retval = true;
    uint32_t *reg = new uint32_t[2];

    double freq, intpart;
    string regname;
    for (uint i=0; i<C_S_pn; i++) {
        regname = "mm/0/REG_WG/" + to_string(i) + "/freq";
        freq = (*_ptr) / C_F_adc;
        freq = modf(freq, &intpart);
        freq = freq * C_WG_FREQ_UNIT;
        reg[0] = (uint32_t)freq;
        retval = Write(regname, reg);
        _ptr++;
    }
    delete[] reg;
    return retval;
}

bool Periph_fpga::read_bsn_monitor_input_sync_timeout(TermOutput& termout, int format, int mode) {
    bool retval = true;
    uint32_t data[20];
    memset((void *)data, 0, sizeof(data));
    string regname;
    regname = "mm/0/REG_BSN_MONITOR_INPUT/0/sync_timeout";
    retval = Read(regname, data);
    my_bsn_input_sync_timeout = (bool)data[0];

    return retval;
}

bool Periph_fpga::read_bsn_monitor_input_bsn(TermOutput& termout, int format, int mode) {
    bool retval = true;
    int64_t bsn = my_bsn_input_bsn;
    if (mode == R_UCP) {
        if (my_bsn_input_sync_timeout == true) {
            bsn = -1;
        } else {
            uint32_t data[20];
            memset((void *)data, 0, sizeof(data));
            string regname;
            regname = "mm/0/REG_BSN_MONITOR_INPUT/0/bsn_at_sync";
            retval = Read(regname, data);
            bsn = (((int64_t)data[1] << 32) + data[0]);
        }
    }
    int64_t *_ptr = (int64_t *)termout.val;
    *_ptr = bsn;
    termout.nof_vals = 1;
    termout.datatype = format;
    my_bsn_input_bsn = bsn;
    return retval;
}

bool Periph_fpga::read_bsn_monitor_input_nof_packets(TermOutput& termout, int format, int mode) {
    bool retval = true;
    int32_t nof_packets = my_bsn_input_nof_packets;
    if (mode == R_UCP) {
        if (my_bsn_input_sync_timeout == true) {
            nof_packets = -1;
        } else {
            uint32_t data[20];
            memset((void *)data, 0, sizeof(data));
            string regname;
            regname = "mm/0/REG_BSN_MONITOR_INPUT/0/nof_sop";
            retval = Read(regname, data);
            nof_packets = (int32_t)data[0];
        }
    }
    int32_t *_ptr = (int32_t *)termout.val;
    *_ptr = nof_packets;
    termout.nof_vals = 1;
    termout.datatype = format;
    my_bsn_input_nof_packets = nof_packets;
    return retval;
}

bool Periph_fpga::read_bsn_monitor_input_nof_valid(TermOutput& termout, int format, int mode) {
    bool retval = true;
    int32_t nof_valid = my_bsn_input_nof_valid;
    if (mode == R_UCP) {
        if (my_bsn_input_sync_timeout == true) {
            nof_valid = -1;
        } else {
            uint32_t data[20];
            memset((void *)data, 0, sizeof(data));
            string regname;
            regname = "mm/0/REG_BSN_MONITOR_INPUT/0/nof_valid";
            retval = Read(regname, data);
            nof_valid = (int32_t)data[0];
        }
    }
    int32_t *_ptr = (int32_t *)termout.val;
    *_ptr = nof_valid;
    termout.nof_vals = 1;
    termout.datatype = format;
    my_bsn_input_nof_valid = nof_valid;
    return retval;
}

bool Periph_fpga::read_bsn_monitor_input_nof_err(TermOutput& termout, int format, int mode) {
    bool retval = true;
    int32_t nof_err = my_bsn_input_nof_err;
    if (mode == R_UCP) {
        if (my_bsn_input_sync_timeout == true) {
            nof_err = -1;
        } else {
            uint32_t data[20];
            memset((void *)data, 0, sizeof(data));
            string regname;
            regname = "mm/0/REG_BSN_MONITOR_INPUT/0/nof_err";
            retval = Read(regname, data);
            nof_err = (int32_t)data[0];
        }
    }
    int32_t *_ptr = (int32_t *)termout.val;
    *_ptr = nof_err;
    termout.nof_vals = 1;
    termout.datatype = format;
    my_bsn_input_nof_err = nof_err;
    return retval;
}

bool Periph_fpga::read_jesd204b_csr_rbd_count(TermOutput& termout, int format, int mode) {
    bool retval = true;
    if (mode == R_UCP) {
        uint32_t data[20];
        memset((void *)data, 0, sizeof(data));
        string regname;
        for (uint i=0; i< C_S_pn; i++) {
            regname = "mm/0/JESD204B/" + to_string(i) + "/csr_rbd_count";
            retval = Read(regname, data);
            my_jesd_csr_rbd_count[i] = (uint32_t)data[0];
        }
    }

    uint32_t *_ptr = (uint32_t *)termout.val;
    for (uint i=0; i< C_S_pn; i++) {
        *_ptr = my_jesd_csr_rbd_count[i];
        _ptr++;
    }
    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_jesd204b_csr_dev_syncn(TermOutput& termout, int format, int mode) {
    bool retval = true;
    if (mode == R_UCP) {
        uint32_t data[20];
        memset((void *)data, 0, sizeof(data));
        string regname;
        for (uint i=0; i< C_S_pn; i++) {
            regname = "mm/0/JESD204B/" + to_string(i) + "/csr_dev_syncn";
            retval = Read(regname, data);
            my_jesd_csr_dev_syncn[i] = (uint32_t)data[0];
        }
    }

    uint32_t *_ptr = (uint32_t *)termout.val;
    for (uint i=0; i< C_S_pn; i++) {
        *_ptr = my_jesd_csr_dev_syncn[i];
        _ptr++;
    }
    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_jesd204b_rx_err0(TermOutput& termout, int format, int mode) {
    bool retval = true;
    if (mode == R_UCP) {
        uint32_t data[20];
        memset((void *)data, 0, sizeof(data));
        string regname;
        for (uint i=0; i< C_S_pn; i++) {
            regname = "mm/0/JESD204B/" + to_string(i) + "/rx_err0";
            retval = Read(regname, data);
            my_jesd_rx_err0[i] = (uint32_t)data[0];
        }
    }

    uint32_t *_ptr = (uint32_t *)termout.val;
    for (uint i=0; i< C_S_pn; i++) {
        *_ptr = my_jesd_rx_err0[i];
        _ptr++;
    }
    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_jesd204b_rx_err1(TermOutput& termout, int format, int mode) {
    bool retval = true;
    if (mode == R_UCP) {
        uint32_t data[20];
        memset((void *)data, 0, sizeof(data));
        string regname;
        for (uint i=0; i< C_S_pn; i++) {
            regname = "mm/0/JESD204B/" + to_string(i) + "/rx_err1";
            retval = Read(regname, data);
            my_jesd_rx_err1[i] = (uint32_t)data[0];
        }
    }

    uint32_t *_ptr = (uint32_t *)termout.val;
    for (uint i=0; i< C_S_pn; i++) {
        *_ptr = my_jesd_rx_err1[i];
        _ptr++;
    }
    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_signal_input_samples_delay(const char *data) {
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = true;
    uint32_t *reg = new uint32_t[2];
    uint32_t min_sample_delay = 0;
    uint32_t max_sample_delay = 4095;
    uint32_t sample_delay;
    string regname;
    for (uint i=0; i< C_S_pn; i++) {
        sample_delay = *_ptr;
        if ((sample_delay < min_sample_delay) || (sample_delay > max_sample_delay)) {
            cerr << "signal_input_sample_delay not in range<" << min_sample_delay << ":" << max_sample_delay << ">" << endl;
            retval = false;
        } else {
            regname = "mm/0/REG_DP_SHIFTRAM/" + to_string(i) + "/shift";
            reg[0] = sample_delay;
            retval = Write(regname, reg);
            _ptr++;
        }
    }
    delete[] reg;
    return retval;
}

bool Periph_fpga::read_signal_input_mean(TermOutput& termout, int format, int mode) {
    bool retval = true;
    if (mode == R_UCP) {
        uint32_t data[2];
        memset((void *)data, 0, sizeof(data));
        string regname;
        int64_t mean_sum;
        for (uint i=0; i< C_S_pn; i++) {
            regname = "mm/0/REG_ADUH_MONITOR/" + to_string(i) + "/mean_sum";
            retval = Read(regname, data);
            mean_sum = (int64_t)(((int64_t)data[1] << 32) + data[0]);
            my_signal_input_mean[i] = (double)mean_sum / C_N_CLK_PER_PPS;
        }
    }

    double *_ptr = (double *)termout.val;
    for (uint i=0; i< C_S_pn; i++) {
        *_ptr = my_signal_input_mean[i];
        _ptr++;
    }
    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_signal_input_rms(TermOutput& termout, int format, int mode) {
    bool retval = true;
    if (mode == R_UCP) {
        uint32_t data[2];
        memset((void *)data, 0, sizeof(data));
        string regname;
        int64_t power_sum;
        for (uint i=0; i< C_S_pn; i++) {
            regname = "mm/0/REG_ADUH_MONITOR/" + to_string(i) + "/power_sum";
            retval = Read(regname, data);
            power_sum = (int64_t)(((int64_t)data[1] << 32) + data[0]);
            my_signal_input_rms[i] = sqrt((double)power_sum / C_N_CLK_PER_PPS);
        }
    }

    double *_ptr = (double *)termout.val;
    for (uint i=0; i< C_S_pn; i++) {
        *_ptr = my_signal_input_rms[i];
        _ptr++;
    }
    termout.nof_vals = C_S_pn;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_signal_input_data_buffer(TermOutput& termout, int format) {
    bool retval = true;
    uint32_t data[C_V_si_db];
    string regname;
    int16_t *_ptr = (int16_t *)termout.val;
    for (uint i=0; i< C_S_pn; i++) {
        memset((void *)data, 0, sizeof(data));
        regname = "mm/0/RAM_DIAG_DATA_BUFFER_BSN/" + to_string(i) + "/data";
        retval &= Read(regname, data);
        for (uint j=0; j< C_V_si_db; j++) {
            *_ptr = (int16_t)((data[j] & 0x3FFF) << 2) / 4;
            _ptr++;
        }
    }
    termout.nof_vals = C_S_pn * C_V_si_db;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::write_subband_weights(const char *data) {
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = true;
    string regname;
    uint32_t span;
    uint32_t data_ptr;
    uint32_t n_regs = (int32_t)(C_S_pn / C_Q_fft);

    for (uint i=0; i<n_regs; i++) {
        regname = "mm/0/RAM_EQUALIZER_GAINS/" + to_string(i) + "/data";
        span = mmap->getSpan((regname));
        data_ptr = i * span;
        retval = Write(regname, &_ptr[data_ptr]);
    }
    return retval;
}

bool Periph_fpga::read_all_from_port(TermOutput& termout, const string& port_name, const string& field_name, const int format) {
    bool retval = true;

    string regname = "mm/0/" + port_name + "/0/" + field_name;
    uint32_t n_periph = mmap->getNPeripherals(regname);
    uint32_t n_ports = mmap->getNPorts(regname);
    uint32_t n_fields = mmap->getNFields(regname);
    uint32_t span = mmap->getSpan(regname);

    uint32_t format_size = reg_format_size_in_bytes(format);

    uint32_t *data = new uint32_t[span];
    char *_ptr = termout.val;

    for (uint i=0; i<n_periph; i++) {
        for (uint j=0; j<n_ports; j++) {
            regname = "mm/" + to_string(i) + "/" + port_name + "/" + to_string(j) + "/" + field_name;
            // cout << "regname=" << regname << endl;

            memset((void *)data, 0, (span * sizeof(uint32_t)));
            retval &= Read(regname, data);
            if (retval) {
                // if a string
                if (format == REG_FORMAT_STRING) {
                    memcpy(_ptr, data, n_fields);
                    _ptr += SIZE1STRING;
                }
                // if >= 4 bytes (uint32_t)
                else if (format_size >= sizeof(uint32_t)) {
                    memcpy(_ptr, data, (n_fields * format_size));
                    _ptr += (n_fields * format_size);
                }
                // if < 4 (uint32_t)
                else {
                    for (uint field_nr=0; field_nr<n_fields; field_nr++) {
                        memcpy(_ptr, &data[field_nr], format_size);
                        _ptr += format_size;
                    }
                }
            }
            // not valid data, set pointer to memory for next port data
            else {
                _ptr += (format_size * n_fields);
            }
        }
    }
    termout.nof_vals = n_periph * n_ports * n_fields;
    termout.datatype = format;
    delete[] data;
    return retval;
}

/*bool Periph_fpga::write_mm_port(const char *data, TermOutput& termout, const string port_name, const string field_name, const int format) {
    bool retval = true;

    string regname = "mm/0/" + port_name + "/0/" + field_name;
    uint32_t n_periph = mmap->getNPeripherals(regname);
    uint32_t n_ports = mmap->getNPorts(regname);
    uint32_t n_fields = mmap->getNFields(regname);
    uint32_t format_size = reg_format_size_in_bytes(format);
    uint32_t *reg = new uint32_t[n_fields];

    char *_ptr = data;

    for (uint i=0; i<n_periph; i++) {
        for (uint j=0; j<n_ports; j++) {
            regname = "mm/" + to_string(i) + "/" + port_name + "/" + to_string(j) + "/" + field_name;

            for (uint field_nr=0; field_nr<n_fields; field_nr++) {
                memcpy(&reg[field_nr], _ptr, format_size);
                _ptr += format_size;
            }
            retval &= Write(regname, reg);
        }
    }
    termout.nof_vals = n_periph * n_ports * n_fields;
    termout.datatype = format;
    delete[] reg;
    return retval;
}*/


bool Periph_fpga::read_pps_expected_cnt(TermOutput& termout, int format) {
    bool retval = true;
    uint32_t data = 0;
    retval = Read("mm/0/PIO_PPS/0/expected_cnt", &data);
    my_pps_expected_cnt = data;

    uint32_t *_ptr = (uint32_t *)termout.val;
    *_ptr = my_pps_expected_cnt;
    termout.nof_vals = 1;
    termout.datatype = format;
    return retval;
}


bool Periph_fpga::write_pps_expected_cnt(const char *data) {
    uint32_t *_ptr = (uint32_t *)data;
    bool retval = true;


    retval = Write("mm/0/PIO_PPS/0/expected_cnt", _ptr);
    return retval;
}


bool Periph_fpga::read_pps_present(TermOutput& termout, int format, int mode) {
    // my_pps_present is set in read_pps_capture_cnt()
    bool retval = true;
    
    bool *_ptr = (bool *)termout.val;
    *_ptr = my_pps_present;
    termout.nof_vals = 1;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_pps_capture_cnt(TermOutput& termout, int format, int mode) {
    bool retval = true;
    if (mode == R_UCP) {
        uint32_t data = 0;
        retval = Read("mm/0/PIO_PPS/0/capture_cnt", &data);
        my_pps_capture_cnt = data;
        // check if capture count is as expected, and set pps_present
        my_pps_present = false;
        if (my_pps_capture_cnt == my_pps_expected_cnt) {
            my_pps_present = true;
        }
    }

    uint32_t *_ptr = (uint32_t *)termout.val;
    *_ptr = my_pps_capture_cnt;
    termout.nof_vals = 1;
    termout.datatype = format;
    return retval;
}

bool Periph_fpga::read_time_since_last_pps(TermOutput& termout, int format, int mode) {
    bool retval = true;
    if (mode == R_UCP) {
        uint32_t data = 0;
        retval = Read("mm/0/PIO_PPS/0/offset_cnt", &data);
        my_pps_offset_cnt = data;
    }

    uint32_t *_ptr = (uint32_t *)termout.val;
    *_ptr = my_pps_offset_cnt;
    termout.nof_vals = 1;
    termout.datatype = format;
    return retval;
}


bool Periph_fpga::write_wdi_override(TermOutput& termout)
{
    uint32_t data = 0xB007FAC7;
    return Write("mm/0/PIO_WDI/0/wdi_override", &data);
}

CMMap Periph_fpga::read_reg_map()
{
    CMMap reg;
    uint32_t nvalues = REG_ADDR_ROM_SYSTEM_SPAN;
    uint32_t addr = REG_ADDR_ROM_SYSTEM;
    uint32_t *data = new uint32_t[nvalues * sizeof(uint32_t)];
    if (data == NULL) {
        cerr << "malloc error" << endl;
    }
    memset((void *)data, 0, nvalues * sizeof(uint32_t));

    if (!ucp->readRegister(addr, nvalues, data)) {
        if (data != NULL) {
            delete[] data;
        }
        cerr << "ucp->readRegister failed" << endl;
        return reg;
    }

    for (uint i=0; i<nvalues; i++) {
        /*
        cout << "data[" << dec << i << "]=0x" << hex << setw(8) << setfill('0') << data[i] << " ";
        for (int j=0;j<4;j++) {
          char c = (char)(data[i]>>(8*j)) ;
          if (c=='\0') printf("\\0"); else cout << c;
        }
        cout << endl;
        */
        data[i] = ntohl(data[i]);
    }
    char *str_ptr = (char *)data;

    string reg_map_str(str_ptr);
    // cout << "Periph_fpga::read_reg_map:\n" << reg_map_str << endl;
    if (data != NULL) {
        delete[] data;
    }

    istringstream iss_regmap(reg_map_str);
    return mmap_to_regmap(iss_regmap);
}


bool Periph_fpga::write_subband_spectral_inversion(const char *data)
{
    uint32_t _data[1];
    _data[0] = (uint32_t)data[0];
    return Write("mm/0/REG_SI/0/enable", _data);
}