//
// Copyright 2013 Ettus Research LLC
//


//
// This module implements a highly customized TCAM that enbales forwarding
// decisions to be made on a 16bit field from a VITA SID field.
// The 16bits are allocated by convention as 8 bits of Network address
// (Addresses USRP's etc) and 8 bits of Host address (adresses endpoints in
// a USRP). By definition if the DEST field in the SID addresses a different
// USRP than this one then we don't care about the Host field, only the Network Field.
// We only look at the Host Field when the Network field addresses us.
// Thus Need TCAM of 256+256 entries with Log2(N) bits, where N is the number of
// slave(output) ports on the crossbar switch.
//
//
//
// SID format:
//
// |--------|---------|--------|---------|
// |      SOURCE      |  DEST  |  DEST   |
// |      ADDRESS     | NETWORK|  HOST   |
// |--------|---------|--------|---------|
//     8         8         8        8
//


`define LOG2(N) (\
                 N < 2 ? 0 : \
                 N < 4 ? 1 : \
                 N < 8 ? 2 : \
                 N < 16 ? 3 : \
                 N < 32 ? 4 : \
                 N < 64 ? 5 : \
		 N < 128 ? 6 : \
		 N < 256 ? 7 : \
		 N < 512 ? 8 : \
		 N < 1024 ? 9 : \
                 10)

module axi_forwarding_cam
  #(
    parameter BASE = 0,      // BASE address for setting registers in this block. (512 addrs used)
    parameter WIDTH=64,      // Bit width of FIFO word.
    parameter NUM_OUTPUTS=2  // Number of outputs (destinations) in crossbar.
    )
    (
     input 			  clk,
     input 			  reset,
     input 			  clear,
     // Monitored FIFO signals
     input [WIDTH-1:0] 		  o_tdata,
     input 			  o_tvalid,
     input 			  o_tready,
     input 			  o_tlast,
     input 			  pkt_present,
     // Configuration
     input [7:0] 		  local_addr,
     // Setting Bus 
     input 			  set_stb,
     input [15:0] 		  set_addr,
     input [31:0] 		  set_data,
     // Forwarding Flags
     output reg [NUM_OUTPUTS-1:0] forward_valid,
     input [NUM_OUTPUTS-1:0] 	  forward_ack,
     // readback bus
     input                        rb_rd_stb,
     input [`LOG2(NUM_OUTPUTS)-1:0] rb_addr,
     output [31:0]                rb_data
     );


   localparam WAIT_SOF = 0;
   localparam WAIT_EOF = 1;
   reg 				  state;
   
   localparam IDLE = 0;
   localparam FORWARD = 1;
   localparam WAIT = 2;
   
   reg 	[1:0]			  demux_state;

   reg [15:0] 			  dst;
   reg 				  dst_valid, dst_valid_reg;
   wire 			  local_dst;
   wire [8:0] 			  read_addr;
   
   //
   // Monitor packets leaving FIFO
   //
   always @(posedge clk)
     if (reset | clear) begin
        state <= WAIT_SOF;
     end else
       case(state)
	 //
	 // After RESET or the EOF of previous packet, the first cycle with
	 // output valid asserted is the SOF and presents the Header word.
	 // The cycle following the concurrent presentation of asserted output 
	 // valid and output ready presents the word following the header.
	 //
         WAIT_SOF: 
           if (o_tvalid && o_tready) begin
              state <= WAIT_EOF;
           end else begin
              state <= WAIT_SOF;
           end
	 //
	 // EOF is signalled by o_tlast asserted whilst output valid and ready asserted.
	 //
         WAIT_EOF: 
           if (o_tlast && o_tvalid && o_tready) begin
              state <= WAIT_SOF;
           end else begin
              state <= WAIT_EOF;
           end
       endcase // case(in_state)  
   
   //
   // Extract Destination fields(s) from SID
   //
   always @(posedge clk)
     if (reset | clear) begin
	dst <= 0;
	dst_valid <= 0;
	dst_valid_reg <= 0;	
     end else if (o_tvalid && (state == WAIT_SOF) && pkt_present) begin
	// SID will remain valid until o_tready is asserted as this will cause a state transition.
	dst <= o_tdata[15:0]; 
	dst_valid <= 1;
	dst_valid_reg <= dst_valid;	
     end else begin
	dst_valid <= 0;
	dst_valid_reg <= dst_valid;
     end

   //
   // Is Network field in DST our local address?
   //
   assign local_dst = (dst[15:8] == local_addr) && dst_valid;
   

   //
   // Mux address to RAM so that it searches CAM for Network field or Host field.
   // Network addresses are stored in the lower 256 locations, host addresses the upper 256.
   //
   assign read_addr = {local_dst,(local_dst ? dst[7:0] : dst[15:8])};

   //
   // Imply a block RAM here, 512xCeil(Log2(NUM_OUTPUTS))
   //
   //synthesis attribute ram_style of mem is block
   reg [(`LOG2(NUM_OUTPUTS))-1 : 0] mem [0:511];
   reg [8:0] 			   read_addr_reg;
   wire 			   write;
   wire [`LOG2(NUM_OUTPUTS)-1:0] 	   read_data;
   
   assign write = (set_addr[15:9] == (BASE >>9)) && set_stb; // Addr decode.
   
   always @(posedge clk) 
     begin
	read_addr_reg <= read_addr;

	if (write) begin
	   mem[set_addr[8:0]] <= set_data[`LOG2(NUM_OUTPUTS)-1:0];
	end

     end

   assign read_data = mem[read_addr_reg];


   //
   // State machine to manage forwarding flags.
   //
    always @(posedge clk)
     if (reset | clear) begin
        demux_state <= IDLE;
     end else
       case(demux_state)
	
	 // Wait for Valid DST which indicates a new packet lookup in the CAM.
	 IDLE: begin
	    if (dst_valid_reg == 1) begin
	       forward_valid <= 1 << read_data;
	       demux_state <= FORWARD;
	    end
	 end
	 // When Slave/Output thats forwarding ACK's the forward flag, clear request and wait for packet to be transfered
	 FORWARD: begin
	    if ((forward_ack & forward_valid) != 0) begin
	       forward_valid <= 0;
	       demux_state <= WAIT;
	    end
	 end
	 // When packet transfered go back to idle.
	 WAIT: begin
	    if (forward_ack == 0)
	      demux_state <= IDLE;
	 end

       endcase // case (demux_state)

   //
   // Compile forwarding statistics
   // (This uses a lot of registers!)
   //
   genvar m;
   reg [31:0] statistics [0:NUM_OUTPUTS-1];
   
   generate
      for (m = 0; m < NUM_OUTPUTS; m = m + 1) begin: generate_stats
	 always @(posedge clk)
	   if (reset | clear) 
	     statistics[m] <= 0;
	   else if ((rb_addr == m) && rb_rd_stb)
	     statistics[m] <= 0;
	   else if (forward_ack[m] & forward_valid[m])
	     statistics[m] <= statistics[m] + 1;
         end
      endgenerate
   
   assign rb_data = statistics[rb_addr];
   	
	  
endmodule