How to check if a signal is in a clock domain

Ok, this might be a silly question but we often assume that a signal is clocked in a specific clock domain and therefore do not pay too much attention to make sure that it’s the case. Especially when you deal with external IPs or VIPs and you want to make sure no extra synchronization is needed.

I unfortunately haven’t found anything similar, except maybe from an answer from Ben Cohen to a similar thread. The issue I have with that approach is that my simulation is purely RTL so I do not have the chance to observe a setup/hold time and therefore the recommended $setuphold timing check is not a valid option (since both setup and hold times will be 1 entire clock cycle).

So I came up with the following:


  // Macro: `check_clk_domain
  //         to make sure a signal is in a specific clock domain
  `define check_clk_domain(clk, signal, msg)                                         \
    always @(signal) begin                                                           \
      time signal_time;                                                              \
      time delta_time;                                                               \
      time clk_per;                                                                  \
      signal_time = $realtime;                                                       \
      @(posedge clk);                                                                \
      delta_time = $realtime - signal_time;                                          \
      @(posedge clk);                                                                \
      clk_per = $realtime - signal_time - delta_time;                                \
      assert(delta_time % clk_per == 0) else                                         \
        `sva_error("CLKDOM", $sformatf("%s: delta found = %t", msg, delta_time));    \
    end

So what I’m trying to do here is to capture the signal time of arrival and measure it compared to the clock one. delta_time should normally be 0 since all blocking assignments should execute in 0 delay but I’ve noticed that when signal is a bus and multiple bits are changing at the same time, it looks like the order of execution changes, screwing everything up. That’s the reason why I added an extra @(posedge clk to measure the period and make sure we have our signal changing on a multiple of the clk.

Since I consider this code rather ugly, I’d tend to say that it will not work consistently (and indeed it doesn’t with transitions from X to value before the reset is deasserted).

Any idea on how to improve on this or provide more indication on how to achieve my goal?

Thanks a lot,

Al

In reply to abasili:

This assumes all the external IPs have NO delays, even a #1. Also this code needs to be in a timescale so that $realtime always returns an integer; otherwise the modulo operator will not work.

You can use
always @(signal iff (!$isunknown(signal) )
to get rid of X transitions.

A much better approach is using a formal CDC checking tool. With little or no stimulus at all, a CDC can trace all the domain crossings exhaustively.

In reply to dave_59:

This assumes all the external IPs have NO delays, even a #1.

That’s correct, we assume no delays between the signal and the clock. Typically this assumption holds well except with behavioral models where we might want to emulate some sort of real behavior.

Also this code needs to be in a timescale so that $realtime always returns an integer; otherwise the modulo operator will not work.

I guess I could change $realtime to $time without necessarily losing much. Our timescale is 1ps/1ps due to constraints in some of the models we are using, so I guess that in that case $realtime is guaranteed to be an integer since it’s scaled to the timescale and there’s no possibility to have an event in the fs range (please correct if I’m wrong).

You can use always @(signal iff (!$isunknown(signal) ) to get rid of X transitions.

that’s the construct I was looking for, indeed I got bitten by the X transitions already! Thanks.

A much better approach is using a formal CDC checking tool. With little or no stimulus at all, a CDC can trace all the domain crossings exhaustively.

I fully agree that a CDC flow would immediately highlight these issues, yet I consider it complementary rather a replacement and the reasons are two:

  1. CDC flow typically is put in place at top level and come much later in the picture. Running CDC cycles when the system is far from being mature is way too time consuming and when designers are head down in the implementation are not necessarily thinking/working on SDCs.
  2. you still have the case of VIP ↔ TB ↔ DUT interfaces where you can’t assume your VIP/TB is holding its promise to provide a synchronous signal. Too many debugging cycles are plagued by assumptions that turn out to be false.

I haven’t seen much literature on these types of checks and maybe it’s worth questioning whether they are really needed, but so far I haven’t been offered a clear solution on how to address at least my second point above, that’s why I’m following the white rabbit!

In reply to dave_59:

In reply to abasili:
You can use
always @(signal iff (!$isunknown(signal) )
to get rid of X transitions.

By the way, I’ve tried the
always @(signal iff (!$isunknown(signal) )
, but it does not seem to work properly and I get in the always block while transitioning from X to 0.

In reply to abasili:
Sorry, it is behaving correctly. If you are only worried about X before reset, you can write

initial wait( !$isunknown(signal) )
        forever @(signal) ...