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