/**
* 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