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