blob: f8ddd92b9ee673b56091a7866f344066082f9525 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// This module should be instantiated within flops of CDC synchronization primitives,
// and allows DV to model real CDC delays within simulations, especially useful at the chip level
// or in IPs that communicate across clock domains.
//
// If not, delay randomization is enabled - the faster of the two clocks is used to latch src_data,
// dst_data is synchronously driven with a random combination of the current src_data and
// the delayed version of src_data.
//
// If a source clock isn't used, the input src_data is latched after a parameterizable latency
// as `src_data_with_latency`, and an internal version of the output data `src_data_delayed` is set
// to this same value after a parameterizable jitter period.
//
// This is meant to model skew between synchronizer bits and wire delay between the src and dst
// flops.
//
// Five (5) different random delay modes are available:
//
// - PrimCdcRandDelayDisable: If this delay mode is picked, this module acts as a simple
// passthrough.
//
// - PrimCdcRandDelaySlow: If this delay mode is picked, the output to the dst domain is
// continuously driven to `src_data_delayed`.
//
// - PrimCdcRandDelayOnce: If this delay mode is picked, the mask `out_data_mask` used to combine
// `src_data_with_latency` and `src_data_delayed` is fully randomized
// once at the beginning of the simulation.
//
// - PrimCdcRandDelayInterval: If this delay mode is picked, the mask `out_data_mask` used to
// combine `src_data_with_latency` and `src_data_delayed` is fully
// randomized every `prim_cdc_num_src_data_changes` times that
// `src_data_with_latency` changes.
//
// - PrimCdcRandDelayFull: If this delay mode is picked, the mask `out_data_mask` used to combine
// `src_data_with_latency` and `src_data_delayed` is fully randomized
// any time `src_data_with_latency` changes.
//
// DV has control of the weights corresponding to each random delay mode when the delay mode is
// randomized, but can also directly override the delay mode as desired.
//
// DV also has control over whether the source clock is used, the latency, and the jitter values -
// these can be modified through the provided setter tasks.
module prim_cdc_rand_delay #(
parameter int DataWidth = 1,
parameter bit UseSourceClock = 1,
parameter int LatencyPs = 500,
parameter int JitterPs = 500
) (
input logic src_clk,
input logic [DataWidth-1:0] src_data,
input logic dst_clk,
output logic [DataWidth-1:0] dst_data
);
`ASSERT_INIT(LegalWidth_A, DataWidth > 0)
int prim_cdc_latency_ps = LatencyPs;
int prim_cdc_jitter_ps = JitterPs;
// Only applies with using CdcRandDelayInterval randomization mode.
//
// This is the number of times that `src_data_with_latency` is allowed to change before
// we re-randomize `out_data_mask`.
int prim_cdc_num_src_data_changes = 10;
int counter = 0;
task automatic set_prim_cdc_latency_ps(int val);
prim_cdc_latency_ps = val;
`ASSERT_I(LegalLatency_A, prim_cdc_latency_ps > 0)
endtask
task automatic set_prim_cdc_jitter_ps(int val);
prim_cdc_jitter_ps = val;
`ASSERT_I(LegalJitter_A, prim_cdc_jitter_ps > 0)
endtask
task automatic set_prim_cdc_num_src_data_changes(int val);
prim_cdc_num_src_data_changes = val;
endtask
typedef enum bit [2:0] {
PrimCdcRandDelayDisable,
PrimCdcRandDelaySlow,
PrimCdcRandDelayOnce,
PrimCdcRandDelayInterval,
PrimCdcRandDelayFull
} prim_cdc_rand_delay_mode_e;
prim_cdc_rand_delay_mode_e prim_cdc_rand_mode;
logic [DataWidth-1:0] out_data_mask;
bit en_passthru = 1'b0;
bit out_randomize_en = 1'b0;
bit en_rand_interval_mask = 1'b0;
logic [DataWidth-1:0] src_data_with_latency;
logic [DataWidth-1:0] src_data_delayed;
int unsigned prim_cdc_rand_disable_weight = 0;
int unsigned prim_cdc_rand_slow_weight = 20;
int unsigned prim_cdc_rand_once_weight = 50;
int unsigned prim_cdc_rand_interval_weight = 20;
int unsigned prim_cdc_rand_full_weight = 10;
initial begin
// DV can override these from command line as desired.
void'($value$plusargs("prim_cdc_latency_ps=%0d", prim_cdc_latency_ps));
void'($value$plusargs("prim_cdc_jitter_ps=%0d", prim_cdc_jitter_ps));
void'($value$plusargs("prim_cdc_num_src_data_changes=%0d", prim_cdc_num_src_data_changes));
void'($value$plusargs("prim_cdc_rand_disable_weight=%0d", prim_cdc_rand_disable_weight));
void'($value$plusargs("prim_cdc_rand_slow_weight=%0d", prim_cdc_rand_slow_weight));
void'($value$plusargs("prim_cdc_rand_once_weight=%0d", prim_cdc_rand_once_weight));
void'($value$plusargs("prim_cdc_rand_interval_weight=%0d", prim_cdc_rand_interval_weight));
void'($value$plusargs("prim_cdc_rand_full_weight=%0d", prim_cdc_rand_full_weight));
if (!$value$plusargs("prim_cdc_rand_mode=%0d", prim_cdc_rand_mode)) begin
// By default pick the most performant(*) random delay mode for normal test
// development/simulation.
//
// (*): Need to do some performance experiments to check that the chosen mode is actually the
// most performant.
prim_cdc_rand_mode = PrimCdcRandDelaySlow;
end
unique case (prim_cdc_rand_mode)
PrimCdcRandDelayDisable: begin
// If CDC randomization disabled, behave like a passthrough
en_passthru = 1'b1;
end
PrimCdcRandDelaySlow: begin
out_data_mask = '1;
end
PrimCdcRandDelayOnce: begin
void'(std::randomize(out_data_mask));
end
PrimCdcRandDelayInterval: begin
out_randomize_en = 1'b1;
en_rand_interval_mask = 1'b1;
end
PrimCdcRandDelayFull: begin
out_randomize_en = 1'b1;
end
default: begin
$fatal("%0d is an invalid randomization mode", prim_cdc_rand_mode);
end
endcase
end
// TODO: Run some performance experiments using this implementation versus an implementation that
// primarily uses `forever` blocks rather than RTL constructs.
// Need to also check if this alternate implementation is still valid when
// compiling/simulating the design.
if (UseSourceClock) begin : gen_use_source_clock
// If relying on src_clk, insert a delay on the fastest clock
always_ff @(posedge src_clk or posedge dst_clk) begin
src_data_delayed <= src_data;
end
assign src_data_with_latency = src_data;
end else begin : gen_no_use_source_clock
// If not relying on src_clk, delay by a fixed number of ps determined by the module parameters
always_comb begin
src_data_with_latency <= #(prim_cdc_latency_ps * 1ps) src_data;
end
always_comb begin
src_data_delayed <= #(prim_cdc_jitter_ps * 1ps) src_data_with_latency;
end
end : gen_no_use_source_clock
// Randomize delayed random data selection only when input data changes
always @(src_data_with_latency) begin
if ((out_randomize_en && !en_rand_interval_mask) ||
(en_rand_interval_mask && counter == prim_cdc_num_src_data_changes) begin
for (int i = 0; i < DataWidth; i += 32) begin
// As of VCS 2017.12-SP2-6, it is slower to randomize for a DataWidth <= 32 with
// std::randomize() than using $urandom(), which may be more noticeable here as this module
// can potentially have a large number of instances.
//
// Whenever time permits, it will be interesting to run some perf tests with the current VCS
// version and see what updated performance looks like.
out_data_mask = (out_data_mask << 32) | $urandom();
end
end
if (en_rand_interval_mask) begin
counter <= (counter == prim_cdc_num_src_data_changes) ? 0 : counter + 1;
end
end
assign dst_data = (en_passthru) ?
(src_data) :
((src_data_delayed & out_data_mask) | (src_data_with_latency & ~out_data_mask));
//TODO: coverage
endmodule