Clock Domain Crossing Basics
Clock Domain Crossing Basics
Clock Domain Crossing Basics
Metastability
The proper operation of a clocked flip-flop depends on the input signal being stable for a certain period of time before and after the clock edge. If the requirements for setup and hold time are met then a valid output will appear at the output of the flip-flop after a maximum delay. But if the requirements are not met, the signal will take much longer to reach a valid output level. We refer to the result as either an unstable state or metastability (Figure 1).
Metastability is avoided by adding various features to a design. Two of the most common such features are: two flip-flop circuits (this technique requires a stable design since incorrect design will lead to synchronization failure); and/or FIFO buffers (handshaking mechanisms).
falling edge violates the hold time for the next clock edge, simulation will propagate two clocks. Here are the assertions and related descriptions required during this step. To check stability of the data:
property stability; @(posedge clk) !$stable(d_in) |=> $stable(d_in) [*2]; endproperty : stability To check for a glitch: property no_glitch; logic data; @(d_in) (1, data = !d_in) |=> @(posedge clk) (d_in == data); endproperty : no_glitch assert property(stability); assert property(no_glitch);
The stability assertion checks the stability of d_in for two clocks. The no_glitch assertion checks for a glitch on d_in.
If the request is not synchronized properly because of flip-flop error, it can be caught during structural verification. That step identifies errors that arise when incorrect data is latched through glitches generated by combinational logic driving a flop-based synchronizer. Insufficient synchronization is also a structural error. High-speed clocks require a greater number of flip-flops and any shortfall can be detected by structural verification. For verification of the handshake synchronizer circuit, the following conditions can be asserted: 1. Every request gets acknowledged. 2. No acknowledgement without a request. 3. Data stability. Here are the assertions and related descriptions required for this stage.
Every request gets acknowledged sequence req_transfer; @(posedge clk) req ##1 !req [*1:max] ##0 ack; endsequence : req_transfer property req_gets_ack; @(posedge clk) req |-> req_transfer; endproperty : req_gets_ack No acknowledgement without request property ack_had_req; @(posedge clk) ack |-> req_transfer ended; endproperty : ack_had_req Data stability property data_stablility; @(posedge clk) req |=> data [*1:max] ##0 ack; endproperty : data_stablility assert property(req_gets_ack);
The req_transfer assertion checks whether every request gets acknowledged. The ack_had_req assertion checks whether every acknowledgement has a request. The data_stability assertion checks if the data is stable for the period of the request, including an acknowledgement. 2.2 Asynchronous FIFO circuit A dual clock asynchronous FIFO circuit is used for CDC synchronization when high latency in the handshake protocol cannot be tolerated. The circuit changes according to the requirements of the SoC but its basic operation is constant (Figure 5). Data is written into the FIFO from the source code and read from the FIFO in the destination clock domain. Read and write pointers are passed into different clock domains to generate full and empty status flags. Data write and read is kept in synchronization by write and read pointer positions. The following conditions can be asserted for verification of dual FIFO asynchronous circuits: 1. Data integrity. 2. Do not write when the FIFO is full. 3. Do not read when the FIFO is empty. Here are the assertions and related descriptions for this step.
Dont write when FIFO is full/Dont read when FIFO is empty property full_empty_access(clk, inc, empty_full_flag); @(posedge clk) inc |-> !empty_full_flag endproprty : full_empty_access Data integrity int write_cnt, read_cnt; always@(posedge write_clk or negedge write_rst_n) if (!write_rst_n) write_cnt = 0; else if (winc) write_cnt = write_cnt+1; always@(posedge read_clk or negedge read_rst_n) if (!read_rst_n) read_cnt = 0; else if (rinc) read_cnt = write_cnt+1; property data_integrity
int cnt; logic [DSIZE-1:0] data; disable iff (!write_rst_n || !read_rst_n) @(posedge write_clk) (winc, cnt = write_cnt, data = wdata) |=> @(posedge read_clk) first_match(##[0:$] (rinc && (read_cnt == cnt))) ##0 (rdata == data); endproperty : data_integrity assert property(full_empty_access(write_clk,winc,wfull)); assert property(full_empty_access(read_clk,rinc,rempty)); assert property (data_integrity);
The full_empty_access assertion ensures that there should not be data access when FIFO is empty and FIFO is full. The data_integrity assertion starts a new thread with an initial count value whenever data is written into the FIFO, and checks the read data value against a local data variable whenever a corresponding read operation occurs.
Gray encoding
In addition to handshake-based and FIFO-based synchronizers, another method of synchronizing data is first to gray encode it and then use multi-flop synchronizers to transfer it across domains. For multibit signals, a gray code ensures that only a single bit changes when a group of signals counts. This makes it possible to detect whether all the bits have been captured by the receiving clock together or if they have been skewed across multiple clock cycles due to metastability. Another example is in an asynchronous FIFO where the read and write pointers cross the write and read clock domains respectively. These pointers are control signals that are flop-synchronized. The
signal is gray-encoded prior to the crossover. This can be verified using assertions. The assertion is simply exclusive-or of the data and the previous value of the data with the condition that it must have at most one active bit (i.e., it should be one hot). The relevant assertions statement is shown here:
property check_graycoded = always ((gc == 8h0) || (((gc ^ prev(gc)) & ((gc ^ prev(gc)) 8h1)) == 8h0)); assert check_graycoded;
All the assertion statements in the examples above verify that the receiver gets the expected data. Since the latency between sending and receiving data is not known, verifying the sequence will ensure that no data gets dropped. The actual value in the data sequence should not make a difference to the verification; an assertion could be written using a more random sequence or specific data values. Formal verification can use this assertion to verify that the control logic of the assertion does not allow any loss of data and will generate counter example traces if any failures are found.
Conclusion
Multiple cross clock domain verification is best performed using both structural and functional verification techniques. The verification process may start with structural verification to remove errors related to insufficient synchronization, combinational logic driving synchronizers or missing synchronizers. After structural verification, functional verification may be used to implement additional assertions and check the usage of the synchronizers.