// editted 
// Module was to implement a deficit round-robin routine to drain 8 
// MAC 100Mbit ethernet queues which would get mux'd into 1 GigE-rate
// stream.  The buffer memory size was restricted, and the MAC chosen
// for the board would only buffer 256 bytes each (max) of the 8 incoming
// 100Mbit streams.

module rxgio (clk, desc_addr_out, desc_rd_data, desc_we, desc_wr_data,
        ffrxbyte, ffrxclk, ffrxda, ffrxdata, ffrxeof, ffrxerr,
        ffrxhalf, ffrxren, ffrxsof, fifo_addr, fifo_data, fifo_we,
        reset_n);
    input [31:0] desc_rd_data;   // read descriptor
    input [31:0] fifo_data;      // datapath
    input clk;
    input ffrxren;               // rx enable
    input reset_n;
    output [12:0] fifo_addr;     // addr into fifo
    output [31:0] desc_wr_data;  // write descriptor
    output [31:0] ffrxdata;      // datapath out
    output [9:0] desc_addr_out;  // descriptor addr
    output desc_we;              // descriptor write enable
    output ffrxbyte;             // NP byte flag
    output ffrxclk;
    output ffrxda;
    output ffrxeof;              // NP End of Frame marker
    output ffrxerr;              // NP error
    output ffrxhalf;             // NP 32 vs 16 bit
    output ffrxsof;              // NP Start of Frame 
    output fifo_we;


   reg [31:0] desc_wr_data;
   reg [31:0] ffrxdata;
   reg [6:0]  desc_addr;
   reg 	      desc_we;
   reg 	      ffrxbyte;
   reg 	      ffrxclk;
   reg 	      ffrxda;
   reg 	      ffrxeof;
   reg 	      ffrxerr;
   reg 	      ffrxhalf;
   reg 	      ffrxsof;
   reg 	      fifo_we;
   reg [4:0]  drain_state;
   reg [2:0]  port;
   reg 	      fill_complete;
   reg [10:0] length;
   reg 	      ixferr_flag;
   reg [9:0]  drain_addr;
   reg [9:0]  drain_count;
   reg [6:0]  desc_ptr[7:0];
   reg [15:0] cam_result;
   reg 	      cam_result_rdy;
   reg 	      cam_discard;
   reg [31:0] ffrxdata_hold; /// must pipeline to accomodate dual_ports
   wire [9:0] desc_addr_out = {port[2:0],desc_addr[6:0]};
   wire [12:0] fifo_addr = {port[2:0], drain_addr[9:0]};

   wire [6:0]  desc_ptr_a = desc_ptr[0];
   wire [6:0]  desc_ptr_b = desc_ptr[1];
   wire [6:0]  desc_ptr_c = desc_ptr[2];
   wire [6:0]  desc_ptr_d = desc_ptr[3];
   wire [6:0]  desc_ptr_e = desc_ptr[4];
   wire [6:0]  desc_ptr_f = desc_ptr[5];
   wire [6:0]  desc_ptr_g = desc_ptr[6];
   wire [6:0]  desc_ptr_h = desc_ptr[7];
   
   parameter  DRAIN_INIT        = 5'd0;
   parameter  DRAIN_LOAD_DESC_A = 5'd1;
   parameter  DRAIN_LOAD_DESC_A2 = 5'd2;
   parameter  DRAIN_LOAD_DESC_B = 5'd3;
   parameter  DRAIN_LOAD_DESC_B2 = 5'd4;
   parameter  DRAIN_LOAD_DESC_C = 5'd5;
   parameter  DRAIN_DECISION    = 5'd8;
   parameter  DRAIN_HEADER      = 5'd9;
   parameter  DRAIN_DRAIN       = 5'd10;
   parameter  DRAIN_EOP_A       = 5'd11;
   parameter  DRAIN_EOP_B       = 5'd12;
   parameter  DRAIN_EOP_C       = 5'd13;
   

   parameter  READ  = 1'b0;
   parameter  WRITE = 1'b1;

   parameter  DRAIN_COUNT = 10'd1000;  // always drain >= 1000 bytes 

`include "/home/zimmy/gio/src/tpd.v"

   wire       pkt_not_ready = (ixferr_flag | cam_discard | ~cam_result_rdy | ~fill_complete);
   
always @(posedge clk or negedge reset_n)
  if (reset_n == 1'b0)
    begin
       desc_wr_data <= `tpd 1'b0;
       ffrxdata     <= `tpd 32'h87654321;
       ffrxdata_hold <= `tpd 32'h12345678;
       desc_addr    <= `tpd 7'd0;
       desc_we      <= `tpd 1'b0;
       fifo_we      <= `tpd 1'b0;
       ffrxbyte     <= `tpd 1'b0;
       ffrxclk      <= `tpd 1'b0;
       ffrxda       <= `tpd 1'b0;
       ffrxeof      <= `tpd 1'b0;
       ffrxerr      <= `tpd 1'b0;
       ffrxhalf     <= `tpd 1'b0;
       ffrxsof      <= `tpd 1'b0;
       drain_state  <= `tpd DRAIN_INIT;
       port         <= `tpd 3'd0;
       fill_complete <= `tpd 1'b0;
       ixferr_flag   <= `tpd 1'b0;
       length        <= `tpd 11'd0;
       drain_addr    <= `tpd 10'd0;
       drain_count   <= `tpd DRAIN_COUNT;       
       desc_ptr[0]   <= `tpd 7'h00; 
       desc_ptr[1]   <= `tpd 7'h00;
       desc_ptr[2]   <= `tpd 7'h00;
       desc_ptr[3]   <= `tpd 7'h00;
       desc_ptr[4]   <= `tpd 7'h00;
       desc_ptr[5]   <= `tpd 7'h00;
       desc_ptr[6]   <= `tpd 7'h00;
       desc_ptr[7]   <= `tpd 7'h00;	
       cam_result    <= `tpd 16'd0;
       cam_result_rdy <= `tpd 1'b0;
       cam_discard    <= `tpd 1'b0;
    end // if (reset_n == 1'b0)
  else begin
     fifo_we  <= `tpd 1'b0;
     ffrxdata <= `tpd ffrxdata_hold;
     ffrxerr  <= `tpd 1'b0;
     ffrxclk  <= `tpd 1'b0;
     
     if(drain_state == DRAIN_EOP_A)
       begin
	  case (length[1:0])
	    2'b00: begin
	       ffrxbyte     <= `tpd 1'b0;
	       ffrxhalf     <= `tpd 1'b0;
	    end
	    2'b01: begin
	       ffrxbyte     <= `tpd 1'b1;
	       ffrxhalf     <= `tpd 1'b1;
	    end
	    2'b10: begin
	       ffrxbyte     <= `tpd 1'b0;
	       ffrxhalf     <= `tpd 1'b1;
	    end
	    2'b11: begin
	       ffrxbyte     <= `tpd 1'b1;
	       ffrxhalf     <= `tpd 1'b0;
	    end
	  endcase // case(length[1:0])
	  ffrxeof      <= `tpd 1'b1;
       end
     else
       begin
	  ffrxbyte     <= `tpd 1'b0;
	  ffrxhalf     <= `tpd 1'b0;
	  ffrxeof      <= `tpd 1'b0;
       end     
     
     case (drain_state)
       DRAIN_INIT:
	 begin
	    desc_addr    <= `tpd desc_addr;
	    drain_state <= `tpd DRAIN_LOAD_DESC_A;
	 end
       DRAIN_LOAD_DESC_A:
	 begin
	    desc_addr   <= `tpd desc_ptr[port];
	    desc_we     <= `tpd READ;
	    drain_state <= `tpd DRAIN_LOAD_DESC_A2;
	 end
       DRAIN_LOAD_DESC_A2:
	 begin
	    desc_addr   <= `tpd desc_addr;
	    desc_we     <= `tpd READ;
	    drain_state <= `tpd DRAIN_LOAD_DESC_B;
	 end
       DRAIN_LOAD_DESC_B:
	 begin // read descriptor word A
	    desc_addr     <= `tpd desc_addr + 7'd1;
	    fill_complete <= `tpd desc_rd_data[31];
	    ixferr_flag   <= `tpd desc_rd_data[30];
	    length        <= `tpd desc_rd_data[26:16];
	    drain_addr    <= `tpd drain_addr;
	    desc_we       <= `tpd READ;
	    drain_state   <= `tpd DRAIN_LOAD_DESC_B2;
      	 end
       DRAIN_LOAD_DESC_B2:
	 begin
	    desc_addr   <= `tpd desc_addr;
	    desc_we     <= `tpd READ;
	    drain_state <= `tpd DRAIN_LOAD_DESC_C;
	 end
       DRAIN_LOAD_DESC_C:
	 begin  // read descriptor word B
	    ffrxda         <= `tpd 1'b0;
	    desc_addr      <= `tpd desc_addr;
	    fill_complete  <= `tpd fill_complete;
	    ixferr_flag    <= `tpd ixferr_flag;
	    length         <= `tpd length;
	    desc_we        <= `tpd READ;
	    cam_result     <= `tpd desc_rd_data[31:16];
	    cam_result_rdy <= `tpd desc_rd_data[15];
	    cam_discard    <= `tpd desc_rd_data[14];
	    drain_addr     <= `tpd desc_rd_data[9:0]; 
	    drain_state    <= `tpd DRAIN_DECISION;
	    ffrxdata_hold  <= `tpd {4'h0,1'b0,port,8'h00,desc_rd_data[31:16]};

	 end
       DRAIN_DECISION:
	 begin
`ifdef RXSIM
	    $display("RXGIO:DRAIN_DECISION for port %d, desc_addr: 0x%h, ixferr_flag:%d, cam_result_rdy:%d, cam_result: 0x%h,fill_complete:%d",port,desc_addr,ixferr_flag,cam_result_rdy,cam_result,fill_complete);
`endif
	    ffrxdata_hold  <= `tpd {4'h0,1'b0,port,8'h00,desc_rd_data[31:16]};
//	    ffrxdata_hold <= `tpd fifo_data;
	    drain_addr   <= `tpd drain_addr + 1;
	    ffrxsof      <= `tpd ~pkt_not_ready;
	    port         <= `tpd pkt_not_ready ? port + 1 : port;  
	    drain_state  <= `tpd pkt_not_ready ? DRAIN_LOAD_DESC_A : DRAIN_HEADER;
	 end
       // ----------------------------------------------------------------------------------------
       DRAIN_HEADER:
	 begin
`ifdef RXSIM
	    $display("RXGIO:DRAIN_pkt_rdy on port %d (desc_addr: %h), length: %d, start addr: %h, cam_result:%h",port,desc_addr, length, drain_addr, cam_result);
`endif
	    drain_addr  <= `tpd drain_addr + 1;
	    desc_addr   <= `tpd desc_addr;
	    ffrxda      <= `tpd 1'b1;
	    ffrxdata_hold <= `tpd fifo_data;
	    ffrxsof     <= `tpd 1'b1;
	    drain_state <= `tpd DRAIN_DRAIN;
	 end
       DRAIN_DRAIN:
	 begin
	    desc_addr   <= `tpd desc_addr;
	    ffrxda      <= `tpd 1'b1;
	    drain_count <= `tpd (drain_count != 10'd0) ? drain_count - 10'd4 : drain_count;
	    length      <= `tpd length - 10'd4;
	    ffrxdata_hold <= `tpd fifo_data;
	    ffrxsof     <= `tpd 1'b0;
	    drain_addr  <= `tpd drain_addr + 1;
	    drain_state <= `tpd (length <= 11'd8) ? DRAIN_EOP_A  : DRAIN_DRAIN;
	 end
       DRAIN_EOP_A:
	 // free up this descriptor (zero word A)
	 begin
`ifdef RXSIM
	    $display("RXGIO:DRAIN_EOP_A");
`endif
	    ffrxda       <= `tpd 1'b1;
	    ffrxdata_hold <= `tpd fifo_data;
	    desc_addr    <= `tpd desc_ptr[port];
	    desc_wr_data <= `tpd 32'd0;
	    ffrxsof      <= `tpd 1'b0;
	    desc_we      <= `tpd WRITE;
	    drain_state  <= `tpd DRAIN_EOP_B;
	 end
       DRAIN_EOP_B:
	 // zero word B
	 begin
`ifdef RXSIM
	    $display("RXGIO:DRAIN_EOP_B");
`endif
	    ffrxda       <= `tpd 1'b0;
	    desc_addr    <= `tpd desc_addr + 7'd1;
	    desc_wr_data <= `tpd 32'd0;
	    ffrxsof      <= `tpd 1'b0;
	    desc_we      <= `tpd WRITE;
	    drain_state  <= `tpd DRAIN_EOP_C;
	 end
       DRAIN_EOP_C:
	 // inc to next decsriptor
	 // if drain_count still not zero, then stay on this port, next desc
	 // if drain_count == zero, then next port
	 begin
	    ffrxeof      <= `tpd 1'b0;
	    desc_ptr[0]  <= `tpd (port == 3'd0 ) ? desc_ptr[0] + 7'd2 : desc_ptr[0];
	    desc_ptr[1]  <= `tpd (port == 3'd1 ) ? desc_ptr[1] + 7'd2 : desc_ptr[1];
	    desc_ptr[2]  <= `tpd (port == 3'd2 ) ? desc_ptr[2] + 7'd2 : desc_ptr[2];
	    desc_ptr[3]  <= `tpd (port == 3'd3 ) ? desc_ptr[3] + 7'd2 : desc_ptr[3];
	    desc_ptr[4]  <= `tpd (port == 3'd4 ) ? desc_ptr[4] + 7'd2 : desc_ptr[4];
	    desc_ptr[5]  <= `tpd (port == 3'd5 ) ? desc_ptr[5] + 7'd2 : desc_ptr[5];
	    desc_ptr[6]  <= `tpd (port == 3'd6 ) ? desc_ptr[6] + 7'd2 : desc_ptr[6];
	    desc_ptr[7]  <= `tpd (port == 3'd7 ) ? desc_ptr[7] + 7'd2 : desc_ptr[7];
	    desc_wr_data <= `tpd 32'd0;
	    ffrxsof      <= `tpd 1'b0;
	    desc_we      <= `tpd READ;
	    port         <= `tpd (drain_count != 10'd0) ? port : port + 1;
	    drain_state  <= `tpd DRAIN_LOAD_DESC_A;
`ifdef RXSIM
	    $display("RXGIO:DRAIN_EOP_C: %d bytes left for port %d",drain_count,port);
`endif
	 end
       default:
	 begin
	    desc_addr    <= `tpd desc_addr;
	    drain_state  <= `tpd DRAIN_INIT;
	 end
     endcase // case(drain_state)
     
  end
endmodule
