Wherelse Blog

FPGA秋招学习笔记整理(4)

2021-11-21 · 34 min read
FPGA 笔记

FPGA autumn recruit study notes (4): Common designs, calculations and problems
FPGA秋招学习笔记(4):常用设计、计算与问题

FPGA中的常用存储

储存器件 特点
单端口RAM 只有一组控制信号线、地址线和数据线,不能同时读写,某时刻只能在控制信号作用下作为数据输入或输出的一种
双端口RAM 有两组独立的控制信号线、地址线和数据线,两组之间互不影响,允许两个独立的系统同时对其进行随机性的访问。即共享式多端口存储器,可以同时读写(两个端口都可以进行读写操作)注意:双端口RAM同时对同一地址进行读写时,会出现仲裁
伪双端口RAM 其他结构和双端口RAM类似,但是一个端口只读,一个端口只写
FIFO 先进先出数据缓冲器,也是一个端口只读,另一个端口只写。

FIFO与RAM的区别
FIFO:数据储存形式为先进先出,没有地址线,对储存单元进行寻址操作
RAM:每个端口都有地址线,可以对储存单元寻址
就是FIFO只能以特定的顺序去存储数据,不能通过寻址的方式去访问数据中的值,而RAM可以。

FIFO一般用于不同时钟域之间的数据传输,对于不同宽度的数据接口也可以用FIFO进行转换。

FIFO设计

FIFO设计在时钟沿来临时同时发生读写操作。FIFO设计的难点在于怎样判断FIFO的空/满状态。为了保证数据正确的写入或读出,而不发生益处或读空的状态出现,必须保证FIFO在满的情况下,不 能进行写操作。在空的状态下不能进行读操作。

读写指针的工作原理
写指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)。
读指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)
FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。

同步FIFO

同步FIFO是指读时钟和写时钟为同一个时钟。以深度为16的FIFO为例,将读写指针位宽设置为5位,但是取低四位作为读取RAM的地址

当读写指针相等时,可以得出FIFO为空。

当继续读取数据,读指针达到15之后,如果再继续写入数据,写指针最高位第五位会变为1,由于取其第四位作为RAM地址,会返回到第0位写入数据,直到写入到地址2。

继续读取数据,直到读指针为11100,此时读写指针低四位相等,最高位相反,可以得出FIFO为满。

同步FIFO判断空满的标志:

  • 地址完全相同,FIFO空
  • 地址最高位不同,其他均相同

异步FIFO

对于异步FIFO,需要将写指针从写时钟域同步到读时钟域,产生空信号,需要将读指针从读时钟域同步到写时钟域,产生满信号。使用两级同步器来减小跨时钟域产生亚稳态的概率。
由于需要同步多位宽的指针信号,如果指针继续使用二进制计数器,当指针从 11111 跳变到 00000 时,每以位数据都需要同步,产生亚稳态的概率会很高。可以使用格雷码代替二进制数进行跨时钟域的传输。格雷码是一个数列集合,每次相邻两数间只有一个位元改变。

//以三位二进制和格雷码为例
十进制 格雷码 二进制
-----------------------
0     000    000
1     001    001
2     011    010
3     010    011
4     110    100
5     111    101
6     101    110
7     100    111

二进制码转换成二进制格雷码,其法则是保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或。
二进制格雷码转换成二进制码,其法则是保留格雷码的最高位作为自然二进制码的最高位,而次高位自然二进制码为高位自然二进制码与次高位格雷码相异或$。

//二进制码转格雷码
module bin2gray(
 input [2:0] bin,
 output [2:0] gray
 );
reg[2:0] gray,temp;

always @(*) begin
	temp = (bin >> 1);
	gray[2] = bin[2];
	gray[1:0] = temp[1:0] ^ bin[1:0];
end    
endmodule

//格雷码转二进制码
module gray2bin(
 input [2:0] gray,
 output [2:0] bin
 );
assign  bin[2] = gray[2];
         
generate
genvar i;
	for(i=0;i<2;i=i+1) begin:g2b
		assign bin[i] = gray[i]^bin[i+1];
	end
endgenerate
endmodule

异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理.异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。

使用4位格雷码作为深度为8的FIFO的读写指针。
将格雷码转换成四位二进制数,使用二进制数低三位作为访问RAM的地址。当读写指针相等时,得出FIFO为空.

当写指针比读指针多循环RAM一周时,此时读写指针的最高位和次高位都相反,其余位相同,FIFO为满。

异步FIFO判断空满的标志:

  • 当读写指针相等时,FIFO空
  • 读写指针的最高位和次高位都相反,其他均相同

FIFO深度的计算

异步FIFO,读时钟频率和写时钟频率一般是不同的,如果读时钟频率快并且平均读取速度快于写入速度,且读写同时进行,则最小深度为1即可。当写时钟频率大于读时钟频率,写入速度快于读取速度。

 fifo depth = burst length  burst length ×XY×rclkwclk\text { fifo depth }=\text { burst length }-\text { burst length } \times \frac{X}{Y} \times \frac{r_{-} clk}{w_{-} clk}

这里wclkw_{-}clk为写时钟频率;rclkr_{-}clk为读时钟频率;写时钟周期里,每B个时钟周期会有A个数据写入FIFO;读时钟周期里,每YY个时钟周期会有XX个数据读出FIFO;burstlengthburst_{-}length为最大突发个数
一般用于写快读慢突发传输时需遵循的规则:

FIFO 深度  写入速率  读出速率 =FIFO 被填满时间 > 数据包传送时间 = 写入最大突发数据量  写入速率 \frac{FIFO \text { 深度 }}{\text { 写入速率 }-\text { 读出速率 }}=FIFO \text { 被填满时间 }>\text { 数据包传送时间 }=\frac{\text { 写入最大突发数据量 }}{\text { 写入速率 }}

如果读写同时进行且写快读慢,那么深度除以速度差就是FIFO被填满的时间,这个时间应该大于最大突发数据量除以写入速率对应的时间,即数据包传送时间,否则写入的数据会溢出造成错误。

例子:

  1. AD采样速率为50M,FPGA读取速率为40M,需要将10万个采样数据读入FPGA需要多大的FIFO?
    写入FIFO 的速率为 1/50M=0.021061/50M=0.02\cdot10^{-6},则写入所有数据的时间为 0.002s,读取FIFO 的速率为 1/40M=0.0251061/40M=0.025\cdot10^{-6},则0.002s内读出的数据为0.002/0.025106=80,0000.002/0.025\cdot10^{-6}=80,000,则FIFO的最小深度为100,00080,000=2,000100,000-80,000=2,000

  2. FIFO写入时钟为100MHz,读取时钟为80Mhz。100个时钟写入80个数据,一个时钟读取一个数据,求FIFO最小深度。

  • 读取2000个数据,FIFO最小深度为多少?
    以100M速率写入2000个数据所需时间为20000ns,以80M速率读出,20000ns能够读出的数据量为20000/1/80Mhz=160020000/1/80Mhz=1600,则FIFO最小深度为2000-1600=400.
  • 100个时钟周期写入80个数据,一个时钟周期读取一个数据,求FIFO最小深度?
    100MHz时钟写入一个数据需要的时间为:1/100Mhz=10ns1/100Mhz=10ns,80Mhz读出一个数据所需时钟为:1/80Mhz=12.5ns1/80Mhz=12.5ns ,100个时钟写入80个数据,可以理解为以下状态:

    而可能发生的数据量最大的情况则为:

    这时会以100M速度连续写入160个数。写入1600个数据需要的时间为16010ns=1600ns160\cdot10ns=1600ns,1600ns内读取的个数为:1600ns/(1/80M)=1281600ns/(1/80M)=128,则FIFO深度应不少于160128=32160-128=32

同步复位与异步复位

异步复位是指无论时钟沿是否到来,只要复位信号有效,就对系统进行复位。

always @ (posedge clk or negedge rst_n)begin
     if(!rst_n) b <= 1'b0;
     else b <= a;
end

同步复位是指只有在时钟上升沿到来时才会对复位信号进行采样,也就是只有在时钟上升沿时,复位信号才有效。

always @ (posedge clk)begin
     if(!rst_n) b <= 1'b0;
     else b <= a;
end

同步复位与异步复位的优缺点

同步复位的优点:

  • 一般能够确保电路是百分之百同步的;
  • 确保复位只发生在有效时钟沿,可以作为过滤掉毛刺的手段;

同步复位的缺点:

  • 复位信号的有效时长必须大于时钟周期,才能真正被系统识别并完成复位。同时还要考虑如:时钟偏移、组合逻辑路径延时、复位延时等因素;
  • 由于大多数的厂商目标库内的触发器都只有异步复位端口,采用同步复位的话,就会耗费较多的逻辑资源;

异步复位优点:

  • 异步复位信号识别方便,而且可以很方便的使用全局复位;
  • 大多数的厂商目标库内的触发器都有异步复位端口,可以节约逻辑资源;

异步复位缺点:

  • 复位信号容易受到毛刺的影响;
  • 复位结束时刻恰在亚稳态窗口内时,无法决定现在的复位状态是1还是0,会导致亚稳态;

异步复位同步释放

使用异步复位同步释放就可以消除上述缺点。所谓异步复位,同步释放就是在复位信号到来的时候不受时钟信号的同步,而是在复位信号释放的时候受到时钟信号的同步。

//example code 
module areset_srelease(
    input       rstn,  //异步复位信号
    input       clk,   //时钟
    input       din,   //输入数据
    output reg  dout   //输出数据
    );
     
    reg   rstn_r1, rstn_r2;
    always @ (posedge clk or negedge rstn) begin
        if (!rstn) begin
            rstn_r1 <= 1'b0;     //异步复位
            rstn_r2 <= 1'b0;  
        end
        else begin
            rstn_r1 <= 1'b1;     //同步释放
            rstn_r2 <= rstn_r1;  //同步打拍,时序差可以多延迟几拍
        end
    end
     
    //使用 rstn_r2 做同步复位,复位信号可以加到敏感列表中
    always @ (posedge clk or negedge rstn_r2) begin
        if (!rstn_r2) dout <= 1'b0; //同步复位
        else          dout <= din;
    end

endmodule

建立保持时间,时钟抖动与时钟偏斜

建立时间

建立时间就是时钟触发事件来临之前,数据需要保持稳定的最小时间,以便数据能够被时钟正确的采样。

保持时间

保持时间就是时钟触发事件来临之后,数据需要保持稳定的最小时间,以便数据能够被电路准确的传输。可以通俗的理解为:时钟到来之前,数据需要提前准备好;时钟到来之后,数据还要稳定一段时间。建立时间和保持时间组成了数据稳定的窗口。

时钟偏斜(偏移)


时钟偏斜(偏移) (clock skew)是因为布线长度和负载不同,导致同一时钟上升沿到不同触发器的时间不同。

时钟抖动


时钟抖动(clock jitter)是指同一时钟,相邻周期间时间不一致的现象。这一误差来源于时钟自身(如:晶振、PLL电路的偏差),与噪声、干扰以及电源变化有关。

建立时间余量和保持时间余量



因为时钟偏斜,到达DFF2为CLK2,考虑时钟到达DFF2的时钟偏斜TskewTskew,则建立时间余量为Tsetupslack=TclkTcqTcoTsetup+TskewTsetupslack =Tclk-Tcq-Tco-Tsetup+Tskew,其中TcqTcq为DFF1的时钟端到输出延迟,为器件固定属性;TcoTco是组合逻辑电路的延时,TsetupTsetup为触发器建立时间特性。TholdThold为触发器保持时间特性。若不考虑DFF2的时钟偏斜,则建立时间裕量为Tsetupslack=TclkTcqTcoTsetupTsetupslack=Tclk-Tcq-Tco-Tsetup

考虑时钟到达DFF2的时钟偏斜TskewTskew,则保持时间余量:Tskew+Thold+Tholdslack=Tcq+TcoTskew+Thold+Tholdslack=Tcq+Tco,因此可推出Tholdslack=Tcq+TcoTskewTholdTholdslack=Tcq+Tco-Tskew-Thold,使保持时间不违例,则需Tholdslack>0Tholdslack>0。若不考虑时钟偏斜,则Tholdslack=Tcq+TcoTholdTholdslack=Tcq+Tco-Thold

FPGA在建立时间余量为0时可以获得最大时钟频率,即最小时钟周期

亚稳态与跨时钟域

在数字电路中,每一位数据不是1(高电平)就是0(低电平)。当然对于具体的电路来说,并非1(高电平)就是1V,0(低电平)就是0V。比方说对于某个器件来说,2.252.5V可以识别出来是高电平,00.25V可以识别出来是低电平,但是如果信号的电压处于0.25~2.25V之间,器件也就无法识别是高电平还是低电平(最终的结果可能是高电平也可能是低电平,无法预测),这种状态也就是亚稳态。

亚稳态的出现归根到底就是因为建立时间和保持时间不满足要求。在时钟变化的这段时间里,寄存器将对数据进行锁存。如果数据在这段时间内发生了变化,对于寄存器来说也就无法识别应该锁存哪个数据,到底是变化前的还是变化后的,寄存器的输出也将变得无法预测。因此,数据在建立时间和保持时间内必须保持稳定不变。

亚稳态无法彻底避免,只有尽可能减少它出现和传播的概率,一般有以下几种方法:

  1. 使用同步器;
  2. 在满足要求的情况下,降低时钟频率;
  3. 采用反应更快的触发器;
  4. 列表项减少使用或者避免使用信号翻转时间很长的输入信号。

跨时钟域处理方法

不同的时钟域之间信号通信时需要进行同步处理,这样可以防止新时钟域中第一级触发器的亚稳态信号对下级逻辑造成影响。
信号跨时钟域同步:

  1. 当单个信号跨时钟域时,可以采用两级触发器来同步
  2. 数据或地址总线跨时钟域时可以采用异步FIFO来实现时钟同步;
  3. 第三种方法就是采用握手信号。

常见分频设计

偶数分频

偶数分频实现比较简单,假设为 N(偶数)分频,只需计数到 N/2-1,然后时钟翻转、计数器清零,如此循环就可以得到 N(偶)分频。

//example code 
module divide_clock
(
input clk , // system clock 50Mhz on board
input rst_n, // system rst, low active
output reg out_clk // output signal
);

parameter N = 4 ;
reg [N/2-1:0] cnt ;

always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
	cnt <= 0;
	out_clk <= 0;
end
else begin
	if(cnt==N/2-1) begin
			out_clk <= ~out_clk;
			cnt <= 0;
	end
	else
		cnt <= cnt + 1;
	end
end

endmodule

奇数分配

实现奇数分频原理是分别用上升沿计数到 N/2+1, 分频后输出时钟进行翻转,再计数到 N/2 输出 out_clk1,再用下降沿计数到 N/2+1, 分频后输出时钟再进行翻转,再计数到 N/2 输出 out_clk2,将 out_clk1 和 out_clk2相或即可。 我们可以通过修改 N 的值和计数器的位宽来实现其他奇数分频。

//example code
module divide_clock
(
input clk , // system clock 50Mhz on board
input rst_n, // system rst, low active
output out_clk // output signal
);

parameter N = 3 ;

 reg [N/2 :0] cnt_1 ;
 reg [N/2 :0] cnt_2 ;

 reg out_clk1 ;
 reg out_clk2 ;

 
always @(posedge clk or negedge rst_n) begin //上升沿输出 out_clk1
if(!rst_n) begin
	out_clk1 <= 0;
 	cnt_1 <= 1; //这里计数器从 1 开始
end
else begin
	if(out_clk1 == 0) begin
		if(cnt_1 == N/2+1) begin
		out_clk1 <= ~out_clk1;
	cnt_1 <= 1;
	end
	else
 	cnt_1 <= cnt_1+1;
	end
	else if(cnt_1 == N/2) begin
		out_clk1 <= ~out_clk1;
		cnt_1 <= 1;
	end
	else
		cnt_1 <= cnt_1+1;
	end
end

always @(negedge clk or negedge rst_n) begin //下降沿输出 out_clk2
if(!rst_n) begin
	out_clk2 <= 0;
	cnt_2 <= 1; //这里计数器从 1 开始
end
	else begin
		if(out_clk2 == 0) begin
			if(cnt_2 == N/2+1) begin
				out_clk2 <= ~out_clk2;
				cnt_2 <= 1;
			end
		else
			cnt_2 <= cnt_2+1;
		end
		else if(cnt_2 == N/2) begin
			out_clk2 <= ~out_clk2;
			cnt_2 <= 1;
		end
		else
			cnt_2 <= cnt_2+1;
		end
end

assign out_clk = out_clk1 | out_clk2;
endmodule

小数分频

假设实现N.5分频(N为整数),在输入时钟上升沿下计数到 2N,同时在 0 和 N+1 处时钟信号1翻转;在输入时钟下降沿下计数到 2N,同时在 0 和 N 处时钟信号2翻转;最后信号1和信号2进行相与。

`timescale 1ns/1ns
//1.5分频
module fraction_div(
    input  i_clk,
    input  i_rst_n,
    output o_div_clk
);

reg [2:0] s_cnt_p;
reg [2:0] s_cnt_n;
reg       s_clk_p;
reg       s_clk_n;	
always @(posedge i_clk or negedge i_rst_n)
    if(!i_rst_n)
	     s_cnt_p <= 3'd0;
	 else if(s_cnt_p == 3'd2)
	     s_cnt_p <= 3'd0;
    else 
	     s_cnt_p <= s_cnt_p + 1'b1;

always @(negedge i_clk or negedge i_rst_n)
    if(!i_rst_n)
	     s_cnt_n <= 3'd0;
	 else if(s_cnt_n == 3'd2)
	     s_cnt_n <= 3'd0;
    else 
	     s_cnt_n <= s_cnt_n + 1'b1;
	 
always @(posedge i_clk or negedge i_rst_n)
    if(!i_rst_n)
	     s_clk_p <= 1'b0;
	 else if(s_cnt_p == 3'd0 || s_cnt_p == 3'd2)
		  s_clk_p <= ~s_clk_p;
	 else
        s_clk_p <= s_clk_p;
		
always @(negedge i_clk or negedge i_rst_n)
    if(!i_rst_n)
	     s_clk_n <= 1'b1;
	 else if(s_cnt_n == 3'd0 || s_cnt_n == 3'd1)
		  s_clk_n <= ~s_clk_n;
	 else
        s_clk_n <= s_clk_n;

assign o_div_clk = s_clk_p & s_clk_n;		  
		  
endmodule 

状态机

第一类,输出只和状态有关而与输入无关,则称为Moore状态机;
第二类,输出不仅和状态有关而且和输入有关系,则称为Mealy状态机。

Mealy型:输出信号不仅取决于当前状态,还取决于输入;
Moore型:输出信号只取决于当前状态;

实现相同的功能时,Mealy型比Moore型能节省一个状态(大部分情况下能够节省一个触发器资源,其余情况下使用的资源相同,视状态数和状态编码方式决定),Mealy型比Moore型输出超前一个时钟周期。
例子:设计一个自动售卖饮料机,设饮料售价为2.5元,可使用5角和一元硬币,有找零功能。
moore型设计
状态图:

stateDiagram
    IDEL/S0 --> S1/0.5元:0.5元
    S1/0.5元 --> S2/1元:0.5元
    IDEL/S0 --> S2/1元:1元
    S1/0.5元 --> S3/1.5元:1元
    S2/1元 --> S3/1.5元:0.5元
    S3/1.5元--> S4/2元:0.5元
    S2/1元 --> S4/2元:1元
    S3/1.5元--> S5/2.5元:1元
    S4/2元--> S5/2.5元:0.5元
    S4/2元--> S6/3元:1元
    S5/2.5元-->IDEL/S0
    S6/3元-->IDEL/S0
    S5/2.5元-->不找零,出货
    S6/3元-->找零,出货

状态表:

状态 含义 编码
S0 IDLE复位状态 3'b000
S1 投入0.5元 3'b001
S2 投入1元 3'b010
S3 投入1.5元 3'b011
S4 投入2元 3'b100
S5 投入2.5元 3'b101
S6 投入3元 3'b110
//example code
module drink_status_moore(clk,reset,half.one,out,cout);
  input clk;
  input reset;
  input half;
  input one;
  output out;
  output cout;
   
  parameter [2:0] s0 = 3'b000,       //3'd0,参数声明
                  s1 = 3'b001,       //3'd1
                  s2 = 3'b010,       //3'd2
                  s3 = 3'b011,       //3'd3
                  s4 = 3'b100,       //3'd4
                  s5 = 3'b101,       //3'd5
                  s6 = 3'b110;       //3'd6

  reg [2:0] curr_state;        //内部信号声明
  reg [2:0] next_state;

//first segement: state transfer---描述状态寄存器、时序逻辑
always @(posedge clk, negedge reset) begin
  if(!reset)
    curr_state <= s0;
  else
    curr_state <= next_state;
end

//second segment: transfer condition--描述次态逻辑、组合逻辑
always @(curr_state,one,half)begin     //等价 always@(*)
  case(curr_state)
    s0:begin
       if(half)     next_state = s1;
       else if(one) next_state = s2;
       else         next_state = s0;
       end
    s1:begin
       if(half)     next_state = s2;
       else if(one) next_state = s3;
       else         next_state = s1;
       end
    s2:begin
       if(half)     next_state = s3;
       else if(one) next_state = s4;
       else         next_state = s2;
       end
    s3:begin
       if(half)     next_state = s4;
       else if(one) next_state = s5;
       else         next_state = s3;
       end
    s4:begin
       if(half)     next_state = s5;
       else if(one) next_state = s6;
       else         next_state = s4;
       end
    s5:begin
                    next_state = s0;
       end
    s6:begin
                    next_state = s0;
       end
    default:        next_state = s0;
 endcase
end

//third segement: state output---状态输出(描述输出逻辑)
assign out = ((curr_state == s5) || (curr_state == s6))?1:0;
assign cout = (curr_state == s6)?1:0;               //assign可简化输出

endmodule

melay型设计
状态图:

stateDiagram
    IDEL/S0 --> S1/0.5元:0.5元
    S1/0.5元 --> S2/1元:0.5元
    IDEL/S0 --> S2/1元:1元
    S1/0.5元 --> S3/1.5元:1元
    S2/1元 --> S3/1.5元:0.5元
    S3/1.5元--> S4/2元:0.5元
    S2/1元 --> S4/2元:1元
		S3/1.5元 -->IDEL/S0:1元
		S3/1.5元 -->不找零,出货
		S4/2元-->IDEL/S0:0.5元
		S4/2元-->IDEL/S0:1元
		S4/2元-->不找零,出货
		S4/2元-->找零,出货

状态表:

状态 含义 编码
S0 IDLE复位状态 3'b000
S1 投入0.5元 3'b001
S2 投入1元 3'b010
S3 投入1.5元 3'b011
S4 投入2元 3'b100
module drink_status_mealy(
  input clk,               //verilog-2001
  input reset,
  input half,
  input one,
  output out,
  output cout);
   
  parameter [2:0] s0 = 3'b000,       //3'd0
                  s1 = 3'b001,       //3'd1
                  s2 = 3'b010,       //3'd2
                  s3 = 3'b011,       //3'd3
                  s4 = 3'b100,       //3'd4
  reg [2:0] curr_state;
  reg [2:0] next_state;

//first segement: state transfer---时序逻辑、描述状态寄存器
always @(posedge clk, negedge reset) begin
  if(!reset)
    curr_state <= s0;
  else
    curr_state <= next_state;
end

//second segment: transfer condition---组合逻辑、描述次态逻辑
always @(*)begin     //等价 always @(curr_state,one,half)
  case(curr_state)
    s0:begin
       if(half)     next_state = s1;
       else if(one) next_state = s2;
       else         next_state = s0;
       end
    s1:begin
       if(half)     next_state = s2;
       else if(one) next_state = s3;
       else         next_state = s1;
       end
    s2:begin
       if(half)     next_state = s3;
       else if(one) next_state = s4;
       else         next_state = s2;
       end
    s3:begin
       if(half)     next_state = s4;
       else if(one) next_state = s0;
       else         next_state = s3;
       end
    s4:begin
       if(half)     next_state = s0;
       else if(one) next_state = s0;
       else         next_state = s4;
       end       
    default:        next_state = s0;
 endcase
end

//third segement: state output---状态输出(描述输出逻辑)
assign out = ((curr_state == s4) & (half|one))?1:((curr_state == s3) & (one))?1:0;    //等价于if--else if--else,即条件操作符的嵌套使用
assign cout = ((curr_state == s4) & (one))?1:0;

endmodule

面积与速度优化

FPGA速度优化方法

  • 流水线设计
  • 寄存器配平
  • 关键路径优化(减少关键路径上的组合逻辑延迟)
  • 迟滞信号后移(将延时较大的信号放到后面,缩短信号的路径长度)
  • 消除代码优先级
  • 并行化

FPGA面积优化方法

  • 串行化
  • 资源共享
  • 逻辑优化

时序约束的概念和基本策略

时序约束主要包括周期约束,偏移约束,静态时序路径约束三种。
通过附加时序约束可以综合布线工具调整映射和布局布线,使设计达到时序要求。
附加时序约束的一般策略是先附加全局约束,然后对快速和慢速例外路径附加专门约束。附加全局约束时,首先定义设计的所有时钟,对各时钟域内的同步元件进行分组,对分组附加周期约束,然后对FPGA/CPLD输入输出PAD附加偏移约束、对全组合逻辑的PAD TO PAD路径附加约束。附加专门约束时,首先约束分组之间的路径,然后约束快、慢速例外路径和多周期路径,以及其他特殊路径。

附加约束的作用

  1. 提高设计的工作频率(减少了逻辑和布线延时);
  2. 获得正确的时序分析报告;(静态时序分析工具以约束作为判断时序是否满足设计要求的标准,因此要求设计者正确输入约束,以便静态时序分析工具可以正确的输出时序报告)
  3. 指定FPGA/CPLD的电气标准和引脚位置。

常用协议

SPI

SPI是一个同步的、基于主从的全双工接口。来自主机或从机的数据在上升或下降的时钟边缘同步。主机和从机都可以同时传输数据。SPI接口可以是3线制,也可以是4线制。

4 线 SPI 设备有四个信号:

  • 时钟(SPI CLK、SCLK)
  • 片选 (CS)
  • 主出,从入(MOSI)(数据线)
  • 主进从出 (MISO)(数据线)

要开始 SPI 通信,主机必须发送时钟信号并通过启用 CS 信号来选择从机。

在 SPI 中,主机可以选择时钟极性和时钟相位。 CPOL 位设置空闲状态期间时钟信号的极性。空闲状态定义为在传输开始时 CS 为高并转换为低以及 CS 为低并在传输结束时转换为高的时段。 CPHA 位选择时钟相位。根据 CPHA 位,时钟上升沿或下降沿用于采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据 CPOL 和 CPHA 位选择,有四种 SPI 模式可用。

SPI Mode CPOL CPHA 时钟极性默认状态 数据采样位置
0 0 0 Logic low 数据在上升沿采样并在下降沿移出
1 0 1 Logic low 数据在下降沿采样并在上升沿移出
2 1 1 Logic high 数据在下降沿采样并在上升沿移出
3 1 0 Logic high 数据在下降沿采样并在上升沿移出

IIC


I2C 总线仅使用两条双向线,串行数据线 (SDA) 和串行时钟线 (SCL)。 I2C 兼容设备通过集电极开路或漏极开路引脚连接到总线,将线路拉低。当没有数据传输时,I2C 总线处于 HIGH 状态空闲;总线被被动拉高。
IIC支持多设备并使用地址进行设备寻址,各从机间的地址不能重复。IIC通过拉低并释放高来切换线路来进行传输。位在时钟下降沿计时。标准数据传输速率为 100kbits/s,而快速模式传输速率为 400kbits/s。

开始信号,SDA 线从高电平状态转换为低电平状态,而 SCL 为高电平。
停止信号,SDA 线从低电平转换为高电平状态,而 SCL 为高电平。

在传输期间可以重复开始信号,而无需停止,称为重复启动,用于更改数据传输方向、重复传输尝试、同步多个 IC,控制串行存储器。

进行传输时,从机地址以 8 位字节格式发送,MSB 在前,但最后一位表示事务是读取还是写入从机。实际上,高 7 位构成从地址,而第 8 位用作 READ/WRITE# 命令位。因此,有一个包含 128 个唯一地址的地址空间,最多可用于寻址 128 个从站。

确认和不确认位 (ACK/NACK),ACK 用于表示一个字节(地址或数据)已成功传输和接收,并且传输可以继续进行到下一个字节传输、停止条件或重复开始。接收方通常使用 NACK 来指示数据传输中是否发生了错误。这用于向传输设备发送信号以立即终止传输或通过发送重复开始进行另一次尝试。

写入流程

主机寻址并将数据写入具有 7 位地址的从接收器。发送的第一位是启动通信的 START 位。地址字节跟随在随后的时钟脉冲之后。此时,总线上的所有 SLAVE 设备都在侦听它们的设备地址,该地址构成地址字节的前 7 位。找到地址匹配的 SLAVE 设备继续侦听最后一位(READ/WRITE# 位)以了解主设备是要从从设备读取还是写入。所有其他从设备通过发送 NACK 来忽略进一步的通信。
从机响应识别其地址和写入命令,被寻址的设备通过向主设备发送确认位 (ACK) 作为反馈来响应总线上存在具有正确地址的从设备并等待进一步通信。然后主机通过以字节格式发送数据来继续进行数据传输。如果 主机正在写入从设备中的特定寄存器,则它会在发送数据之前写入相应的命令字节。在每个字节传输后,从机通过发送 ACK 进行响应。一旦 主机完成所有数据的传输,它就会通过发送一个 STOP 条件来终止传输。

简单来说:写时序:写地址->ack->写寄存器->ack->写数据->ack->stop

读取流程

主机从从机中读取数据。传输再次由主机以 START信号启动,之后地址在后续时钟脉冲上传输。寻址设备通过读取 READ/WRITE 位继续侦听传输,并以确认位进行响应。一旦 从机发送确认信号,它就会接管 SDA 并向主机发送数据。为响应传输的每个字节,主机发送一个确认位。当主机不再想要接收数据时,它会在它希望接收的最后一个字节之后以 NACK 响应,然后恢复对总线的控制并发送 STOP 信号结束传输。

简单来说:写地址->ack->写寄存器->ack->写地址->restart->读数据->nack->stop

UART

UART,通用异步接收器-发送器,是最常用的设备到设备通信协议之一。UART 的速度是可以配置的,而且它不需要时钟信号来同步发送设备和接收设备。
对于 UART 和大多数串行通信,需要在发送和接收设备上设置相同的波特率。波特率是信息传输到通信信道的速率。在串行端口配置中,设置的波特率将作为每秒传输的最大位数。
在 UART 中,传输模式是数据包的形式。。数据包由起始位、数据帧、奇偶校验位和停止位组成。

起始位
UART 数据传输线在不传输数据时通常保持高电平。为了开始数据传输,发送 UART 将传输线从高电平拉至低电平一个时钟周期。当接收 UART 检测到高低电压转换时,它开始以波特率的频率读取数据帧中的位。

数据帧
数据帧包含正在传输的实际数据。如果使用奇偶校验位,它可以是5位到8位长度。如果未使用奇偶校验位,则数据帧的长度可以是9位。在大多数情况下,数据首先以最低有效位发送。

校验帧
奇偶性描述了一个数的偶数或奇数。奇偶校验位是接收 UART 判断传输过程中是否有任何数据发生变化的一种方式。数据位可能受到电磁辐射、不匹配的波特率或长距离数据传输的影响而改变。
接收 数据帧后,计算值为 1 的位数,并检查总数是偶数还是奇数。如果奇偶校验位为 0(偶校验),则数据帧中的 1 或逻辑高位总计应为偶数。如果奇偶校验位为 1(奇校验),则数据帧中的 1 位或逻辑高位总计应为奇数。
当奇偶校验位与数据匹配时,UART 就知道传输没有错误。但如果奇偶校验位为 0,且总数为奇数,或者奇偶校验位为 1,且总数为偶数,则 UART 知道数据帧中的位发生了变化。

停止位
为了发出数据包结束的信号,发送 UART 将数据传输线从低电压驱动到高电压,持续1-2位。

https://blog.csdn.net/qq_40230112/article/details/109766740
https://zhuanlan.zhihu.com/p/168744814
https://zhuanlan.zhihu.com/p/187458456
https://zhuanlan.zhihu.com/p/370935420
https://blog.csdn.net/jingfengvae/article/details/51691124
https://zhuanlan.zhihu.com/p/119302165
https://blog.csdn.net/bleauchat/article/details/97028410
https://www.runoob.com/w3cnote/verilog2-setup-hold-time.html
https://www.cnblogs.com/shadow-fish/p/13472129.html
https://zhuanlan.zhihu.com/p/80698138
https://zhuanlan.zhihu.com/p/363743703
https://blog.csdn.net/weixin_46022434/article/details/105166407
https://www.analog.com/en/analog-dialogue/articles/introduction-to-spi-interface.html
https://www.analog.com/en/technical-articles/i2c-primer-what-is-i2c-part-1.html
https://www.analog.com/en/analog-dialogue/articles/uart-a-hardware-communication-protocol.html

若需要md版本或者pdf版本,请邮件联系我邮箱: (emh5aG9uZ3lhbmdAb3V0bG9vay5jb20=)(base64),或者于下方留言。

// bilibili 视频支持