学习笔记:

1时序电路的优点:

1: 可以容忍组合逻辑电路产生的毛刺; 组合逻辑综合出来的电路可能因为路径不同导致延时不同,易出现毛刺;利用时钟沿可以等待输出数据稳定之后(因毛刺导致的错误输出结束之后),再将数据采入;

2: 纯组合逻辑只能由当前输入决定当前输出,无法实现带反馈的逻辑;否则就会出现死循环;这种带反馈的逻辑,只能先通过寄存器暂存起来,然后由时钟来控制输出的反馈和更新;

2异步复位:

就是将复位信号写在敏感列表中,代表我异步复位不受时钟的控制; * always @ (posedge clk or negedge rst_n)* 相反,不写代表同步复位,说明只有在时钟上升沿来临之时,我检查我的复位信号有没有效;

3:状态机部分知识:

分类:moore:状态机输出只与当前的状态有关;(状态多一点) mealy: 状态机输出和当前的状态以及当前输入有关;(判断输出时还需要考虑当前输入)

三段式状态机:

alt

第一个逻辑块:

为组合逻辑,always@(*) ... case(c_state).. 描述状态转移条件;

第二个逻辑块:

为时序逻辑,是一个寄存器,将当前状态寄存,将n_state的值赋给c_state。也是时序电路的体现;

第三个逻辑块:

为组合逻辑,寄存器输出的状态c_state分为两路,一路送给第一个逻辑块进行下一状态的判断,另一路送给该块进行输出数据的判断;

两种状态机输出特性:

moore:在有限个门延时之后,输出会达到稳定,输出会在一个时钟周期内保持稳定,即使某关键信号在该时钟内发生变化,输出信号也不会发生变化。这是因为最后判断是用c_state,然而c_state被n_state赋值需要一个周期;分开输入和输出是该状态机的特点; mealy:因为输出受到当前输入的影响,然而输入随时可能变化,这就导致输出状态比moore快一个时钟周期,输入信号的噪声可能会出现在输出上;

注意事项:

每一种情况都要考虑,特别是case(c_state),要加default,否则可能生成锁存;

///////////////////////////////

FIFO篇

///////////////////////////////

双端口ram:

fifo是基于ram的, 设计实现一个16*8的双端口ram; 宽度8bit 深度16,addr4bit

module dp_ram(
   input wr_clk,
   input rd_clk,
   input wr_allow,
   input rd_allow,
   input [ADDR_WIDTH -1:0]wr_addr,
   input [ADDR_WIDTH -1:0]rd_addr,
   
   input [RAM_WIDTH -1:0]wr_data,
   output reg [RAM_WIDTH -1:0]rd_data
);

  parameter RAM_WIDTH = 8;
  parameter RAM_DEPTH = 16;
  parameter ADDR_WIDTH = 4;
 
  reg [RAM_WIDTH -1:0] memory [RAM_DEPTH -1 : 0];

always @ (posedge wr_clk )
begin
   if(wr_allow)
     memory[wr_addr] <= wr_data;
end

always @ (posedge rd_clk )
begin
   if(rd_allow)
     rd_data <=  memory[rd_addr];
end
endmodule

注意ram的定义方式; reg [RAM_WIDTH -1:0] memory [RAM_DEPTH -1 : 0];

单端口ram

module ram(

input clk,                   //只有一个时钟,读写都用它
input rst_n,
input [7:0] addr,            
input [15:0] w_data,
input   wr_en,    //1write 0read  

output [15:0] r_data
);

  reg[15:0] memory [255:0];
  
  integar i;
  always @ (posedge clk)
     if(!rst_n)
          for(i=0;i<255;i=i+1)   //复位的时候给ram清零
               mem[i] <= 0;
     else if(wr_en)
	     mem[addr] <= w_data;
		 
   always @ (posedge clk)
     if(!rst_n)
          r_data <= 0;
     else if(!wr_en)
	     r_data <= mem[addr];          	 

双端口ram与单端口ram最大的区别就是:双端口可同时读写,都不耽搁; 单端口只能实现一个;

同步fifo

  1. 先送入的数据也要先读出来;这是和ram不一样的地方;
  2. 原则:满不能写,空不能读;
  3. 关键:full和empty信号如何产生;
  4. 产生方法1:计数器:执行一次写,计数器加一;执行一次读,计数器减一;所有:cnt == depth(满了);
  5. 产生方法2:地址位扩展一位; 先试试第一种方法:
module syn_fifo
(
  input fifo_rst,
  input clk,
  input read_enable,
  input write_enable,
  input [DATA_WIDTH -1 :0]wr_data,
  output [DATA_WIDTH -1 :0]rd_data,
  
  output reg full,
  output reg empty,
  output reg [ADDR_WIDTH -1 :0]cnt
); 
    
      parameter DATA_WIDTH = 8;
      parameter ADDR_WIDTH = 9;
	   
dp_ram dp_ram_1(
   .wr_clk      (clk),
   .rd_clk      (clk),
   .wr_allow    (wr_allow),
   .rd_allow    (rd_allow),
   .wr_addr     (wr_addr),
   .rd_addr     (rd_addr),
   .wr_data     (wr_data),
   .rd_data     (rd_data)
);	   
	   
   always@(posedge clk or posedge fifo_rst)   
      if(fifo_rst)
	      empty <= 1'b1;   //上电之后肯定空的
	  else 
          empty <= (!write_enable && (cnt[8:1] == 8'd0) && ((cnt [0] ==0) ||read_enable));   
            //先看前面,如果地址位前八位都是0并且我还不写,进行下一个判断:如果最后一位是0,好了 空了,如果是1,说明还留了一个数据,但是此时我读请求,则为空;
            //(!write_data && (cnt[8:1] == 8'b0)  当前计数器[8:1]全为0 并且此时我没有写入请求
			//(cnt [0] ==0) || read_enable == 1)        
            // 0               0    : 此时(cnt [0] ==1) read_enable ==0 ;此时有一个数据 并且不把他读出来; 
            // 0               1    : 此时(cnt [0] ==1) read_enable ==1; 此时有一个数据 并且把他读出来;     
            // 1               0    : 此时(cnt [0] ==0) read_enable ==0 ;此时无数据 不读;
            // 1               1    : 此时(cnt [0] ==0) read_enable ==1 ;此时无数据 要读;               
   always@(posedge clk or posedge fifo_rst)
      if(fifo_rst)
	      full <= 1'b0;  //上电之后肯定不满的
	  else 
          full <= (!read_enable && (cnt[8:1] == 8'hff) && ((cnt [0] ==1) ||write_enable));          
        //简而言之:我先看除了第一位地址位以外的其他位,如果当前所有地址位都为1(除了第一位),并且此时我不读,就进行下一个判断:如果最后一位也是1,不用看了,满了,如果最后一位不是1,但是此时我写入了,也是满了;
        //(!read_enable && (cnt[8:1] == 8'hff)  前八位地址已满,最后一位暂时不考虑的情况下,我不读
		//(cnt [0] ==1) ||write_enable == 1)
		//0           0     cnt [0] ==0  write_enable == 0  地址最后一位为0,也不写入      
		//0           1     cnt [0] ==0  write_enable == 1  地址最后一位为0,但是写入数据  
		//1           0     cnt [0] ==1  write_enable == 0  地址最后一位为1,不写入
		//1           1     cnt [0] ==1  write_enable == 1  地址最后一位为1,且继续写入      
   
     //读允许信号: 读请求且此时不空
     wire rd_allow = (read_enable && !empty   );
     //写允许信号: 写请求且此时不满
     wire wr_allow = (write_enable && !full   );
   
    reg [ADDR_WIDTH -1 :0]rd_addr;
    reg [ADDR_WIDTH -1 :0]wr_addr;
   //产生读写地址(在允许的情况下,读取地址自动加一)
    always@(posedge clk or posedge fifo_rst)   
      if(fifo_rst)
	      rd_addr <= 0;   
	  else if((rd_allow) && (!empty))  //其实不要!empty也行,前面已经判断过了 赘余了
          rd_addr <= rd_addr + 1;     

    always@(posedge clk or posedge fifo_rst)   
      if(fifo_rst)
	      wr_addr <= 0;   
	  else if((wr_allow) && (!full))
          wr_addr <= wr_addr + 1;      
   //计数器
  always@(posedge clk or posedge fifo_rst)   
      if(fifo_rst)
	      cnt <= 0;   
	  else if(   (!rd_allow && wr_allow) || (rd_allow && !wr_allow)  )   //同时只存在一个动作
           begin
           //写一个数就+1 否则-1;
		        if(wr_allow)
		        cnt <= cnt + 1;     
                else 
				cnt <= cnt - 1;  
            end
   
endmodule

这是其中例化的双端口ram

module dp_ram(
   input wr_clk,
   input rd_clk,
   input wr_allow,
   input rd_allow,
   input [ADDR_WIDTH -1:0]wr_addr,
   input [ADDR_WIDTH -1:0]rd_addr,
   
   input [RAM_WIDTH -1:0]wr_data,
   output reg [RAM_WIDTH -1:0]rd_data
);

  parameter RAM_WIDTH = 8;
  parameter RAM_DEPTH = 512;
  parameter ADDR_WIDTH =9;
 
  reg [RAM_WIDTH -1:0] memory [RAM_DEPTH -1 : 0];

always @ (posedge wr_clk )
begin
   if(wr_allow)
     memory[wr_addr] <= wr_data;
end
always @ (posedge rd_clk )
begin
   if(rd_allow)
     rd_data <=  memory[rd_addr];

end
endmodule

//例化的时候读写时钟都用了clk, 测试脚本:

`timescale 1ns / 1ps
module tb();
 reg clk = 0;
 always #5 clk = !clk;

	

       parameter DATA_WIDTH = 8;
       parameter ADDR_WIDTH = 9;
  reg fifo_rst                        ;
  //reg clk                             ;
  reg read_enable                     ;
  reg write_enable                    ;
  reg [DATA_WIDTH -1 :0]wr_data    ;
  wire [DATA_WIDTH -1 :0]rd_data     ;
                                      
  wire full                           ;
  wire empty                          ;
  wire [ADDR_WIDTH -1 :0]cnt          ;
	
syn_fifo syn_fifo_1(
  .fifo_rst       (fifo_rst),
  .clk            (clk),
  .read_enable    (read_enable),
  .write_enable   (write_enable),
  .wr_data     (wr_data),
  .rd_data      (rd_data),

  . full          (full),
  . empty         (empty),
  . cnt           (cnt)
); 
    initial 
      begin 
       fifo_rst <= 1; read_enable <= 0; write_enable <= 0; wr_data <= 0;
       #10 fifo_rst <= 0;     read_enable  <= 0 ; write_enable <= 0; wr_data <= 0;
       #10 read_enable <= 0;  write_enable <= 1; wr_data <= 8'd2;
       #10 read_enable <= 0;  write_enable <= 1; wr_data <= 8'd3;
       #10 read_enable <= 0;  write_enable <= 1; wr_data <= 8'd4;
       #10 read_enable <= 0;  write_enable <= 1; wr_data <= 8'd5;
      
       #10 read_enable <= 1;  write_enable <= 0; wr_data <=0;
       #10 read_enable <= 1;  write_enable <= 0; wr_data <=0;
       #10 read_enable <= 1;  write_enable <= 0; wr_data <=0;
       #10 read_enable <= 1;  write_enable <= 0; wr_data <=0;
       #10 read_enable <= 0;  write_enable <= 0; wr_data <=0;
  
     end
  endmodule

写入2345后读出; 仿真波形: alt

读出数据是不是应该在前边给0呢;

第二种方法: 通过扩展地址位来判断空满; 这个方法真是简单多了:

module sync_fifo_2(
   input clk,
   input rst_n,
   input write_enable,
   input read_enable,
   input [7:0] write_data,
   output empty,
   output full,
   output reg[7:0] read_data
); 

   reg [7:0] mem [15:0];
   wire [3:0] write_addr_a,read_addr_a;
   wire [4:0] write_addr_e,read_addr_e;

   assign w_addr_a = w_addr_e[3:0];
   assign r_addr_a = r_addr_e[3:0];
   always @ (posedge or negedge rst)
   if (!rst_n)
      r_addr_e <= 5'b0;
	 else 
	  begin
	   if( empty == 0 && read_enable == 1)
	   begin
        read_data <= mem[r_addr_a];
	    r_addr_e <= r_addr_e + 1;
       end
   
   
      always @ (posedge or negedge rst)
   if (!rst_n)
      w_addr_e <= 5'b0;
	 else 
	  begin
	   if( full == 0 && write_enable == 1)
	   begin
         mem[w_addr_a] <= write_data ;
	    w_addr_e <= w_addr_e + 1;
       end
	   
	assign empty = ( r_addr_e == w_addr_e)? 1:0;
    assign full = (r_addr_e[4] != w_addr_e[4] && r_addr_e[3:0] == w_addr_e[3:0] )? 1:0;	
	   
	   
	  endmodule 

前面真是费力不讨好;这种方法fifo的深度一定是2的n次方;

异步fifo:

  1. 读写时钟不一样;
  2. 使用扩展位进行空满判断;因为时钟不一样,产生的空满信号有跨时钟域问题,需要格雷码进行打一拍;
  3. 格雷码:每次只改变一位,这样产生亚稳态的可能就更小了;
  4. 要点:
写指针和读指针要分别放在不同时钟域中采样判断,易出现亚稳态;
       2.为什么不用二进制编码进行空满判断?
           格雷码编码的好处是每次只有1bit变化,这样有两种结果:
           1)拿到目标的准确状态,符合期望;
           2)拿到还没有更新的状态,但是假设现在采样读指针,最坏的情况就是把不满判断为满,这会使得写操作被禁止,但这样只会带来写入的延迟,不会有什么大的影响,写指针同理;不会对逻辑产生影响;

代码: alt

顶层模块:asyn_fifo: 读写part,ram,俩syn;

module asyn_fifo #(
   parameter DEPTH   = 256,       
   parameter WIDTH_A = 8,       
   parameter WIDTH_D = 16       
)(                                 
   input                     w_clk,
   input                     rst_n,
   input                     w_req,
   input [WIDTH_D-1:0]       w_data,
                     
   input                     r_clk, 
   input                     r_req, 
                     
   output [WIDTH_D-1:0]      r_data,
   
   output                    w_full ,  
   output                    r_empty 
   
);

wire [WIDTH_A:0]  w_addr;
wire [WIDTH_A:0]  r_addr;

wire [WIDTH_A:0]  r_gaddr_syn;
wire [WIDTH_A:0]  w_gaddr_syn;


wire [WIDTH_A:0]  w_gaddr;
wire [WIDTH_A:0]  r_gaddr;
//写部分
write_part #(
   .WIDTH_A(WIDTH_A)     
)write_control(            
  .w_clk  ( w_clk   ),     
  .w_rst  ( rst_n   ),     
  .w_req  ( w_req   ),     
                   
  .r_gaddr( r_gaddr_syn ),   
                   
  .w_full ( w_full  ),   
  .w_addr ( w_addr  ),    
                   
  .w_gaddr( w_gaddr )    
);                     
//例化双端口ram
RAM #(                                 
   .DEPTH  (DEPTH  ),                     
   .WIDTH_A(WIDTH_A),                     
   .WIDTH_D(WIDTH_D)                                                                  
)
RAM_inst
(                                            
    .r_clk ( r_clk  ) , 
    .w_clk ( w_clk  ) ,             
    .rst_n ( rst_n  ) ,             
                   
    .w_addr( w_addr[WIDTH_A-1:0] ) ,            
    .w_data( w_data ) ,     
    .w_en  ( w_req & (!w_full)   ) ,              
                   
    .r_addr( r_addr[WIDTH_A-1:0] ) ,            
    .r_en  ( r_req & (!r_empty)   ) ,              
                   
    .r_data( r_data )      
);                                            
    
//例化了两个同步模块用来输出格雷码转换的地址
syn #(                          
  .WIDTH_D(WIDTH_A)                
)
syn_w_2_r                                      
(                                      
    .syn_clk ( r_clk ) ,  
    .syn_rst ( rst_n ) ,  
    .data_in ( w_gaddr ) ,  
             
    .syn_data( w_gaddr_syn )  
);        

syn #(                     
  .WIDTH_D(WIDTH_A)                                          
)                          
syn_r_2_w                  
(                          
   .syn_clk ( w_clk ) ,    
   .syn_rst ( rst_n ) ,    
   .data_in ( r_gaddr ),  
                           
   .syn_data( r_gaddr_syn )  
);     

//读部分                
read_part  #(
  .WIDTH_A(WIDTH_A)   
)read_control(                               
  .r_clk   ( r_clk   ),              
  .r_rst   ( rst_n   ),              
  .r_req   ( r_req   ),              
                              
  .w_gaddr ( w_gaddr_syn ),            
                            
  .r_empty ( r_empty ),           
  .r_addr  ( r_addr  ),            
                             
  .r_gaddr ( r_gaddr )            
);                      
endmodule                   

格雷码转换模块:格雷码与普通二进制编码不同点在于每次只变一位数;用处在后面cdc;


module bin_to_gray #(
  parameter  WIDTH_D = 5
)(
   input  [WIDTH_D-1:0] bin_c,
   output [WIDTH_D-1:0] gray_c
);                 

//将最高一位取出
wire h_b;
assign h_b = bin_c[WIDTH_D-1];       
//gray码的第n位 == 源码的第n位和第n+1位异或  
//gray的第一位 == 源码的第一位
reg [WIDTH_D-2:0] gray_c_d;

integer i;     
always @( * )
  for( i=0;i<WIDTH_D-1;i=i+1 )
    gray_c_d[i] = bin_c[i]^bin_c[i+1];      

//拼出输出的gray码    
assign gray_c = {h_b,gray_c_d};
endmodule

例化ram模块:前面提到的双端口ram

module RAM #(
   parameter DEPTH = 256,
   parameter WIDTH_A = 8,
   parameter WIDTH_D = 16

)(
  input                    r_clk,
  input                    w_clk,
  input                    rst_n,
                           
  input [WIDTH_A-1:0]      w_addr,   
  input [WIDTH_D-1:0]             w_data,
  input                    w_en,
  
  input [WIDTH_A-1:0]      r_addr,
  input                    r_en,
  
  output reg[WIDTH_D-1:0]         r_data
);

//定义一个ram
reg [WIDTH_D -1:0]  mem[0:DEPTH-1];
//ram初始值全部赋零
//如果有写申请,将数据写入特定地址中
integer i;
always @( posedge w_clk )
  if( !rst_n )
    for(i=0;i<DEPTH;i=i+1)
      mem[i] <= 'h0000;
  else if( w_en )
    mem[w_addr] <= w_data;
 
//如果有读申请,将特定地址中数据读出
always @( posedge r_clk )
  if( !rst_n )
    r_data <= 'h0000;
  else if( r_en )
    r_data <= mem[r_addr];
    
endmodule  

读部分:



module read_part #(
   parameter WIDTH_A = 8 
)(
      input            r_clk,          
      input            r_rst,          
      input            r_req,          
                                              
      input      [WIDTH_A:0] w_gaddr,        
                                       
      output           r_empty ,        
      output reg[WIDTH_A:0]  r_addr ,         
                                       
      output reg [WIDTH_A:0]  r_gaddr         
);
 
 
   //在不空的情况下,如果有读请求,就将读地址+1
always @( posedge r_clk )                                                          
  if( !r_rst )                                                                     
    r_addr <= 'h00;                                                                
  else if( r_req&&(!r_empty) )                                                      
    r_addr <= r_addr + 1'b1;                                                       
    

//例化gray码转换模块	
wire [WIDTH_A:0] r_gaddr_w;									    
bin_to_gray #(                                                                     
  .WIDTH_D(WIDTH_A+1)                                                                      
)                                                                                  
bin_to_gray_inst                                                                   
(                                                                                  
   .bin_c ( r_addr ),                                                              
   .gray_c( r_gaddr_w )                                                              
); 

//将经过gray模块转换的地址打一拍输出
always @( posedge r_clk )
  if( !rst_n )
    r_gaddr <= 'h0;
  else
    r_gaddr <= r_gaddr_w;	  

  // 当输入的写地址(经过gray码转换)与读地址相同时,输出空标志                                                                               
assign r_empty = (w_gaddr==r_gaddr_w)?1'b1:1'b0;       

endmodule

同步模块:将接收到的数据打两拍输出,避免亚稳态



module syn #(
  parameter WIDTH_D = 5
)
(
    input                    syn_clk,
    input                    syn_rst,
    input [WIDTH_D:0]      data_in,
    
    output [WIDTH_D:0]     syn_data
);

reg [WIDTH_D:0] syn_reg_1,syn_reg_2;

//将输入数据在当前时钟域下打两拍然后输出
always @( posedge syn_clk )
  if( !syn_rst ) begin
  	syn_reg_1 <= 'h00;
  	syn_reg_2 <= 'h00;
  end
  else begin
    syn_reg_1 <= data_in;
    syn_reg_2 <= syn_reg_1;
  end
  
assign syn_data = syn_reg_2;

endmodule

写部分:

module write_part #(
  parameter WIDTH_A = 8 
)(
  input            w_clk,
  input            w_rst,
  input            w_req,
             
  input      [WIDTH_A:0] r_gaddr,
             
  output           w_full ,
  output reg[WIDTH_A:0]  w_addr ,
  
  output reg [WIDTH_A:0]  w_gaddr       
);                                   

//当前有写请求并且当前不空时:写地址+1
reg [4:0] w_addr;
always @( posedge w_clk )
  if( !w_rst )
    w_addr <= 'h00;
  else if( w_req&&(!w_full) )
    w_addr <= w_addr + 1'b1;


//将输出地址输入到gray码转换模块
wire [WIDTH_A:0] w_gaddr_w;
bin_to_gray #(             
  .WIDTH_D(WIDTH_A+1)              
)
bin_to_gray_inst
(                                
   .bin_c ( w_addr ),    
   .gray_c( w_gaddr_w )    
);        


//将gray转换模块输出信号打一拍后输出
always @( posedge w_clk )
  if( !rst_n )
    w_gaddr <= 'h0;
  else
    w_gaddr <= w_gaddr_w;	  


//当gray输出写地址的前两位取反,后两位不变,和读地址相比较,相等则说明满;
assign w_full = ({~w_gaddr_w[WIDTH_A],~w_gaddr_w[WIDTH_A-1],w_gaddr_w[WIDTH_A-2:0]}==r_gaddr)?1'b1:1'b0;
endmodule                  

测试脚本:

`timescale 1ns/1ps

module asyn_fifo_tb;

logic         w_clk ;
logic         w_req ;
logic [7:0]   w_data;
      
logic         r_clk; 
logic         rst_n; 
logic         r_req; 

logic [7:0]   r_data;
logic         w_full;
logic         r_empty;

always #2 w_clk = ~w_clk;
always #8 r_clk = ~r_clk;

initial begin
  w_req = 0;
  w_data = 0;
  r_req = 0;
  w_clk = 0;
  r_clk = 0;
  rst_n = 0;
  #30; 
  rst_n = 1;
  #20;
  r_req = 1;
  w_req = 1;
  forever begin
    @( posedge w_clk )
    if( !w_full )
    w_data = w_data + 1'b1;
  end     
end

asyn_fifo #(
  .DEPTH   (256),
  .WIDTH_A (8  ),  
  .WIDTH_D (8 ) 
)
asyn_fifo_inst(             
     .w_clk  ( w_clk   ),   
     .rst_n  ( rst_n   ),   
     .w_req  ( w_req   ),   
     .w_data ( w_data  ),  
                      
     .r_clk  ( r_clk   ),   
     .r_req  ( r_req   ),  
                      
     .r_data ( r_data  ),  
                      
     .w_full ( w_full  ), 
     .r_empty( r_empty )                                       
);   

endmodule                         

典型的跨时钟域会出现的问题

metastability: 亚稳态问题;

  1. 什么是亚稳态? 电路中不所期望的状态,比如说触发器只有01两种状态,但是在特定情况下会盘旋在两者之间很长时间(无法确定是0还是1),并且还会传播; sta只能检测同步电路,异步电路得使用spyglass做cdc检测;
  2. 什么情况会造成亚稳态? 建立保持时间违例的时候; alt 如图所示,当tx信号建立保持时间违例的时候,rx会出现介于0和1之间的不定态,几个周期后才趋于稳定; alt

虚线左右是两个时钟域;

  1. 如何解决单bit的跨时钟域问题? 答:在当前时钟域两级同步;在接受的时钟域打两拍;

另:功能仿真(行为级仿真)无法看到亚稳态效应,只有后仿才能看到;

reconvergence 如何解决多bit的跨时钟域问题

alt

  1. 如果依旧使用单bit策略,消耗的资源太多;这里我们使用交互信号en。

当en == 1 输入值更新。

当en == 0 保持原值不变。

  1. 如果我们采用每个bit打两拍的方式,还存在一种可能;

alt

alt

简单来说就是由于电路通过逻辑电路的时间不同,导致多bit数据中间的数据到达clkb时钟域的时间不同,例如途中恰好x在clkb时钟上升沿之前,而Y在时钟上升沿之后,这就导致x领先了y一个clkb; 这样的数据势必是错误的; 解决方法:使用gray码编码,这样同时只有一位数据位发生变化,就不会出现这种情况;

data hold问题:快慢时钟问题

alt

如果源时钟域时钟快,目的时钟域慢(x倍),就可能出现这种情况,即原时钟持续一个周期的信号,等不到目的时钟的上升沿就拉平了,导致检测不到;这样怎么解决? alt

也就是说拓宽源数据宽度x倍;图中是两倍,也就是

D2 = D | D1;

问题:如果目的时钟是原时钟的四倍,且传输的是低电平信号,电路怎么改? 答: 或门变与门,即有0为0,且与门改为四输入与门;

fifo handshake问题

alt

也就是需要跨时钟域传输大量数据的时候,不可能每个都打两拍,我们可以规定一个交互信号来帮助; 比如握手信号中的req和ack;异步fifo中的地址;也即是上边说过的;

reset syn问题

异步复位时,进行异步复位,同步释放;优点;不会出现时钟违例;

alt alt

输出复位信号的时候打两拍;

注意事项: sta无法解决跨时钟域问题,修复时钟树也不行;

上一个时钟域的输出最后必须打一拍送入下个时钟域,不能直接组合逻辑输出给下个时钟域;

fpga知识层次结构

层次定义: 1.仿真层:在软件上模拟真实环境,产生模拟输入,检测设计的正确性。 2.器件层; 以d触发器为核心,满足d触发器建立保持时间的各种理论和规则; 3.模块层 围绕“模块”的功能,在模块内实现多种多样的功能; 4.架构层; 使用“模块层”的模块,通过互联,相互配合等,实现FPGA的整体功能; 5.系统层 根据fpga,arm,dsp,上位机等器件的优势和劣势,将产品分配到不同器件实现,以及确认器件间的配合方式; 6.功能层 以人可理解的语言,定义出该产品的功能;

数字集成电路静态时序分析

sta核心就是确定每个触发器的建立时间和保持时间不违例; sta是对于同步电路而言的,不能出现时钟违例,但是对于异步电路,还是有些专门的区别的; 问题:触发器-逻辑电路-触发器模型? 保持时间出现违例会出现什么结果;

tcl语言学习

tcl解释器运用规则把命令分成一个个独立的单词,同时进行必要的置换; 置换分为三类:变量值换$,命令置换[]斜杠置换;

tcl语言控制流:

if: alt 注意:{要写在上一行;

foreach循环指令;

alt

循环控制指令break:

alt

循环控制指令:continue

alt

循环控制指令:while

alt

循环指令for

alt

过程函数:

proc: alt 全局变量和局部变量 alt

正则匹配:

  1. 正则表达式是一种特数的字符串模式,用来去匹配符合规则的字符串;
  2. 正则表达式的 \w ,用来匹配一个字母、数字、下划线;
  3. 正则表达式的\d 用来匹配一个数字,如字符串abc123,应该是\w\w\w\d\d\d; 简化:
    • 零次或多次匹配;
    • 一次或多次匹配;
  • ?零次或者一次匹配; 则可以表示为 \w+\d+ or \w*\d*
  • 锚位: ^符串开头,$字符串结尾 ^\d\d\d or \d\d\d$;
  • 空格\s; 123空格abc空格123 == 表达式:\d+\s\w+\s\d+
  • 任意字符:点. xxx空格xxx空格xxx == .+\s.+\s.+

正则匹配指令:regexp

alt 例题: alt alt 简而言之就是把想要捕获的位置前后都用表示符表示出来,再将捕获的位置用括号圈起来; tcl文本处理; 三大类:open gets close alt 举例如下: alt

alt 最后一个小例题; alt

tcl学习与应用: 第一部分: tcl在eda工具中的扩展和应用,基于synopsys工具的扩展tcl语言; (手册:Using Tcl With Synopsys Tools) 第二部分:使用tcl控制eda工具流程; 第一部分:(以下都是在dc的脚本模式下输入的) alt alt alt

alt alt

alt

alt alt alt

alt

alt

alt alt

第二部分:使用tcl语言来控制流程 alt

alt

alt alt

alt alt alt alt

alt alt alt alt alt

alt

primetime的库文件和时序模型; 时序弧:描述两个节点之间的延时; 分为连线延时和单元延时; 连线延时:单元的输出端口和扇出网路负载的信息 单元延时:单元的输入端口到单元的输出端口延时; 单元延迟:计算:1 transition time 1 transition time 0到1,1到0; 2 logic gate delay 就是该逻辑单元从输入端口到输出端口的延时;

建立时间和保持时间; 时序路径概念:起点,终点; 起点:可以是clockpin或者input port; 终点:可以是d触发器的输入端或者是output port; 时钟域:全局异步,局部同步; pvt:工艺,电压,温度与delay; 、 标准单元库: alt alt alt

非线性延迟模型: timing arc ;两个因素transition time 和out load;

threshold spec;slew derating;

sta学习 STA环境: 如何书写sdc约束文件; 环境:有以下几个重要元素:setting up clock,IO timing ,false paths,muticycle paths. 首先;时钟沿; alt 对于一些时钟还可以指定uncertainty,包括setup hold,如果在严苛的条件下电路仍然没有问题说明电路更加稳健; 时钟 uncertainty 包括:set up /hold/clock skew/clock delay/clock jitter; alt

时钟延迟分为两种; alt 在时钟树建立之前和建立之后LATENCY有什么区别; 生成时钟: alt 注意图中二分频电路的实现; 注意MASTER时钟和generated 时钟的定义和区别; alt alt

约束input & output path

alt

alt

alt

output 也是类似的 alt

DRC:design rule check;

未完。。。