blob: 815fef8963411e1cd14f07d4bb48baaa75b7e85a [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Component handling register CDC
`include "prim_assert.sv"
// There are three handling scenarios.
// 1. The register can only be updated by software.
// 2. The register can be updated by both software and hardware.
// 3. The register can only be updated by hardware.
//
// For the first scenario, hardware updates are completely ignored.
// The software facing register (`src_q` in prim_reg_cdc) simply reflects
// the value affected by software. Since there is no possibility the
// register value can change otherwise, there is no need to sample or
// do any other coordination between the two domains. In this case,
// we use the gen_passthru block below.
//
// For the second scenario, one of 4 things can happen:
// 1. A software update without conflict
// 2. A hardware update without conflict
// 3. A software update is initiated when a hardware update is in-flight
// 4. A hardware update is initiated when a software update is in-flight
//
// For the first case, it behaves similarly to the gen_passthru scenario.
//
// For the second case, the hardware update indication and update value are
// captured, and the intent to change is synchronized back to the software
// domain. While this happens, other hardware updates are ignored. Any hardware
// change during the update is then detected as a difference between the
// transit register `dst_qs_o` and the current hardware register value `dst_qs_i`.
// When this change is observed after the current handshake completes, another
// handshake event is generated to bring the latest hardware value over to the
// software domain.
//
// For the third case, if a hardware update event is already in progress, the
// software event is held and not acknowledged. Once the hardware event completes,
// then the software event proceeds through its normal updating process.
//
// For the forth case, if a hardware update event is received while a software
// update is in progress, the hardware update is ignored, and the logic behaves
// similarly to the second case. Specifically, after the software update completes,
// a delta is observed between the transit register and the current hardware value,
// and a new handshake event is generated.
//
// The third scenario can be folded into the second scenario. The only difference
// is that of the 4 cases identified, only case 2 can happen since there is never a
// software initiated update.
module prim_reg_cdc_arb #(
parameter int DataWidth = 32,
parameter logic [DataWidth-1:0] ResetVal = 32'h0,
parameter bit DstWrReq = 0
) (
input clk_src_i,
input rst_src_ni,
input clk_dst_i,
input rst_dst_ni,
// destination side acknowledging a software transaction
output logic src_ack_o,
// destination side requesting a source side update after
// after hw update
output logic src_update_o,
// input request from prim_reg_cdc
input dst_req_i,
// output request to prim_subreg
output logic dst_req_o,
input dst_update_i,
// ds allows us to sample the destination domain register
// one cycle earlier instead of waiting for it to be reflected
// in the qs.
// This is important because a general use case is that interrupts
// are captured alongside payloads from the destination domain into
// the source domain. If we rely on `qs` only, then it is very likely
// that the software observed value will be behind the interrupt
// assertion. If the destination clock is very slow, this can seem
// an error on the part of the hardware.
input [DataWidth-1:0] dst_ds_i,
input [DataWidth-1:0] dst_qs_i,
output logic [DataWidth-1:0] dst_qs_o
);
typedef enum logic {
SelSwReq,
SelHwReq
} req_sel_e;
typedef enum logic [1:0] {
StIdle,
StWait
} state_e;
// Only honor the incoming destinate update request if the incoming
// value is actually different from what is already completed in the
// handshake
logic dst_update;
assign dst_update = dst_update_i & (dst_qs_o != dst_ds_i);
if (DstWrReq) begin : gen_wr_req
logic dst_lat_q;
logic dst_lat_d;
logic dst_update_req;
logic dst_update_ack;
req_sel_e id_q;
state_e state_q, state_d;
// Make sure to indent the following later
always_ff @(posedge clk_dst_i or negedge rst_dst_ni) begin
if (!rst_dst_ni) begin
state_q <= StIdle;
end else begin
state_q <= state_d;
end
end
logic busy;
logic dst_req_q, dst_req;
always_ff @(posedge clk_dst_i or negedge rst_dst_ni) begin
if (!rst_dst_ni) begin
dst_req_q <= '0;
end else if (dst_req_q && dst_lat_d) begin
// if request is held, when the transaction starts,
// automatically clear.
// dst_lat_d is safe to used here because dst_req_q, if set,
// always has priority over other hardware based events.
dst_req_q <= '0;
end else if (dst_req_i && !dst_req_q && busy) begin
// if destination request arrives when a handshake event
// is already ongoing, hold on to request and send later
dst_req_q <= 1'b1;
end
end
assign dst_req = dst_req_q | dst_req_i;
// Hold data at the beginning of a transaction
always_ff @(posedge clk_dst_i or negedge rst_dst_ni) begin
if (!rst_dst_ni) begin
dst_qs_o <= ResetVal;
end else if (dst_lat_d) begin
dst_qs_o <= dst_ds_i;
end else if (dst_lat_q) begin
dst_qs_o <= dst_qs_i;
end
end
// Which type of transaction is being ack'd back?
// 0 - software initiated request
// 1 - hardware initiated request
// The id information is used by prim_reg_cdc to disambiguate
// simultaneous updates from software and hardware.
// See scenario 2 case 3 for an example of how this is handled.
always_ff @(posedge clk_dst_i or negedge rst_dst_ni) begin
if (!rst_dst_ni) begin
id_q <= SelSwReq;
end else if (dst_update_req && dst_update_ack) begin
id_q <= SelSwReq;
end else if (dst_req && dst_lat_d) begin
id_q <= SelSwReq;
end else if (!dst_req && dst_lat_d) begin
id_q <= SelHwReq;
end else if (dst_lat_q) begin
id_q <= SelHwReq;
end
end
// if a destination update is received when the system is idle and there is no
// software side request, hw update must be selected.
`ASSERT(DstUpdateReqCheck_A, ##1 dst_update & !dst_req & !busy |=> id_q == SelHwReq,
clk_dst_i, !rst_dst_ni)
// if hw select was chosen, then it must be the case there was a destination update
// indication or there was a difference between the transit register and the
// latest incoming value.
`ASSERT(HwIdSelCheck_A, $rose(id_q == SelHwReq) |-> $past(dst_update_i, 1) ||
$past(dst_lat_q, 1),
clk_dst_i, !rst_dst_ni)
// send out prim_subreg request only when proceeding
// with software request
assign dst_req_o = ~busy & dst_req;
logic dst_hold_req;
always_comb begin
state_d = state_q;
dst_hold_req = '0;
// depending on when the request is received, we
// may latch d or q.
dst_lat_q = '0;
dst_lat_d = '0;
busy = 1'b1;
unique case (state_q)
StIdle: begin
busy = '0;
if (dst_req) begin
// there's a software issued request for change
state_d = StWait;
dst_lat_d = 1'b1;
end else if (dst_update) begin
state_d = StWait;
dst_lat_d = 1'b1;
end else if (dst_qs_o != dst_qs_i) begin
// there's a direct destination update
// that was blocked by an ongoing transaction
state_d = StWait;
dst_lat_q = 1'b1;
end
end
StWait: begin
dst_hold_req = 1'b1;
if (dst_update_ack) begin
state_d = StIdle;
end
end
default: begin
state_d = StIdle;
end
endcase // unique case (state_q)
end // always_comb
assign dst_update_req = dst_hold_req | dst_lat_d | dst_lat_q;
logic src_req;
prim_sync_reqack u_dst_update_sync (
.clk_src_i(clk_dst_i),
.rst_src_ni(rst_dst_ni),
.clk_dst_i(clk_src_i),
.rst_dst_ni(rst_src_ni),
.req_chk_i(1'b1),
.src_req_i(dst_update_req),
.src_ack_o(dst_update_ack),
.dst_req_o(src_req),
// immediate ack
.dst_ack_i(src_req)
);
assign src_ack_o = src_req & (id_q == SelSwReq);
assign src_update_o = src_req & (id_q == SelHwReq);
// once hardware makes an update request, we must eventually see an update pulse
`ifdef FPV_ON
`ASSERT(ReqTimeout_A, $rose(id_q == SelHwReq) |-> s_eventually(src_update_o),
clk_src_i, !rst_src_ni)
// TODO: #14913 check if we can add additional sim assertions.
`endif
`ifdef FPV_ON
//VCS coverage off
// pragma coverage off
logic async_flag;
always_ff @(posedge clk_dst_i or negedge rst_dst_ni or posedge src_update_o) begin
if (!rst_dst_ni) begin
async_flag <= '0;
end else if (src_update_o) begin
async_flag <= '0;
end else if (dst_update && !dst_req_o && !busy) begin
async_flag <= 1'b1;
end
end
//VCS coverage on
// pragma coverage on
// once hardware makes an update request, we must eventually see an update pulse
// TODO: #14913 check if we can add additional sim assertions.
`ASSERT(UpdateTimeout_A, $rose(async_flag) |-> s_eventually(src_update_o),
clk_src_i, !rst_src_ni)
`endif
end else begin : gen_passthru
// when there is no possibility of conflicting HW transactions,
// we can assume that dst_qs_i will only ever take on the value
// that is directly related to the transaction. As a result,
// there is no need to latch further, and the end destination
// can in fact be used as the holding register.
assign dst_qs_o = dst_qs_i;
assign dst_req_o = dst_req_i;
// since there are no hw transactions, src_update_o is always '0
assign src_update_o = '0;
prim_pulse_sync u_dst_to_src_ack (
.clk_src_i(clk_dst_i),
.rst_src_ni(rst_dst_ni),
.clk_dst_i(clk_src_i),
.rst_dst_ni(rst_src_ni),
.src_pulse_i(dst_req_i),
.dst_pulse_o(src_ack_o)
);
logic unused_sigs;
assign unused_sigs = |{dst_ds_i, dst_update};
end
endmodule