Add MultiFifo

MultiFifo is a Fifo implementation that can enqueue/dequeue multiple
elements in a cycle.

Change-Id: I36087f2a3bb5f4e7517eb59ce3a429f6201e9fd7
diff --git a/hdl/verilog/rvv/Makefile b/hdl/verilog/rvv/Makefile
index 0814e2a..e95da0e 100644
--- a/hdl/verilog/rvv/Makefile
+++ b/hdl/verilog/rvv/Makefile
@@ -4,7 +4,13 @@
 Aligner_tb: Aligner_tb.sv Aligner.sv
 	@vcs -full64 -sverilog Aligner.sv Aligner_tb.sv -o Aligner_tb
 
+MultiFifo: MultiFifo.sv
+	@vcs -full64 -sverilog MultiFifo.sv -o MultiFifo
+
+MultiFifo_tb: MultiFifo_tb.sv MultiFifo.sv
+	@vcs -full64 -sverilog MultiFifo.sv MultiFifo_tb.sv -o MultiFifo_tb
+
 .PHONY : clean
 
 clean:
-	@rm -f Aligner Aligner_tb
+	@rm -f Aligner Aligner_tb MultiFifo MultiFifo_tb
diff --git a/hdl/verilog/rvv/MultiFifo.sv b/hdl/verilog/rvv/MultiFifo.sv
new file mode 100644
index 0000000..27821cd
--- /dev/null
+++ b/hdl/verilog/rvv/MultiFifo.sv
@@ -0,0 +1,99 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// A FIFO that can enqueue and dequeue multiple elements at a time.
+module MultiFifo#(type T=logic [7:0],
+                  parameter N = 4,
+                  parameter MAX_CAPACITY=16,
+                  parameter INTERFACE_BITS=$clog2(N+1),
+                  parameter CAPACITYBITS=$clog2(MAX_CAPACITY+1))
+(
+  input clk,
+  input rstn,
+
+  // Command input.
+  input logic [INTERFACE_BITS-1:0] valid_in,
+  input T [N-1:0] data_in,
+
+  // fill_level is used to determine if elements can be enqueued and dequeued.
+  // valid_in must be <= MAX_CAPACITY - fill_level
+  // ready_out must be <= fill_level
+  output logic [CAPACITYBITS-1:0] fill_level,
+
+  // Command output.
+  output T [N-1:0] data_out,
+  input logic [INTERFACE_BITS-1:0] ready_out
+);
+  typedef logic [CAPACITYBITS-1:0] buffer_ptr_t;
+  typedef logic [CAPACITYBITS-1:0] buffer_size_t;
+
+  // Module state
+  buffer_ptr_t head;  // Elements to enqueue
+  buffer_ptr_t tail;  // Elements to dequeue
+  buffer_size_t m_fill_level;
+  T [MAX_CAPACITY-1:0] buffer;
+
+  function automatic buffer_ptr_t WrapAroundSum(buffer_ptr_t ptr,
+                                                buffer_size_t sz);
+      logic [CAPACITYBITS:0] sum;
+      sum = ptr + sz;
+      return (sum >= MAX_CAPACITY) ? (sum - MAX_CAPACITY) : sum;
+  endfunction
+
+  // Output fill_level
+  always_comb begin
+    fill_level = m_fill_level;
+  end
+
+  always_ff @(posedge clk or negedge rstn) begin
+    if (!rstn) begin
+      head <= 0;
+      tail <= 0;
+      m_fill_level <= 0;
+    end else begin
+      head <= WrapAroundSum(head, valid_in);
+      tail <= WrapAroundSum(tail, ready_out);
+
+      // Update buffer
+      for (int i = 0; i < N; i++) begin
+        if (i < valid_in) begin
+          buffer[WrapAroundSum(head, i)] <= data_in[i];
+        end
+      end
+      m_fill_level <= m_fill_level + valid_in - ready_out;
+    end
+  end
+
+  always_comb begin
+    for (int i = 0; i < N; i++) begin
+      data_out[i] = buffer[WrapAroundSum(tail, i)];
+    end
+  end
+
+  // Assertions
+`ifndef SYNTHESIS
+  always @(posedge clk) begin
+    // Producer should enqueue less than what's empty
+    assert (valid_in <= (MAX_CAPACITY - m_fill_level)) else
+        $error("Trying to enqueue ", valid_in, " elements ",
+               (MAX_CAPACITY - m_fill_level), " free");
+
+    // Consumer should dequeue less than what's full
+    assert (ready_out <= m_fill_level) else
+        $error("Trying to dequeue ", ready_out, " elements ", m_fill_level,
+               " free");
+  end
+`endif  // not def SYNTHESIS
+
+endmodule
\ No newline at end of file
diff --git a/hdl/verilog/rvv/MultiFifo_tb.sv b/hdl/verilog/rvv/MultiFifo_tb.sv
new file mode 100644
index 0000000..bcb7ee7
--- /dev/null
+++ b/hdl/verilog/rvv/MultiFifo_tb.sv
@@ -0,0 +1,258 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+class MultiFifoTransaction;
+  rand logic [3:0][31:0] data;
+  rand logic [2:0] valid;  // Number of elements that producer is enqueuing
+  rand logic [2:0] ready;  // Number of elements that consumer is dequeuing.
+
+  constraint valid_limit { valid < 4; };
+  constraint ready_limit { ready < 4; };
+endclass
+
+class MultiFifoTransactionGenerator;
+  rand MultiFifoTransaction transaction;
+  mailbox gen2driv;
+  int n;
+  event finished;
+
+  function new(mailbox gen2driv, int n, event finished);
+    this.gen2driv = gen2driv;
+    this.n = n;
+    this.finished = finished;
+  endfunction
+
+  task generate_transactions();
+    repeat(n) begin
+      transaction = new();
+      if( !transaction.randomize() ) $fatal("Gen:: trans randomization failed");
+      gen2driv.put(transaction);
+    end
+    -> finished;
+  endtask
+endclass
+
+interface MultiFifoInterface(input logic clk, rstn);
+  logic [2:0] valid_in;
+  logic [3:0][31:0] data_in;
+  logic [4:0] fill_level;
+  logic [3:0][31:0] data_out;
+  logic [2:0] ready_out;
+
+  clocking driver_cb @(posedge clk);
+    default input negedge output negedge;
+    input fill_level, data_out;
+    output valid_in, data_in, ready_out;
+  endclocking
+
+  modport DRIVER (clocking driver_cb, input clk, rstn);
+
+endinterface
+
+class MultiFifoDriver;
+  virtual MultiFifoInterface multififo_iface;
+  mailbox gen2driv;
+  mailbox enqueued;
+  mailbox dequeued;
+
+  MultiFifoTransaction transaction;
+  int n_transactions_processed;
+
+  logic [2:0] enqueued_fired;  // Number of elements to enqueue
+  logic [2:0] dequeued_fired;  // Number of elements to enqueue
+  logic [4:0] fill_level;
+  logic [3:0][31:0] data_out;
+
+  function new(virtual MultiFifoInterface multififo_iface,
+               mailbox gen2driv,
+               mailbox enqueued,
+               mailbox dequeued);
+    this.multififo_iface = multififo_iface;
+    this.gen2driv = gen2driv;
+    this.enqueued = enqueued;
+    this.dequeued = dequeued;
+  endfunction
+
+  task reset;
+    wait(!multififo_iface.rstn);
+    $display("Reset started.");
+    multififo_iface.DRIVER.driver_cb.valid_in <= 0;
+    multififo_iface.DRIVER.driver_cb.data_in <= 0;
+    multififo_iface.DRIVER.driver_cb.ready_out <= 0;
+    wait(multififo_iface.rstn);
+    $display("Finished resetting.");
+  endtask
+
+  task record_enqueue_dequeue;
+  endtask
+
+  task drive;
+    forever begin
+      $display("Running iteration ", n_transactions_processed);
+      gen2driv.get(transaction);
+
+      @(negedge multififo_iface.DRIVER.clk);
+
+      fill_level = multififo_iface.DRIVER.driver_cb.fill_level;
+      data_out = multififo_iface.DRIVER.driver_cb.data_out;
+
+      enqueued_fired = (transaction.valid < (16 - fill_level)) ?
+          transaction.valid : 16 - fill_level;
+      dequeued_fired = (transaction.ready > fill_level) ?
+          fill_level : transaction.ready;
+
+      multififo_iface.DRIVER.driver_cb.valid_in <= enqueued_fired;
+      multififo_iface.DRIVER.driver_cb.data_in <= transaction.data;
+      multififo_iface.DRIVER.driver_cb.ready_out <= dequeued_fired;
+
+      // Put enqueued and dequeued elements into checker mailboxes
+      for (int i = 0; i < 4; i++) begin
+        if (i < transaction.valid && i < enqueued_fired) begin
+          // $display("Enqueuing ", transaction.data[i]);
+          enqueued.put(transaction.data[i]);
+        end
+      end
+
+      for (int o = 0; o < 4; o++) begin
+        if (o < dequeued_fired && o < transaction.ready) begin
+          // $display("Dequeuing ", data_out[o]);
+          dequeued.put(data_out[o]);
+        end
+      end
+
+      n_transactions_processed++;
+
+      @(posedge multififo_iface.DRIVER.clk);
+    end
+  endtask
+
+endclass
+
+class MultiFifoComparator;
+  mailbox enqueued;
+  mailbox dequeued;
+  int n_transactions_processed = 0;
+
+  logic [31:0] enqueued_elem;
+  logic [31:0] dequeued_elem;
+
+  function new(mailbox enqueued,
+               mailbox dequeued);
+    this.enqueued = enqueued;
+    this.dequeued = dequeued;
+  endfunction
+
+  task check;
+    forever begin
+      enqueued.get(enqueued_elem);
+      dequeued.get(dequeued_elem);
+      if (enqueued_elem != dequeued_elem) begin
+        $display("Error: expected ", enqueued_elem, " got ", dequeued_elem);
+      end
+      n_transactions_processed++;
+    end
+  endtask
+endclass
+
+class MultiFifoEnvironment;
+  localparam ITERATIONS = 512;
+
+  MultiFifoTransactionGenerator gen;
+  MultiFifoDriver driv;
+  MultiFifoComparator comparator;
+  mailbox gen2driv;
+  mailbox enqueued;
+  mailbox dequeued;
+  event gen_ended;
+
+  virtual MultiFifoInterface multififo_iface;
+
+  function new(virtual MultiFifoInterface multififo_iface);
+    this.multififo_iface = multififo_iface;
+
+    gen2driv = new();
+    enqueued = new();
+    dequeued = new();
+    gen = new(gen2driv, ITERATIONS, gen_ended);
+    driv = new(multififo_iface, gen2driv, enqueued, dequeued);
+    comparator = new(enqueued, dequeued);
+  endfunction
+
+  task setup();
+    driv.reset();
+  endtask
+
+  task test();
+    fork
+      gen.generate_transactions();
+      driv.drive();
+      comparator.check();
+    join_any
+  endtask
+
+  task teardown();
+    wait(gen_ended.triggered);
+    wait(driv.n_transactions_processed == ITERATIONS);
+  endtask
+
+  task run;
+    setup();
+    test();
+    teardown();
+    $finish;
+  endtask
+endclass;
+
+program MultiFifoTest(MultiFifoInterface multififo_iface);
+  MultiFifoEnvironment env;
+  initial begin
+    env = new(multififo_iface);
+    env.run();
+  end
+endprogram
+
+module MultiFifo_tb();
+  logic clk;
+  logic rstn;
+
+  MultiFifoInterface multififo_iface(clk, rstn);
+
+  typedef logic[31:0] MyInt;
+  MultiFifo#(.T (MyInt), .N (4), .MAX_CAPACITY (16))
+  dut(
+    .clk(multififo_iface.clk),
+    .rstn(multififo_iface.rstn),
+    .valid_in(multififo_iface.valid_in),
+    .data_in(multififo_iface.data_in),
+    .fill_level(multififo_iface.fill_level),
+    .data_out(multififo_iface.data_out),
+    .ready_out(multififo_iface.ready_out)
+  );
+
+  // Clock and reset
+  initial begin
+    rstn = 0;
+    clk = 0;
+    #5
+    clk = 1;
+    #5
+    clk = 0;
+    rstn = 1;
+
+    forever
+      #5 clk = ~clk;
+  end
+
+  MultiFifoTest test(multififo_iface);
+endmodule