VC Spyglass CDC(二)常见的CDC处理方法

本篇源代码:
https://github.com/holdenQWER/CDC_example

CDC解决方案

EDA厂商提供golden的CDC处理单元,Synopsys的Building Block IP提供如下解决方案:
在这里插入图片描述
归类如下:
在这里插入图片描述

CDC处理中,根据跨时钟域信号宽度,分为Signle-bitMulit-bit。VC Spyglass CDC将Single-bit归为control pathMulit-bit归为data path

Single-bit

Single-bit分为level 类型event 类型

Level Signal

level signal跨时钟域时,会保持较长时间不变,从快时钟域到慢时钟域,还是慢时钟域到快时钟域,都不会漏采样,使用2级同步器即可。

Event Signal

event signal常看做一个单周期pulse signal。无论是快到慢,还是慢到快,都有漏采样的风险。

destination domain要确保成功采样到event single,需要event single至少维持destination domain 采样时钟3个沿(无论是上升沿还是下降沿,连续的3个沿)。

在这里插入图片描述

所以从慢时钟域到快时钟域,如果快时钟域的频率大于慢时钟域的1.5倍,就满足了three edge的要求。

Fast to Slow
从快时钟域到慢时钟域,需要对快时钟域的信号做展宽处理,以满足慢时钟域的three edge要求。
可以通过移位寄存器对evnet_signal做打拍处理,然后自身各bit相或,展宽event signal。移位寄存器的位宽和clock_ratio相关。这种展宽的方式只适合clock_ratio固定的CDC处理,相邻pulse的间隔也有要求。

Slow to Fast
从慢时钟域到快时钟域,如果频率相差较大,慢时钟域会过采样。可以使用边沿检测的方式,防止过采样。
在这里插入图片描述
event_d_dly1Q端和D端取反后相与,检测上升边沿。evnet_s需间隔至少一个clk_s的周期,避免信号重叠漏采。

Pulse Synchronizer
上述两种CDC处理方法都有明显的限制,只能从快到慢或者从慢到快。可以将信号展宽边沿检测相结合,设计一种通用的pulse synchronizer

module pulse_sync(input clk_s,
                  input rstn_s,
                  input event_s,
                  input clk_d,
                  output event_d);
            

   reg event_s_dly;
   reg event_d_dly;
   wire event_s_expand;
   wire event_d_clr;
   wire event_d_sync;

   assign event_s_expand = (event_s | event_s_dly) & (~event_d_clr);

	always @(posedge clk_s or negedge rstn_s)
      if(~rstn_s)
         event_s_dly <= 1'b0;
      else
         event_s_dly <= event_s_expand;

   rstn_sync u_rstn_sync(.clk(clk_d),
                         .rstn_in(rstn_s),
                         .rstn_out(rstn_d));

   sync_cell u_sync_to_dst(.clk(clk_d),
                         .rst_n(rstn_d),
                         .in(event_s_dly),
                         .out(event_d_sync));

   sync_cell u_sync_to_src(.clk(clk_s),
                         .rst_n(rstn_s),
                         .in(event_d_sync),
                         .out(event_d_clr));

	always @(posedge clk_d or negedge rstn_d)
      if(~rstn_d) begin
         event_d_dly <= 1'b0;
      end
      else begin
         event_d_dly <= event_d_sync;
      end

   assign event_d = event_d_sync & ~event_d_dly;

endmodule

在这里插入图片描述

src domain利用或门将边沿信号转化成电平信号;destination domain采样到信号后回复ack清0。ack信号也需要2级同步处理。(上述电路图缺少了destination domain边沿检测的部分。
上述设计的latency比较大,需要2次2级同步处理。event_s信号要和ack做好handshake。

A Better Design for Pulse Synchronizer

下面介绍一种Design Ware库中的脉冲同步器:DW_pulse_sync

电路逻辑如下:
在这里插入图片描述
source domain两级同步+电平转脉冲的电路结构用简易的图标表示如下:
在这里插入图片描述
CODE:

module pulse_sync(input clk_s,
                  input rstn_s,
                  input event_s,
                  input clk_d,
                  input rstn_d,
                  output event_d);
            

   reg event_s_toggle;
   reg event_d_dly;
   wire event_d_sync;

	always @(posedge clk_s or negedge rstn_s)
      if(~rstn_s)
         event_s_toggle <= 1'b0;
      else
        event_s_toggle <= event_s_toggle ^ event_s;

   sync_cell u_2dff_sync(.clk(clk_d),
                         .rst_n(rstn_d),
                         .in(event_s_toggle),
                         .out(event_d_sync));

	always @(posedge clk_d or negedge rstn_d)
      if(~rstn_d)
         event_d_dly <= 1'b0;
      else
         event_d_dly <= event_d_sync;

   assign event_d = event_d_dly ^ event_d_sync;

endmodule

source domain通过异或门,实现pulse的边沿转电平的操作。这种结构实现不归零编码Non-Return-to-Zero (NRZ),相当于一个toggle register。
destimation domain通过异或门将level转化成一个周期的pulse。
这种不需要ack的脉冲同步器,相比上一种设计,缩减了latency,结构也更简单。
使用时需注意一下两点:
第一:脉冲的频率小于目的域时钟频率的1/2
在这里插入图片描述
上述限制出于两点考虑,
1:event_s_toggle需要满足three edge,才能被目的域时钟有效采样到,频率相差1.5倍即可。
2:event_d_sync的level至少需要持续2拍,否则无法恢复出脉冲,也就是说event_s_toggle至少要被目的域时钟采样两次,频率相差2倍即可。

第二:source domain复位后,destinantion domain也需要复位

在奇数脉冲时,电平被toggle为1,只复位source domain,再次传播脉冲时,第一个脉冲会被漏采。
偶数脉冲时,可以单独复位source domain

DW库中也提供了带反馈信号的脉冲同步器Dw_pulseack_sync ,结构如下:
在这里插入图片描述
FSM判断ready busy:

module pulse_sync_ack(input clk_s,
                  input rstn_s,
                  input event_s,
                  input clk_d,
                  input rstn_d,
                  output event_d,
                  output ack_s);
            

   reg event_s_toggle;
   reg event_d_dly;
   wire event_d_sync;

   wire event_ack;
   reg event_ack_dly;
   wire ack_pulse;

   reg state_cur;
   reg state_nxt;
   reg ack_r;
   parameter READY = 1'b0;
   parameter BUSY  = 1'b1;

   always @(posedge clk_s or negedge rstn_s)
      if(~rstn_s) 
         state_cur <= READY;
      else
         state_cur <= state_nxt;

   always @(*) begin
      state_nxt = state_cur;
      case (state_cur)
         READY:begin
            ack_r = 1;
            if(event_s)
               state_nxt = BUSY;
         end
         BUSY: begin
            ack_r = 0;
            if(ack_pulse)
               state_nxt = READY;
         end
         default: begin
            state_nxt = READY;
         end
      endcase
   end

   assign ack_s = ack_r;

	always @(posedge clk_s or negedge rstn_s)
      if(~rstn_s)
         event_s_toggle <= 1'b0;
      else
        event_s_toggle <= event_s_toggle ^ (event_s & ack_s);

   sync_cell u_2dff_sync_s(.clk(clk_d),
                         .rst_n(rstn_d),
                         .in(event_s_toggle),
                         .out(event_d_sync));

	always @(posedge clk_d or negedge rstn_d)
      if(~rstn_d)
         event_d_dly <= 1'b0;
      else
         event_d_dly <= event_d_sync;

   assign event_d = event_d_dly ^ event_d_sync;

   sync_cell u_2dff_sync_d(.clk(clk_s),
                         .rst_n(rstn_s),
                         .in(event_d_dly),
                         .out(event_ack));

	always @(posedge clk_s or negedge rstn_s)
      if(~rstn_s)
         event_ack_dly <= 1'b0;
      else
         event_ack_dly <= event_ack;

   assign ack_pulse = event_ack_dly ^ event_ack;
endmodule

只有在ack_s为高时,source domain才可以发送event_s脉冲,对异步时钟的频率没有要求,latency较大。
在这里插入图片描述

Mulit-bit

对于Mulit-bit的signal,总结如下几类方法:

  1. Muilt-bit的各bit分别2级同步器处理。这种方式适用于可以通过格雷编码的设计,如Binary counters;下级电路对data coherency不敏感,如被软件处理的interrupt信号;使用更高频率过采样等等。
  2. 采用Multi-Cycle Path (MCP ) 结构的设计。
  3. 使用异步FIFO。

上述的方法根据实际业务选择最佳方案,而不是相互取代的。

Multi-Cycle Path (MCP)formulation

MCP解释如下:
在这里插入图片描述
DW_data_sync_na就是一个MCP结构的设计:
在这里插入图片描述
分为control pathdata path
control path需要同步处理,采用的是DW-pulse_sync
data path不需要同步处理,节约寄存器使用;
源和目的端各一个data MUX,由同步的脉冲control信号控制。control使能时,data需保持不变,维持数个Multi-Cycle目的域时钟,直到被采样,避免了data path上的亚稳态。

DW_data_sync_na不带ack反馈,若要支持back-to-back的传输,需要destination domain 频率比source domain频率快三倍。

如下图,频率2倍关系,dataf2和data49 back -to-back发送,dataf2 miss captured。DW_pulse_sync并不支持back-to-back的同步,需要额外逻辑设计,所以至少要3倍频率。
在这里插入图片描述
DW_data_sync则是带ack反馈的data同步器,结构类似如下:
是将上述DW_pulseack_sync的输出脉冲作为data MUXsel
在这里插入图片描述
上述电路默认destination domain一直可以接收source domain的数据。

下面介绍一种destination domain可以通过load信号控制是否准备接受source doamin数据的流控结构:

在这里插入图片描述
从电路图可以看出,source domain destination domain结构对称,destination domain的FSM划分为 idle wait ready的三个状态。

  1. ack_s拉高时,src端拉高data_vld_s发送有效数据,期间data_s保持不变。随后ack_s拉低。
  2. 数拍后data_vld_d有效,并持续为高。直到dst端拉高load_ddata_vld_d下的data_d采样。
  3. 完成采样后,ack_s拉高,src端可以进行下一次操作。
    在这里插入图片描述
    CODE:
module data_sync(input clk_s,
                  input rstn_s,
                  input data_vld_s,
                  input [7:0] data_s,
                  input clk_d,
                  input rstn_d,
                  input load_d,
                  output data_vld_d,
                  output reg [7:0] data_d,
                  output ack_s);
            


   reg state_curs;
   reg state_nxts;
   reg [1:0]state_curd;
   reg [1:0]state_nxtd;
   reg ack_r;
   parameter READYS = 1'b0;
   parameter BUSYS  = 1'b1;
   parameter IDLED  = 2'b00;
   parameter WAITD  = 2'b01;
   parameter READYD = 2'b10;

   reg [7:0] data_r;
   reg data_vld;
   wire data_sel_s;
   wire data_sel_d;
   wire ack_sync;
   wire data_vld_sync;

   // source domain data MUX
   always @(posedge clk_s or negedge rstn_s)
      if(~rstn_s) 
         data_r <= 8'h0;
      else if (data_sel_s)
         data_r <= data_s;
         
   // destination domain data MUX
   always @(posedge clk_d or negedge rstn_d)
      if(~rstn_d) 
         data_d <= 8'h0;
      else if (data_sel_d)
         data_d <= data_r;

   // source domain FSM
   always @(posedge clk_s or negedge rstn_s)
      if(~rstn_s) 
         state_curs <= READYS;
      else
         state_curs <= state_nxts;

   always @(*) begin
      state_nxts = state_curs;
      case (state_curs)
         READYS:begin
            ack_r = 1;
            if(data_vld_s)
               state_nxts = BUSYS;
         end
         BUSYS: begin
            ack_r = 0;
            if(ack_sync)
               state_nxts = READYS;
         end
         default: begin
            state_nxts = READYS;
         end
      endcase
   end

   assign ack_s = ack_r;
   assign data_sel_s = data_vld_s & ack_s;

   // source to destination pulse sync
   pulse_sync u_pulse_sync_s(.clk_s(clk_s),
                             .rstn_s(rstn_s),
                             .event_s(data_sel_s),
                             .clk_d(clk_d),
                             .rstn_d(rstn_d),
                             .event_d(data_vld_sync));


   // destination domain FSM
   always @(posedge clk_d or negedge rstn_d)
      if(~rstn_d) 
         state_curd <= IDLED;
      else
         state_curd <= state_nxtd;

   always @(*) begin
      state_nxtd = state_curd;
      case (state_curd)
         IDLED:begin
            data_vld = 0;
            if(data_vld_sync)
               state_nxtd = WAITD;
         end
         WAITD:begin
            data_vld = 1;
            if(load_d)
               state_nxtd = READYD;
         end
         READYD:begin
            data_vld = 0;
            if(!load_d)
               state_nxtd = IDLED;
         end
         default:begin
            state_nxtd = IDLED;
            data_vld = 0;
         end
      endcase
   end

   assign data_sel_d = load_d & data_vld;
   assign data_vld_d = data_vld;

   // destination to source domain sync
   pulse_sync u_pulse_sync_d(.clk_s(clk_d),
                             .rstn_s(rstn_d),
                             .event_s(data_sel_d),
                             .clk_d(clk_s),
                             .rstn_d(rstn_s),
                             .event_d(ack_sync));

endmodule

AHB to APB asynchronous bridge设计采用的也是MCP结构,只需要同步req/ack 握手信号。
在这里插入图片描述

异步FIFO

异步FIFO适用对缓冲有要求的异步数据流的设计。DW提供了常见的两种fifo,DW_fifo_s2_sfDW_fifo_2c_df
在这里插入图片描述
异步FIFO的设计难点主要在于异步读写指针的比较和空满状态的产生。具体参考:🔗 Simulation and Synthesis Techniques for Asynchronous FIFO Design with Asynchronous Pointer Comparisons – Cliff Cummings

在使用DW_fifo_s2_sf需要注意,clk_pushclk_pop时钟域由一个rst_n控制,VC Spyglass CDC检测时会报error。可以将fifo设置为blackbox或者将rst_n设置为static。

为了避免复位时的亚稳态,需要rst_n拉低至少三个慢时钟的周期,并在一个clk_push周期后才可以push数据。建议rst_n使用clk_push的复位信号。
在这里插入图片描述

Qasi Synchronous

时钟同源,由同一个分频器产生,频率为整数倍关系,相位差固定,可以看成qasi synchronous准同步电路,可以当作同步电路处理,ctrl path不需要同步处理。
DW_data_qsync_lh 为快时钟域到慢时钟域的传输。
DW_data_qsync_hl 为慢时钟域到快时钟域的传输。
使用时配置clk_ratio参数。当clk_ratio = 2时,负责采样的第一个触发器使用下降沿采样,利于时序管理;其余情况所有触发器都是上升沿采样。
clk_ratio=2DW_data_qsync_lh结构如下:
在这里插入图片描述
clk_ratio=2DW_data_qsync_hl 结构如下:
在这里插入图片描述

### 解决Spyglass无法打开的问题 当遇到Spyglass无法正常启动的情况时,可以尝试以下几个方法来解决问题。 对于`feature could not be checked out`错误,解决方案涉及更新授权文件。具体操作是在命令行环境中使用最新的`Synopsys.dat`文件替换原有虚拟机中的同名文件[^1]: ```bash cp /path/to/new/Synopsys.dat /original/path/ ``` 之后再次尝试通过命令行启动Spyglass: ```bash spyglass ``` 如果收到 `[Error] Another session is already running or previous run had some issues` 的提示,则表明当前工作目录下存在正在运行的vc_spyglass实例或者是上次执行过程中出现了未处理的问题。此时应该先终止任何已存在的Spyglass进程,或者切换到其他路径重新开始会话[^2]: ```bash ps aux | grep spyglass # 查找并结束所有相关进程 kill -9 PID # 使用找到的PID号杀死特定进程 cd /new/directory # 或者简单地改变当前的工作目录 spyglass # 尝试重启工具 ``` 另外,在某些场景下,移除批处理模式参数也可以帮助排除因自动化脚本引起的异常情况。例如修改启动指令如下所示[^4]: ```bash spyglass –project training.prj –goals cdc/cdc_setup & ``` 尽管Lint检查并非总是必要的,但在开发流程中保持良好的实践习惯有助于提前发现潜在风险,减少后期维护成本。因此建议尽可能修复所有的警告信息而不是依赖于偶然性的成功编译结果[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

劲仔小鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值