//
// Copyright 2018 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: chdr_traffic_source_sim
// Description:
//   A traffic generator for CHDR traffic. Simulation only.
//   Supports multiple traffic pattern and injection rates.
//

`timescale 1ns/1ps

`include "sim_cvita_lib.svh"

module chdr_traffic_source_sim #(
  parameter        WIDTH        = 64,   // Width of the AXI-Stream data bus
  parameter        MTU          = 5,    // log2 of the max number of lines in a packet
  parameter [15:0] NODE_ID      = 'd0,  // Node ID for this generator
  parameter [15:0] NUM_NODES    = 'd16  // Total number of generators in the application
) (
  // Clocks and resets
  input               clk,              // AXI-Stream clock
  input               rst,              // AXI-Stream reset
  // Settings         
  input [63:0]        current_time,     // The current value of the global timebase (synch to clk)
  input               start_stb,        // A strobe that indicates the start of a generation session
  input [7:0]         injection_rate,   // The inject rate (in percent) to simulate
  input [15:0]        lines_per_pkt,    // Number of lines per packet to generate
  input [7:0]         traffic_patt,     // The traffic pattern (see localparams below for values)
  input [31:0]        num_pkts_to_send, // Number of packets to send
  // CHDR master interface
  output [WIDTH-1:0]  m_axis_tdata,     // AXI-Stream master tdata
  output              m_axis_tlast,     // AXI-Stream master tlast
  output              m_axis_tvalid,    // AXI-Stream master tvalid
  input               m_axis_tready,    // AXI-Stream master tready
  // Metrics          
  output              session_active,   // Signal indicating if generation session is active
  output [63:0]       session_duration, // Session duration (only valid after session ends)
  output [31:0]       xfer_count,       // Number of lines transferred (only valid after session ends)
  output [31:0]       pkt_count         // Number of packets transferred (only valid after session ends)
);
  // **** Supported Traffic Patters ****
  localparam [7:0] TRAFFIC_PATT_LOOPBACK       = 8'd76;  //L
  localparam [7:0] TRAFFIC_PATT_NEIGHBOR       = 8'd78;  //N
  localparam [7:0] TRAFFIC_PATT_BIT_COMPLEMENT = 8'd67;  //C
  localparam [7:0] TRAFFIC_PATT_SEQUENTIAL     = 8'd83;  //S
  localparam [7:0] TRAFFIC_PATT_UNIFORM        = 8'd85;  //U
  localparam [7:0] TRAFFIC_PATT_UNIFORM_OTHERS = 8'd79;  //O
  localparam [7:0] TRAFFIC_PATT_RANDOM_PERM    = 8'd82;  //R

  cvita_master  #(.DWIDTH(WIDTH)) m_chdr (.clk(clk));
  axis_t        #(.DWIDTH(WIDTH)) post_fifo (.clk(clk));
  axis_t        #(.DWIDTH(WIDTH)) pre_gate (.clk(clk));
  cvita_hdr_t  header;
  reg throttle = 1'b1;

  logic        running = 0;
  logic [31:0] curr_pkt_num = 'd0;
  logic [31:0] num_samps_xferd = 'd0;
  logic [63:0] start_time = 0;
  logic [63:0] stop_time = 0;
  logic [15:0] last_gen_sid = (NODE_ID - 16'd1);

  assign xfer_count = num_samps_xferd;
  assign pkt_count = curr_pkt_num;
  assign session_duration = (stop_time - start_time);
  assign session_active = running;

  // Utility function to assign SIDs based on traffic pattern
  function [15:0] gen_dst_sid;
    input [7:0] traffic_patt;
    input [15:0] last_sid;

    logic [31:0] rnum;
    
    if (traffic_patt == TRAFFIC_PATT_UNIFORM) begin
      gen_dst_sid = $urandom_range('d0, NUM_NODES-'d1);
    end else if (traffic_patt == TRAFFIC_PATT_UNIFORM_OTHERS) begin
      rnum = $urandom_range('d0, NUM_NODES-'d2);
      if (rnum < NODE_ID)
        gen_dst_sid = rnum[15:0];
      else
        gen_dst_sid = rnum[15:0] + 16'd1;
    end else if (traffic_patt == TRAFFIC_PATT_SEQUENTIAL) begin
      gen_dst_sid = (last_sid + 16'd1) % NUM_NODES;
    end else if (traffic_patt == TRAFFIC_PATT_NEIGHBOR) begin
      gen_dst_sid = (NODE_ID + 16'd1) % NUM_NODES;
    end else if (traffic_patt == TRAFFIC_PATT_LOOPBACK) begin
      gen_dst_sid = NODE_ID;
    end else if (traffic_patt == TRAFFIC_PATT_BIT_COMPLEMENT) begin
      gen_dst_sid = (NUM_NODES - NODE_ID - 1) % NUM_NODES;
    end else if (traffic_patt == TRAFFIC_PATT_RANDOM_PERM) begin
      //TODO: Implement me
      gen_dst_sid = 0;
    end else begin
      gen_dst_sid = 'd0;
    end
  endfunction

  // Generation loop. Push to m_chdr infinitely fast
  initial begin: gen_blk
    // Generate infinitely
    std::process p;
    p = process::self();
    p.srandom(NODE_ID + NUM_NODES);
    m_chdr.reset();
    while (1) begin
      // A generation session begins on the posedge of start_stb
      while (start_stb !== 1) @(posedge clk);
      curr_pkt_num = 'd0;
      m_chdr.reset();
      num_samps_xferd = 'd0;
      start_time = current_time;
      running = 1;
      while (curr_pkt_num < num_pkts_to_send) begin
        header = '{
          pkt_type:DATA, has_time:1, eob:0,
          seqnum:curr_pkt_num[11:0], length:(lines_per_pkt*8),
          src_sid:NODE_ID, dst_sid:gen_dst_sid(traffic_patt, last_gen_sid),
          timestamp:0 //TS attached later
        };
        last_gen_sid = header.dst_sid;
        curr_pkt_num = curr_pkt_num + 'd1;
        m_chdr.push_ramp_pkt(lines_per_pkt-2, 'h0, 'h1, header);
        num_samps_xferd = num_samps_xferd + lines_per_pkt;
      end
      running = 0;
      stop_time = current_time;
    end
  end

  // Capture packets in a really short FIFO (for backpressure)
  axi_fifo #(
    .WIDTH(WIDTH+1), .SIZE(MTU + 1)
  ) fifo_i (
    .clk      (clk), 
    .reset    (rst), 
    .clear    (1'b0),
    .i_tdata  ({m_chdr.axis.tlast, m_chdr.axis.tdata}),
    .i_tvalid (m_chdr.axis.tvalid),
    .i_tready (m_chdr.axis.tready),
    .o_tdata  ({post_fifo.tlast, post_fifo.tdata}),
    .o_tvalid (post_fifo.tvalid),
    .o_tready (post_fifo.tready),
    .space    (),
    .occupied ()
  );

  // Attach timestamp after the packet leaves the FIFO after
  // throttling. 

  localparam [1:0] ST_HDR   = 2'd0;
  localparam [1:0] ST_TS    = 2'd1;
  localparam [1:0] ST_BODY  = 2'd2;

  reg [1:0] pkt_state = ST_HDR;
  always_ff @(posedge clk) begin
    if (rst) begin
      pkt_state <= ST_HDR;
    end else if (pre_gate.tvalid & pre_gate.tready) begin
      case (pkt_state)
        ST_HDR:
          if (~pre_gate.tlast)
            pkt_state <= pre_gate.tdata[61] ? ST_TS : ST_BODY;
        ST_TS:
          pkt_state <= pre_gate.tlast ? ST_HDR : ST_BODY;
        ST_BODY:
          pkt_state <= pre_gate.tlast ? ST_HDR : ST_BODY;
        default:
          pkt_state <= ST_HDR;
      endcase
    end
  end

  // Enforce injection rate by pulling from FIFO with a certain time probability
  always_ff @(posedge clk) begin
    throttle <= ($urandom_range(32'd99, 32'd0) > {24'h0, injection_rate});
  end

  // Insert timestamp + throttle logic
  assign pre_gate.tdata   = (pkt_state == ST_TS) ? current_time : post_fifo.tdata;
  assign pre_gate.tlast   = post_fifo.tlast;
  assign pre_gate.tvalid  = post_fifo.tvalid & ~throttle;
  assign post_fifo.tready = pre_gate.tready & ~throttle;

  // Gate the packet to smooth out throttle-related noise.
  // This also serves as a buffer for the packet in case things are backed up
  axi_packet_gate #(
    .WIDTH(WIDTH), .SIZE(MTU + 4), .USE_AS_BUFF(1)
  ) pkt_gate_i (
    .clk      (clk), 
    .reset    (rst), 
    .clear    (1'b0),
    .i_tdata  (pre_gate.tdata),
    .i_tlast  (pre_gate.tlast),
    .i_terror (1'b0),
    .i_tvalid (pre_gate.tvalid),
    .i_tready (pre_gate.tready),
    .o_tdata  (m_axis_tdata),
    .o_tlast  (m_axis_tlast),
    .o_tvalid (m_axis_tvalid),
    .o_tready (m_axis_tready)
  );

endmodule