/*****************************************************************************
 AvalonFIFOs
 ===========
 Simon Moore, Dec 2009
 
 Dec 2009 - added multiported version
 
 Provides a set of FIFOs (specified by "num_fifos") and one Avalon Slave
 interface to access them.  Data is tagged with one byte specified via
 register 1.  The tag should be written before the data is written and it
 should be read after the data is read.  Each FIFO is allocated 4 word 
 addresses to access it.
 
 Processor memory map:
   read/write  address   meaning
     read         0      read from fifo
     write        0      write word to fifo
     read         1      bit 9 = overflow-output, bit 8 = underflow-input
                         bits 7-0 = tag byte
     write        1      write tag byte
     read         2      number of waiting data items
     write        2      clear fifo + overflow and underflow bits
     read         3      number of empty fifo slots
     write        3      clear fifo + overflow and underflow bits

  Bug fixes:
   * in mkAvalonFIFOs_UGFIFOCount
      - moved fifo.clear from clear method to keep_count rule
        - the fifo is now cleared when the counter is cleared
        - seemed to be necessary to help the scheduler otherwise
          fifo.enq in do_enq never fired despite no_implicit_conditions
 *****************************************************************************/

package AvalonFIFOs;

import GetPut::*;
import Vector::*;
import ClientServer::*;
import Avalon2ClientServer::*;
import FIFOF::*;
import FIFOLevel::*;
import FIFO::*;
import Connectable::*;

`define fifo_depth 32
// num fifos for single Avalon port version
`define num_fifos 4
// num ports for multi-Avalon slave version
`define num_ports 3
`define fifo_address_width 2
//`define replication_address_width Log(num_fifos)
`define replication_address_width 2
//`define total_address_width (fifo_address_width+replication_address_width);
`define total_address_width 4

(* always_enabled, always_ready *)
interface AvalonFIFOs_UGFIFOCountIfc#( type element_type );
   method Action enq ( element_type sendData ) ;
   method Action deq () ;
   method element_type first () ;
   method Bool notFull ;
   method Bool notEmpty ;
   method UInt#(32) count; //TODO: fix this hack...
   method Action clear;
endinterface


// like FIFOCountIfc but with ungarded enq and deq
module mkAvalonFIFOs_UGFIFOCount#(Integer fifoDepth)(AvalonFIFOs_UGFIFOCountIfc#(element_type))
   provisos (Bits#(element_type,element_type_width));

   FIFOF#(element_type) fifo <- mkUGSizedFIFOF(fifoDepth);
   Wire#(Maybe#(element_type)) enq_dw <- mkDWire(tagged Invalid);
   PulseWire deq_pulse <- mkPulseWire;
   Reg#(UInt#(32)) counter <- mkReg(0); // hack - fix
   PulseWire count_down <- mkPulseWire;
   PulseWire count_up <- mkPulseWire;
   PulseWire count_zero <- mkPulseWire;
   Wire#(element_type) first_bw <- mkBypassWire;
   
   (* no_implicit_conditions *)
   rule do_first_bypass (True); // needed?
      first_bw <= fifo.notEmpty ? fifo.first : unpack(0);
   endrule
   
   (* no_implicit_conditions *)
   rule keep_count;
      if(count_zero)
	 begin
	    counter <= 0;
	    fifo.clear;
	 end
      else if(count_up && !count_down) counter<=counter+1;
      else if(!count_up && count_down) counter<=counter-1;
   endrule
   
   (* no_implicit_conditions *)
   rule do_enq (isValid(enq_dw) && fifo.notFull);
      fifo.enq(fromMaybe(?,enq_dw));
      count_up.send;
      $display("%05d: %m - do_enq fired",$time);
   endrule
   
   rule do_enq_failed (isValid(enq_dw) && !fifo.notFull);
      $display("%05d: %m - WARNING: fifo enq when full",$time);
   endrule
   
   (* no_implicit_conditions *)
   rule do_deq (deq_pulse && fifo.notEmpty);
      fifo.deq;
      count_down.send;
   endrule
   
   rule do_deq_failed (deq_pulse && !fifo.notEmpty);
      $display("Warning: fifo deq when full");
   endrule
   
   method Action enq ( element_type sendData );
      enq_dw <= tagged Valid sendData;
   endmethod
   method Action deq ();
      deq_pulse.send;
   endmethod
   method element_type first (); return first_bw; endmethod
   method Bool notFull = fifo.notFull;
   method Bool notEmpty = fifo.notEmpty;
   method count; return extend(counter); endmethod
   method Action clear;
      count_zero.send;
   endmethod
endmodule
 
   
typedef UInt#(8) AvalonFIFOs_tagT;
   
typedef struct {
   AvalonFIFOs_tagT  tag;
   AvalonWordT       data;
   } AvalonFIFOs_tagdataT deriving (Bits);


typedef struct {
   UInt#(`replication_address_width) addr_top;
   UInt#(`fifo_address_width) addr_bot;
} AvalonFIFOs_Addr_SeperatorT deriving (Bits);
   
interface AvalonFIFOsIfc;
   interface AvalonSlaveIfc#(`total_address_width) avs;
endinterface
      
(* synthesize,
   reset_prefix = "csi_clockreset_reset_n",
   clock_prefix = "csi_clockreset_clk" *)
(* doc="************** Simon's comments for Verilog code: **************" *)
(* doc="Top level module for Avalon Bridge Example" *)
(* doc="Port names should align with SOPC builder's new device requirements" *)
(* doc="Need to add the $BLUESPECDIR/Verilog directory to the Libraries in Quartus" *)
module mkAvalonFIFOs(AvalonFIFOsIfc);
   AvalonSlave2ClientIfc#(`total_address_width) slave <- mkAvalonSlave2Client;
   
   Vector#(`num_fifos,AvalonFIFOs_UGFIFOCountIfc#(AvalonFIFOs_tagdataT)) f <- replicateM(mkAvalonFIFOs_UGFIFOCount(`fifo_depth));
   Vector#(`num_fifos,Reg#(AvalonFIFOs_tagT)) tag_in  <- replicateM(mkReg(0));
   Vector#(`num_fifos,Reg#(AvalonFIFOs_tagT)) tag_out <- replicateM(mkReg(0));
   Vector#(`num_fifos,Reg#(Bool)) underflow <- replicateM(mkReg(False));
   Vector#(`num_fifos,Reg#(Bool)) overflow <- replicateM(mkReg(False));

   rule handle_requests;
      let req <- slave.client.request.get;
      ReturnedDataT rtn = tagged Invalid;
      AvalonFIFOs_Addr_SeperatorT a = unpack(pack(req.addr));
      case(tuple2(a.addr_bot,req.rw))
	 tuple2(0, MemRead ) : if(f[a.addr_top].notEmpty)
				  begin
				     let d = f[a.addr_top].first;
				     f[a.addr_top].deq;
				     rtn = tagged Valid d.data;
				     tag_in[a.addr_top] <= d.tag;
				  end
			       else
				  begin
				     rtn = tagged Valid 0;
				     tag_in[a.addr_top] <= 0;
				     underflow[a.addr_top] <= True;
				  end
	 tuple2(0, MemWrite) : if(f[a.addr_top].notFull)
				  f[a.addr_top].enq(AvalonFIFOs_tagdataT{tag:tag_out[a.addr_top], data:req.data});
			       else
				  overflow[a.addr_top] <= True;
	 tuple2(1, MemRead ) : rtn = tagged Valid extend(unpack({pack(overflow[a.addr_top]),pack(underflow[a.addr_top]),pack(tag_in[a.addr_top])}));
	 tuple2(1, MemWrite) : tag_out[a.addr_top] <= truncate(req.data);
	 tuple2(2, MemRead ) : rtn = tagged Valid extend(f[a.addr_top].count);
	 tuple2(2, MemWrite) : begin
				  overflow[a.addr_top] <= False;
				  underflow[a.addr_top] <= False;
				  f[a.addr_top].clear;
			       end
	 tuple2(3, MemRead ) : rtn = tagged Valid extend(`fifo_depth-f[a.addr_top].count);
	 tuple2(3, MemWrite) : begin
				  overflow[a.addr_top] <= False;
				  underflow[a.addr_top] <= False;
				  f[a.addr_top].clear;
			       end
      endcase
      slave.client.response.put(rtn);
   endrule
   
   interface avs=slave.avs;
endmodule


/*****************************************************************************
 AvalonFIFOMultiPort
 ===================
 Simon Moore, Dec 2009
 
 Provides N Avalon Slaves and point-to-point FIFO communication between
 these ports (i.e. grows O(N^2)).
 
 It is constructed from N x AvalonFIFOOnePort
 
 Port P has N output ports and N input ports, i.e. there is one port
 which allows it to communicate with itself
 
 Use same address map as AvalonFIFOs.
 
  Processor memory map (word addressed):
   read/write  address   meaning
     read         0      read from fifo
     write        0      write word to fifo
     read         1      bit 9 = overflow-output, bit 8 = underflow-input
                         bits 7-0 = tag byte
     write        1      write tag byte
     read         2      number of waiting data items
     write        2      clear fifo + overflow and underflow bits
     read         3      number of empty fifo slots
     write        3      clear fifo + overflow and underflow bits 

 *****************************************************************************/


interface AvalonFIFOOnePortIfc;
   interface AvalonSlaveIfc#(`total_address_width) avs;
   interface Vector#(`num_ports,Client#(AvalonFIFOs_tagdataT,AvalonFIFOs_tagdataT)) chan;
endinterface

module mkAvalonFIFOOnePort(AvalonFIFOOnePortIfc);
   AvalonSlave2ClientIfc#(`total_address_width) slave <- mkAvalonSlave2Client;

   // TODO - use just one long fifo and token based flow control?
   //      - N.B. require "number of empty fifo slots" for sender
   Vector#(`num_ports,AvalonFIFOs_UGFIFOCountIfc#(AvalonFIFOs_tagdataT)) fin <- replicateM(mkAvalonFIFOs_UGFIFOCount(`fifo_depth));
   Vector#(`num_ports,AvalonFIFOs_UGFIFOCountIfc#(AvalonFIFOs_tagdataT)) fout <- replicateM(mkAvalonFIFOs_UGFIFOCount(`fifo_depth));
   Vector#(`num_ports,Reg#(AvalonFIFOs_tagT)) tag_in  <- replicateM(mkReg(0));
   Vector#(`num_ports,Reg#(AvalonFIFOs_tagT)) tag_out <- replicateM(mkReg(0));
   Vector#(`num_ports,Reg#(Bool)) underflow <- replicateM(mkReg(False));
   Vector#(`num_ports,Reg#(Bool)) overflow <- replicateM(mkReg(False));
   
   rule handle_requests;
      let req <- slave.client.request.get;
      ReturnedDataT rtn = tagged Invalid;
      AvalonFIFOs_Addr_SeperatorT a = unpack(pack(req.addr));
      case(tuple2(a.addr_bot,req.rw))
	 tuple2(0, MemRead ) : if(fin[a.addr_top].notEmpty)
				  begin
				     let d = fin[a.addr_top].first;
				     fin[a.addr_top].deq;
				     rtn = tagged Valid d.data;
				     tag_in[a.addr_top] <= d.tag;
				  end
			       else
				  begin
				     rtn = tagged Valid 0;
				     tag_in[a.addr_top] <= 0;
				     underflow[a.addr_top] <= True;
				  end
	 tuple2(0, MemWrite) : if(fout[a.addr_top].notFull)
				  fout[a.addr_top].enq(AvalonFIFOs_tagdataT{tag:tag_out[a.addr_top], data:req.data});
			       else
				  overflow[a.addr_top] <= True;
	 tuple2(1, MemRead ) : rtn = tagged Valid extend(unpack({pack(overflow[a.addr_top]),pack(underflow[a.addr_top]),pack(tag_in[a.addr_top])}));
	 tuple2(1, MemWrite) : tag_out[a.addr_top] <= truncate(req.data);
	 tuple2(2, MemRead ) : rtn = tagged Valid extend(fin[a.addr_top].count);
	 tuple2(2, MemWrite) : begin
				  overflow[a.addr_top] <= False;
				  underflow[a.addr_top] <= False;
				  fout[a.addr_top].clear;
			       end
	 tuple2(3, MemRead ) : rtn = tagged Valid extend(`fifo_depth-fout[a.addr_top].count);
	 tuple2(3, MemWrite) : begin
				  overflow[a.addr_top] <= False;
				  underflow[a.addr_top] <= False;
				  fin[a.addr_top].clear;
				  fout[a.addr_top].clear;
			       end
      endcase
      slave.client.response.put(rtn);
   endrule
   
   // this is a bit of a hack to provide get and put interfaces to fifoin and fifoout
   Vector#(`num_ports,FIFO#(AvalonFIFOs_tagdataT)) bout <- replicateM(mkFIFO);
   Vector#(`num_ports,FIFO#(AvalonFIFOs_tagdataT)) bin <- replicateM(mkFIFO);
   for(Integer p=0; p<`num_ports; p=p+1)
      begin
	 rule bin_to_fin (fin[p].notFull);
	    fin[p].enq(bin[p].first);
	    bin[p].deq;
	 endrule
	 rule fout_to_bout(fout[p].notEmpty);
	    bout[p].enq(fout[p].first);
	    fout[p].deq;
	 endrule
      end
   
   Vector#(`num_ports,Client#(AvalonFIFOs_tagdataT,AvalonFIFOs_tagdataT)) c = newVector;
   for(Integer p=0; p<`num_ports; p=p+1)
      c[p] = Client{request: toGet(bout[p]), response: toPut(bin[p])};
      
   interface chan=c;
   interface avs=slave.avs;
endmodule


interface AvalonFIFOMultiPortIfc;
   interface Vector#(`num_ports,AvalonSlaveIfc#(`total_address_width)) avs;
endinterface


(* synthesize,
   reset_prefix = "csi_clockreset_reset_n",
   clock_prefix = "csi_clockreset_clk" *)
(* doc="************** Simon's comments for Verilog code: **************" *)
(* doc="Top level module for Avalon Bridge Example" *)
(* doc="Port names should align with SOPC builder's new device requirements" *)
(* doc="Need to add the $BLUESPECDIR/Verilog directory to the Libraries in Quartus" *)
module mkAvalonFIFOMultiPort(AvalonFIFOMultiPortIfc);
   Vector#(`num_ports,AvalonFIFOOnePortIfc) ports <- replicateM(mkAvalonFIFOOnePort);
   Vector#(`num_ports,AvalonSlaveIfc#(`total_address_width)) slaves;
   
   for(Integer src=0; src<`num_ports; src=src+1)
      for(Integer dest=0; dest<`num_ports; dest=dest+1)
	 mkConnection(ports[src].chan[dest].request.get,ports[dest].chan[src].response.put);
   
   for(Integer j=0; j<`num_ports; j=j+1)
      slaves[j] = ports[j].avs;

   interface avs = slaves;
endmodule



endpackage
       