module noc_traffic_counter #(
  parameter SR_REG_BASE = 128,
  parameter RB_REG_BASE = 0)
(
  input bus_clk, input bus_rst,
  input ce_clk, input ce_rst,

  // Control sink
  input [31:0] set_data, input [7:0] set_addr, input set_stb,
  output rb_stb, input [7:0] rb_addr, output [63:0] rb_data,

  // Traffic signals to count
  input i_tlast, input i_tvalid, input i_tready,
  input o_tlast, input o_tvalid, input  o_tready,
  input str_sink_tlast, input str_sink_tvalid, input str_sink_tready,
  input str_src_tlast, input str_src_tvalid, input str_src_tready
);
  wire        en, counter_enable_changed;

  wire [31:0] set_data_bclk;
  wire [7:0]  set_addr_bclk;
  wire        set_stb_bclk;

  reg  [63:0] rb_data_bclk;
  wire [7:0]  rb_addr_bclk;
  reg         rb_stb_bclk;

  reg  [63:0] tick_cnt_noc_shell;

  wire [63:0] xbar_to_shell_xfer_cnt;
  wire [63:0] xbar_to_shell_pkt_cnt;

  wire [63:0] shell_to_xbar_xfer_cnt;
  wire [63:0] shell_to_xbar_pkt_cnt;

  wire [63:0] shell_to_ce_xfer_cnt;
  wire [63:0] shell_to_ce_pkt_cnt;

  wire [63:0] ce_to_shell_xfer_cnt;
  wire [63:0] ce_to_shell_pkt_cnt;

  localparam SR_COUNTER_ENABLE      = SR_REG_BASE + 0;

  localparam RB_SIGNATURE              = RB_REG_BASE + 0;
  localparam RB_BUS_CLK_TICKS          = RB_REG_BASE + 1;
  localparam RB_XBAR_TO_SHELL_XFER_CNT = RB_REG_BASE + 2;
  localparam RB_XBAR_TO_SHELL_PKT_CNT  = RB_REG_BASE + 3;
  localparam RB_SHELL_TO_XBAR_XFER_CNT = RB_REG_BASE + 4;
  localparam RB_SHELL_TO_XBAR_PKT_CNT  = RB_REG_BASE + 5;
  localparam RB_SHELL_TO_CE_XFER_CNT   = RB_REG_BASE + 6;
  localparam RB_SHELL_TO_CE_PKT_CNT    = RB_REG_BASE + 7;
  localparam RB_CE_TO_SHELL_XFER_CNT   = RB_REG_BASE + 8;
  localparam RB_CE_TO_SHELL_PKT_CNT    = RB_REG_BASE + 9;

  // Registers are implemented on bus clock
  axi_fifo_2clk #(.WIDTH(8+8+32), .SIZE(2)) reg_write_to_bclk (
     .reset(ce_rst), .i_aclk(ce_clk),
     .i_tdata({set_addr, rb_addr, set_data}), .i_tvalid(set_stb), .i_tready(),
     .o_aclk(bus_clk),
     .o_tdata({set_addr_bclk, rb_addr_bclk, set_data_bclk}), .o_tvalid(set_stb_bclk), .o_tready(1'b1));

  axi_fifo_2clk #(.WIDTH(64), .SIZE(2)) reg_rb_from_bclk (
     .reset(bus_rst), .i_aclk(bus_clk),
     .i_tdata(rb_data_bclk), .i_tvalid(rb_stb_bclk), .i_tready(),
     .o_aclk(ce_clk),
     .o_tdata(rb_data), .o_tvalid(rb_stb), .o_tready(1'b1));

  setting_reg #(.my_addr(SR_COUNTER_ENABLE), .width(1)) enable_measurement_reg (
    .clk(bus_clk), .rst(bus_rst), .strobe(set_stb_bclk), .addr(set_addr_bclk),
    .in(set_data_bclk), .out(en), .changed(counter_enable_changed));

  always @(posedge bus_clk)
    if (set_stb_bclk) begin
      case(rb_addr_bclk)
        RB_SIGNATURE             : rb_data_bclk <= 64'h712AFF1C00000000;
        RB_BUS_CLK_TICKS         : rb_data_bclk <= tick_cnt_noc_shell;
        RB_XBAR_TO_SHELL_XFER_CNT : rb_data_bclk <= xbar_to_shell_xfer_cnt;
        RB_XBAR_TO_SHELL_PKT_CNT  : rb_data_bclk <= xbar_to_shell_pkt_cnt;
        RB_SHELL_TO_XBAR_XFER_CNT : rb_data_bclk <= shell_to_xbar_xfer_cnt;
        RB_SHELL_TO_XBAR_PKT_CNT  : rb_data_bclk <= shell_to_xbar_pkt_cnt;
        RB_SHELL_TO_CE_XFER_CNT   : rb_data_bclk <= shell_to_ce_xfer_cnt;
        RB_SHELL_TO_CE_PKT_CNT    : rb_data_bclk <= shell_to_ce_pkt_cnt;
        RB_CE_TO_SHELL_XFER_CNT   : rb_data_bclk <= ce_to_shell_xfer_cnt;
        RB_CE_TO_SHELL_PKT_CNT    : rb_data_bclk <= ce_to_shell_pkt_cnt;
        default                   : rb_data_bclk <= 64'h0BADC0DE0BADC0DE;
      endcase
    end

  always @(posedge bus_clk)
    rb_stb_bclk <= set_stb_bclk;

  assign counter_rst = en & counter_enable_changed;

  axis_strm_monitor #(.COUNT_W(64), .PKT_COUNT_EN(1), .XFER_COUNT_EN(1)) xbar_to_shell (
    .clk(bus_clk), .reset(counter_rst),
    .axis_tdata(), .axis_tlast(i_tlast & en), .axis_tvalid(i_tvalid & en), .axis_tready(i_tready & en),
    .xfer_count(xbar_to_shell_xfer_cnt), .pkt_count(xbar_to_shell_pkt_cnt));

  axis_strm_monitor #(.COUNT_W(64), .PKT_COUNT_EN(1), .XFER_COUNT_EN(1)) shell_to_xbar (
    .clk(bus_clk), .reset(counter_rst),
    .axis_tdata(), .axis_tlast(o_tlast & en), .axis_tvalid(o_tvalid & en), .axis_tready(o_tready & en),
    .xfer_count(shell_to_xbar_xfer_cnt), .pkt_count(shell_to_xbar_pkt_cnt));

  axis_strm_monitor #(.COUNT_W(64), .PKT_COUNT_EN(1), .XFER_COUNT_EN(1)) shell_to_ce (
    .clk(bus_clk), .reset(counter_rst),
    .axis_tdata(), .axis_tlast(str_sink_tlast & en), .axis_tvalid(str_sink_tvalid & en), .axis_tready(str_sink_tready & en),
    .xfer_count(shell_to_ce_xfer_cnt), .pkt_count(shell_to_ce_pkt_cnt));

  axis_strm_monitor #(.COUNT_W(64), .PKT_COUNT_EN(1), .XFER_COUNT_EN(1)) ce_to_shell (
    .clk(bus_clk), .reset(counter_rst),
    .axis_tdata(), .axis_tlast(str_src_tlast & en), .axis_tvalid(str_src_tvalid & en), .axis_tready(str_src_tready & en),
    .xfer_count(ce_to_shell_xfer_cnt), .pkt_count(ce_to_shell_pkt_cnt));

  // Count clock ticks
  always @(posedge bus_clk)
    if (counter_rst)
      tick_cnt_noc_shell <= 0;
    else
      if (en)
        tick_cnt_noc_shell <= tick_cnt_noc_shell + 1;

endmodule