calyx 0.7.1

Compiler Infrastructure for Hardware Accelerator Generation
/**
* Synchronization primitives for Calyx
* Requirements: 
* 1. write_en_* signals should remain 1 until the 
*    corresponding done_* signals are set to 1 once they are set to 1.
* 2. in_* should remain the same value once their corresponding write_en_* signals
*    are set high until their corresponding done_* signals are set to 1.
* 3. read_en_* signals should remain 1 until the read_done_* signal is set to 1. 
* 4. read_en_* signals should be set to 0 once the read_done_* signals are high.
*/

// M-structure: Register primitive that blocks writes until a read happens.
module std_sync_reg #(
    parameter WIDTH = 32
) (
   input wire [ WIDTH-1:0]    in_0,
   input wire [ WIDTH-1:0]    in_1,
   input wire                 read_en_0,
   input wire                 read_en_1,
   input wire                 write_en_0,
   input wire                 write_en_1,
   input wire                 clk,
   input wire                 reset,
    // output
   output logic [WIDTH - 1:0] out_0,
   output logic [WIDTH - 1:0] out_1,
   output logic               write_done_0,
   output logic               write_done_1,
   output logic               read_done_0,
   output logic               read_done_1,
   output logic [WIDTH - 1:0] peek
);

  logic is_full;
  logic [WIDTH - 1:0] state;
  logic arbiter_w;
  logic arbiter_r;

  // States
  logic READ_ONE_HOT, READ_MULT, WRITE_ONE_HOT, WRITE_MULT, WRITE_0, WRITE_1, READ_0, READ_1;

  assign READ_ONE_HOT = is_full && (read_en_0 ^ read_en_1);
  assign READ_MULT = is_full && (read_en_0 && read_en_1);
  assign WRITE_ONE_HOT = !is_full && (write_en_0 ^ write_en_1);
  assign WRITE_MULT = !is_full && (write_en_0 && write_en_1);
  assign WRITE_0 = (WRITE_ONE_HOT && write_en_0 == 1) || (WRITE_MULT && arbiter_w == 0);
  assign WRITE_1 = (WRITE_ONE_HOT && write_en_1 == 1) || (WRITE_MULT && arbiter_w == 1);
  assign READ_0 = (READ_ONE_HOT && read_en_0 == 1) || (READ_MULT && arbiter_r == 0);
  assign READ_1 = (READ_ONE_HOT && read_en_1 == 1) || (READ_MULT && arbiter_r == 1);

  // State transitions
  always_ff @(posedge clk) begin
    if (reset)
      is_full <= 0;
    else if (WRITE_ONE_HOT || WRITE_MULT)
      is_full <= 1;
    else if (READ_ONE_HOT || READ_MULT)
      is_full <= 0;
    else
      is_full <= is_full;
  end

  // Value of writer arbiter.
  // The arbiter is round robin: if in the current cycle it is 0, the next cycle
  // it is set to 1 and vice versa.
  // If the arbiter is not used to decide which value to write in for the current
  // cycle, then in the next cycle its value does not change.
  always_ff @(posedge clk) begin
    if (reset) 
      arbiter_w <= 0;
    else if (WRITE_MULT && arbiter_w == 0)
      arbiter_w <= 1;
    else if (WRITE_MULT && arbiter_w == 1)
      arbiter_w <= 0;
    else 
      arbiter_w <= arbiter_w;
  end

  // Value of reader arbiter.
  // The arbiter is round robin: if in the current cycle it is 0, the next cycle
  // it is set to 1 and vice versa.
  // If the arbiter is not used to decide which reader to give its value to for the current
  // cycle, then in the next cycle its value does not change.
  always_ff @(posedge clk) begin
    if (reset) 
      arbiter_r <= 0;
    else if (READ_MULT && arbiter_r == 0)
      arbiter_r <= 1;
    else if (READ_MULT && arbiter_r == 1)
      arbiter_r <= 0;
    else 
      arbiter_r <= arbiter_r;
  end
      

  // Value of out_0 port.
  // Note that output is only available for one cycle.
  // out_0 has value only when 
  // 1. only read_en_0 is high
  // 2. read_en_0 and read_en_1 are both high and arbiter_r == 0.
  always_ff @(posedge clk) begin
    if (reset)
      out_0 <= 0;
    else if (READ_0)
      out_0 <= state;
    else
      out_0 <= 'x; // This could've been a latch but we explicitly define the output as undefined.
  end

  // Value of out_1 port.
  // Note that output is only available for one cycle.
  // out_1 has value only when 
  // 1. only read_en_1 is high
  // 2. read_en_0 and read_en_1 are both high and arbiter_r == 1.
  always_ff @(posedge clk) begin
    if (reset)
      out_1 <= 0;
    else if (READ_1)
      out_1 <= state;
    else
      out_1 <= 'x; // This could've been a latch but we explicitly define the output as undefined.
  end

  // Writing values
  // If only one writer is active, we write the active value in
  // If multiple writers are active at the same time, the arbiter decides which 
  // writer's input to take
  always_ff @(posedge clk) begin
    if (reset)
      state <= 0;
    else if (WRITE_0)
      state <= in_0;
    else if (WRITE_1)
      state <= in_1;
    else if (READ_ONE_HOT || READ_MULT)
      state <= 'x;  // This could've been a latch but explicitly make it undefined.
    else
      state <= state;
  end

  //Value of the "peek" port.
  // If the register is full, peek holds the same value as the state of the register.
  // If the register is empty, peek holds the most recent valid state of the register.
  always_ff @(posedge clk) begin
    if (reset)
      peek <= 0;
    else if (WRITE_0)
      peek <= in_0;
    else if (WRITE_1)
      peek <= in_1;
    else
      peek <= peek;
  end

  // Done signal for write_0 commital
  // Two scenarios that write_done_0 is set to 1:
  // 1. in_0 is the only writer for the current cycle
  // 2. Two writers are active at the same time, and the arbiter chooses
  //    in_0 to write into the register
  always_ff @(posedge clk) begin
    if (reset)
      write_done_0 <= 0;
    else if (WRITE_0)
      write_done_0 <= 1;
    else
      write_done_0 <= 0;
  end

  //Done signal for write_1 commital
  // Two scenarios that write_done_1 is set to 1:
  // 1. in_1 is the only writer for the current cycle
  // 2. Two writers are active at the same time, and the arbiter chooses
  //    in_1 to write into the register
  always_ff @(posedge clk) begin
    if (reset)
      write_done_1 <= 0;
    else if (WRITE_1)
      write_done_1 <= 1;
    else
      write_done_1 <= 0;
    end

  // Done signal for read_0 commital
  always_ff @(posedge clk) begin
    if (reset)
      read_done_0 <= 0;
    else if (READ_0)
      read_done_0 <= 1;
    else
      read_done_0 <= 0;
  end

  // Done signal for read_1 commital
  always_ff @(posedge clk) begin
    if (reset)
      read_done_1 <= 0;
    else if (READ_1)
      read_done_1 <= 1;
    else
      read_done_1 <= 0;
  end

  //Simulation self test against overlapping of mutually exclusive states
  `ifdef VERILATOR
    always @(posedge clk) begin
      if (READ_0 && READ_1) 
        $error(
          "\nstd_sync_reg: overlapping of mutually exclusive states!\n",
          "can be at only one of READ_0 and READ_1", 
        );
      else if (WRITE_0 && WRITE_1)
        $error(
          "\nstd_sync_reg: overlapping of mutually exclusive states!\n",
          "can be at only one of WRITE_0 and WRITE_1", 
        );
    end
  `endif

endmodule