#!/usr/bin/env python3
""" hudp_setup.py : configure Hardware UDP 
hudp_setup.py [opts] TXUUT RXUUT
sets up a one way transfer from TXUUT to RXUUT
Increasing spp reduces the packet rate per sample, potentially enabling a higher sample rate (do NOT exceed MTU 1400 bytes)
Increasing decimation reduces the packet rate, suitable for spp=1 low latency control, while full rate data flows to DRAM for archive
The DISCOntinuity check is a packet data checker. 
Typically, the TX data comes from ACQ2106 with SPAD enabled, and the DISCO index is SPAD[0], sample ramp.
If either TXUUT or RXUUT is NOT an ACQ2106, or has already been configured for one direction, specify "none"
Examples:
Send data from UUT acq2106_363 at tx_ip ip 10.12.198.128 to UUT acq2106_364 at rx_ip=10.12.198.129::
    ./user_apps/acq2106/hudp_setup.py --rx_ip=10.12.198.128 --tx_ip 10.12.198.129 --run0='1 1,16,0' --play0='1 16' acq2106_363 acq2106_364
Send data from UUT acq2106_363 at tx_ip ip 10.12.198.128 to non-HUDP destination rx_ip=10.12.198.254::
    ./user_apps/acq2106/hudp_setup.py --rx_ip=10.12.198.254 --tx_ip 10.12.198.128 --run0='1 1,16,0' acq2106_363 none
Send data from non-HUDP source at tx_ip 10.12.198.254 to UUT acq2106_363 at rx_ip  10.12.198.128::
    ./user_apps/acq2106/hudp_setup.py --rx_ip=10.12.198.128 --tx_ip 10.12.198.254 --play0='1 16' none acq2106_363
In all cases,
for UUT Tx, run0 specifies data from site1 followed by a 16 column ScratchPAD.
for UUT Rx, play0 specifues datas to site1 followed by a 16 column TrashCAN.
This allows, for example a 32 channel, 16 bit ADC to play data direct to a 32 channel DAC, 
including instrumentation that could be checked with --disco=16 (SPAD[0] at offset 16 LW)
::
    [pgm@hoy5 acq400_hapi]$ cat /home/pgm/PROJECTS/ACQ400/ACQ420FMC/NOTES/HUDPDEMO.txt
    #!/bin/bash
    set -x
    TX1=${TX1:-acq2106_189} 
    RX1=${RX1:-acq2106_274}
    RX2=${RX2:-acq2106_130}
    IP_TX1=${IP_TX1:-10.12.198.128}
    IP_RX1=${IP_RX1:-10.12.198.129}
    IP_RX2=${IP_RX2:-10.12.198.130}
    echo HUDP Demo TX1:$TX1,$IP_TX1 RX1:$RX1,$IP_RX1 RX2:$RX2,$IP_RX2
    echo set clk/trg
    ./user_apps/acq400/sync_role.py --fin=50k --fclk=50k --si5326_bypass 1 --toprole=fpmaster,strg acq2106_189
    echo 'UNICAST ->' $RX1
    ./user_apps/acq2106/hudp_setup.py --tx_ip $IP_TX1 --rx_ip $IP_RX1 --broadcast=0 $TX1 $RX1
    read continue
    echo 'UNICAST ->' $RX2
    ./user_apps/acq2106/hudp_setup.py --tx_ip $IP_TX1 --rx_ip $IP_RX2 --broadcast=0 $TX1 $RX2
    echo 'BROADCAST ->' $RX1 $RX2 and naboo
    ./user_apps/acq2106/hudp_setup.py --tx_ip $IP_TX1 --rx_ip $IP_RX1 --broadcast=1 $TX1 $RX1
    ./user_apps/acq2106/hudp_setup.py --tx_ip $IP_TX1 --rx_ip $IP_RX2 --broadcast=1 $TX1 $RX2
.. rst-class:: hidden
    usage: hudp_setup.py [-h] [--netmask NETMASK] [--tx_ip TX_IP] [--rx_ip RX_IP] [--gw GW] [--port PORT] [--run0 RUN0] 
                        [--play0 PLAY0] [--broadcast BROADCAST] [--disco DISCO] [--spp SPP]
                        [--hudp_decim HUDP_DECIM]
                        txuut rxuut
    hudp_setup
    positional arguments:
    txuut                 transmit uut
    rxuut                 transmit uut
    options:
    -h, --help            show this help message and exit
    --netmask NETMASK     netmask (default: 255.255.255.0)
    --tx_ip TX_IP         tx ip address (default: 10.12.198.128)
    --rx_ip RX_IP         rx ip address (default: 10.12.198.129)
    --gw GW               gateway (default: 10.12.198.1)
    --port PORT           port (default: 53676)
    --run0 RUN0           set tx sites+spad (default: 1 1,16,0)
    --play0 PLAY0         set rx sites+spad (default: 1 16)
    --broadcast BROADCAST  broadcast the data (default: 0)
    --disco DISCO         enable discontinuity check at index x (default: None)
    --spp SPP             samples per packet (default: 1) 
    --hudp_decim HUDP_DECIM  hudp decimation, 1..16 (default: 1)
"""
import argparse
import acq400_hapi
import time
import sys
if sys.version_info < (3, 0):
    from future import builtins
    from builtins import input
[docs]def hudp_init(args, uut, ip):
    uut.s10.tx_ctrl = 9
    uut.s10.ip = ip
    uut.s10.gw = args.gw
    uut.s10.netmask = args.netmask
    if args.disco is not None:
        print("enable disco at {}".format(args.disco))
        uut.s10.disco_idx = args.disco
        uut.s10.disco_en  = 1
    else:
        uut.s10.disco_en = 0 
    
[docs]def hudp_enable(uut):
    uut.s10.tx_ctrl = 1 
[docs]def init_arp_req(uut):
    uut.s10.arp_request = 1
    uut.s10.arp_request = 0 
    
[docs]def ip_broadcast(args):
    ip_dest = args.rx_ip.split('.')
    nm = args.netmask.split('.')
    
    for ii in range(3,0,-1):
        if nm[ii] != '0':
            break
        else:
            ip_dest[ii] = '255'
    
    return '.'.join(ip_dest) 
        
MTU = 9038 - 66
# tx: XI : AI, DI       
[docs]def config_tx_uut(txuut, args):    
    print("txuut {}".format(txuut.uut))
    if args.run0 != 'notouch':
        txuut.s0.run0 = args.run0
    hudp_init(args, txuut, args.tx_ip)
    txuut.s10.hudp_decim = args.hudp_decim
    txuut.s10.src_port = args.port
    txuut.s10.dst_port = args.port
    txuut.s10.dst_ip = args.rx_ip if args.broadcast == 0 else ip_broadcast(args)
    if args.hudp_relay is not None:
        txuut.s10.udp_data_src = 1
        tx_ssb = int(txuut.s0.dssb) - args.hudp_relay
        txuut.s10.slice_len = tx_ssb//4
        txuut.s10.slice_off = args.hudp_relay//4
    else:
        txuut.s10.udp_data_src = 0
        tx_ssb = int(txuut.s0.ssb)
    txuut.s10.tx_sample_sz = tx_ssb
    txuut.s10.tx_spp = args.spp
    tx_pkt_sz = tx_ssb*args.spp                         # compute tx pkt sz and check bounds
    if  tx_pkt_sz > MTU:
        print("ERROR packet length {} exceeds MTU {}".format(tx_pkt_sz, MTU))
    hudp_enable(txuut)
    init_arp_req(txuut)
    tx_calc_pkt_sz = int(txuut.s10.tx_calc_pkt_sz)      # actual tx pkt sz computed by FPGA logic.
    if tx_pkt_sz != tx_calc_pkt_sz:
        print("ERROR: set tx_pkt_size {} actual tx_pkt_size {}".format(tx_pkt_sz, tx_calc_pkt_sz))    
    print("TX configured. ssb:{} spp:{} tx_pkt_size {}".format(tx_ssb, args.spp, tx_pkt_sz)) 
# rx: XO : AO, DO        
[docs]def config_rx_uut(rxuut, args):
    print("rxuut {}".format(rxuut.uut))
       
    if args.play0 != 'notouch':
        rxuut.s0.play0 = args.play0       
    rxuut.s0.distributor = 'comms=U off'        
    rxuut.s0.distributor = 'on'
    hudp_init(args, rxuut, args.rx_ip)
    rxuut.s10.rx_src_ip = args.tx_ip
    rxuut.s10.rx_port = args.port
    hudp_enable(rxuut)     
   
PCSEL = ("none", "pc")
[docs]def run_main(args):
    if args.gw is None:
        args.gw = args.rx_ip
    if args.txuut[0] not in PCSEL:
        config_tx_uut(acq400_hapi.factory(args.txuut[0]), args)
    if args.rxuut[0] not in PCSEL:
        config_rx_uut(acq400_hapi.factory(args.rxuut[0]), args) 
[docs]def get_parser():
    parser = argparse.ArgumentParser(description="Setup HUDP for UUTs", 
                formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("--netmask", default='255.255.255.0', help='netmask')
    parser.add_argument("--tx_ip",   default='10.12.198.128', help='tx ip address')
    parser.add_argument("--rx_ip",   default='10.12.198.129', help='rx ip address')
    parser.add_argument("--gw",      default=None,   help='gateway')
    parser.add_argument("--port",    default='53676',         help='port')
    parser.add_argument("--run0",    default='1 1,16,0',      help="set tx sites+spad or notouch if set elsewhere")
    parser.add_argument("--play0",   default='1 16',          help="set rx sites+spad or notouch if set elsewhere")
    parser.add_argument("--broadcast", default=0, type = int, help="broadcast the data")
    parser.add_argument("--disco",   default=None, type=int,  help="enable discontinuity check at index x")
    parser.add_argument("--hudp_relay", default=None, type=int,  help="0..N: relay LLC VI out HUDP txt offset in vector")
    parser.add_argument("--spp",     default=1, type=int,     help="samples per packet")
    parser.add_argument("--hudp_decim", default=1, type=int,  help="hudp decimation, 1..16")
    parser.add_argument("txuut", nargs=1,                     help=f"transmit uut (if it's a PC, type one of {PCSEL})")
    parser.add_argument("rxuut", nargs=1,                     help=f"rx uut (if it's a PC, type one of {PCSEL})")
    return parser 
    
if __name__ == '__main__':
    run_main(get_parser().parse_args())