docs: def fifo(rst, clk, full, we, din, empty, re, dout, afull=None, aempty=None, afull_th=None, aempty_th=None, ovf=None, udf=None, count=None, count_max=None, depth=None, width=None):
      """ Synchronous FIFO
  
          Input  interface: full,  we, din
          Output interface: empty, re, dout
              It s possible to set din and dout to None. Then the fifo width will be 0 and the fifo will contain no storage.
  
          Extra interface:
          afull     (o) - almost full flag, asserted when the number of empty cells <= afull_th
          aempty    (o) - almost empty flag, asserted when the number of full cells <= aempty_th
  
          afull_th  (i) - almost full threshold, in terms of fifo cells; signal or constant; Optional, default depth/2
          aempty_th (i) - almost empty threshold, in terms of fifo cells; signal or constant; Optional, default depth/2
  
          count     (o) - number of occupied fifo cells
  
          count_max (o) - max number of occupied fifo cells reached since the last reset
          ovf       (o) - overflow flag, set at the first write in a full fifo, cleared at reset
          udf       (o) - underflow flag, set at the first read from an empty fifo, cleared at reset
  
          Parameters:
          depth         - fifo depth, must be >= 1; if not set or set to `None` default value 2 is used
          width         - data width in bits, must be >= 0; if not set or set to `None` the `din` width is used
  
      """
  
      if (width == None):
          width = 0
          if din is not None:
              width = len(din)
      if (depth == None):
          depth = 2
  
      full_flg        = Signal(bool(1))
      empty_flg       = Signal(bool(1))
      we_safe         = Signal(bool(0))
      re_safe         = Signal(bool(0))
  
      rd_ptr          = Signal(intbv(0, min=0, max=depth))
      rd_ptr_new      = Signal(intbv(0, min=0, max=depth))
      wr_ptr          = Signal(intbv(0, min=0, max=depth))
      wr_ptr_new      = Signal(intbv(0, min=0, max=depth))
  
      @always_comb
      def safe_read_write():
          full.next       = full_flg
          empty.next      = empty_flg
          we_safe.next    = we and not full_flg
          re_safe.next    = re and not empty_flg
  
  
      #===========================================================================
      # Write, Read, Full, Empty
      #===========================================================================
      @always_comb
      def ptrs_new():
          rd_ptr_new.next     = ((rd_ptr + 1) % depth)
          wr_ptr_new.next     = ((wr_ptr + 1) % depth)
  
      @always(clk.posedge)
      def state_main():
          if (rst):
              wr_ptr.next     = 0
              rd_ptr.next     = 0
              full_flg.next   = 0
              empty_flg.next  = 1
          else:
              # Write pointer
              if (we_safe): wr_ptr.next = wr_ptr_new
              # Read pointer
              if (re_safe): rd_ptr.next = rd_ptr_new
              # Empty flag
              if (we_safe):
                  empty_flg.next  = 0
              elif (re_safe and (rd_ptr_new == wr_ptr)):
                  empty_flg.next  = 1
              # Full flag
              if (re_safe):
                  full_flg.next   = 0
              elif (we_safe and (wr_ptr_new == rd_ptr)):
                  full_flg.next   = 1
  
  
      #===========================================================================
      # Count, CountMax
      #===========================================================================
      ''' Count '''
      if (count != None) or (count_max != None) or (afull != None) or (aempty != None):
          count_r = Signal(intbv(0, min=0, max=depth+1))
          count_new  = Signal(intbv(0, min=-1, max=depth+2))
  
          if (count != None):
              assert count.max > depth
              @always_comb
              def count_out():
                  count.next = count_r
  
          @always_comb
          def count_comb():
              if   (we_safe and not re_safe):
                      count_new.next = count_r + 1
              elif (not we_safe and re_safe):
                      count_new.next = count_r - 1
              else:
                      count_new.next = count_r
  
          @always(clk.posedge)
          def count_proc():
              if (rst):
                  count_r.next = 0
              else:
                  count_r.next = count_new
  
      ''' Count max '''
      if (count_max != None):
          assert count_max.max > depth
          count_max_r = Signal(intbv(0, min=0,max=count_max.max))
          @always(clk.posedge)
          def count_max_proc():
              if (rst):
                  count_max_r.next = 0
              else:
                  if (count_max_r < count_new):
                      count_max_r.next = count_new
  
          @always_comb
          def count_max_out():
              count_max.next = count_max_r
  
      #===========================================================================
      # AlmostFull, AlmostEmpty
      #===========================================================================
      ''' AlmostFull flag '''
      if (afull != None):
          if (afull_th == None):
              afull_th = depth//2
          @always(clk.posedge)
          def afull_proc():
              if (rst):
                  afull.next = 0
              else:
                  afull.next = (count_new >= depth-afull_th)
  
      ''' AlmostEmpty flag '''
      if (aempty != None):
          if (aempty_th == None):
              aempty_th = depth//2
          @always(clk.posedge)
          def aempty_proc():
              if (rst):
                  aempty.next = 1
              else:
                  aempty.next = (count_new <=  aempty_th)
  
  
      #===========================================================================
      # Overflow, Underflow
      #===========================================================================
      ''' Overflow flag '''
      if (ovf != None):
          @always(clk.posedge)
          def ovf_proc():
              if (rst):
                  ovf.next = 0
              else:
                  if (we and full_flg ):
                      ovf.next = 1
  
      ''' Underflow flag '''
      if (udf != None):
          @always(clk.posedge)
          def udf_proc():
              if (rst):
                  udf.next = 0
              else:
                  if (re and empty_flg):
                      udf.next = 1
  
      if width>0:
          #===========================================================================
          # Memory instance
          #===========================================================================
          mem_we      = Signal(bool(0))
          mem_addrw   = Signal(intbv(0, min=0, max=depth))
          mem_addrr   = Signal(intbv(0, min=0, max=depth))
          mem_di      = Signal(intbv(0)[width:0])
          mem_do      = Signal(intbv(0)[width:0])
  
          # RAM: Simple-Dual-Port, Asynchronous read
          mem = ram_sdp_ar(   clk     = clk,
                              we      = mem_we,
                              addrw   = mem_addrw,
                              addrr   = mem_addrr,
                              di      = mem_di,
                              do      = mem_do )
  
          @always_comb
          def mem_connect():
              mem_we.next         = we_safe
              mem_addrw.next      = wr_ptr
              mem_addrr.next      = rd_ptr
              mem_di.next         = din
              dout.next           = mem_do
  
      return instances()