2.4 基于FPGA的UART协议实现(四)实用UART传输FPGA实现(一)

  上一节设计实现的UART只是简易的实现,没考虑诸如抖动(起始位抖动会导致数据位传输或接收错误)等问题,但是对于理解UART传输协议却很有帮助。在单片机中使用时一般串口都可以进行大量数据的传输,这得益於单片机在串口传输时会有“缓存”空间用于数据的存储,下面来看看单片机内部串口结构,如图2 43所示:
在这里插入图片描述

          图2 43 80C51串口组成示意图
  所以本节设计串口时会参考单片机内部串口结构,主要是将上一节的UART功能拆解并完善部分功能,其中缓冲部分会在后面章节介绍完FIFO(First Input First Output)后,在将最终版本的UART进行封装,但是本节的串口设计也可以单独使用,也具有实用性。

2.3.4.1 电平检测模块

  在进行实用型UART设计之前,需要先了解在FPGA中怎么进行电平检测。
  在FPGA中最常见的就是利用“阻塞”“非阻塞”赋值语句进行电平检测,在一些特定场合还需要利用延迟达到精确检测的目的。
  为了更好地理解“阻塞”“非阻塞”赋值要点,我们需要对Verilog语言中的阻塞赋值和非阻塞赋值的功能和执行时间上的差别有深入的理解。我们定义下面的两个关键字:
  RHS——方程式右手方向的表达式或变量可分别缩写成 RHS表达式或RHS变量;
  LHS ——方程式左手方向的表达式或变量可分别缩写成LHS 表达式或LHS变量。
  IEEE Verilog标准定义了有些语句有确定的执行时间,有些语句没有确定的执行时间。若有两条或两条以上的语句准备在同一时间执行,但由于语句的排列顺序不同,却产生了不同的输出结果。这就是造成Verilog模块冒险和竞争的原因。为了避免产生竞争,理解阻塞和非阻塞赋值在执行时间上的差别是至关重要的。
  

1、阻塞赋值

  阻塞赋值用等号(=)表示。为什么称这种赋值为阻塞赋值呢?因为在赋值时先计算RHS部分的值,这是赋值语句不允许任何别的Verilog语言的干扰,直到现行的赋值完成时刻,即把RHS赋值给LHS的时刻,它才允许别的赋值语句的执行。
  一般可综合的赋值操作在RHS不能设定延时(即使是0延时也不允许)。从理论上讲,它与后面的赋值语句只有概念上的先后,而无实质的延迟。若在RHS上加延迟,则在延迟时间会阻止赋值语句的执行,延迟后才进行赋值,这种赋值语句是不可综合的,在需要综合的模块设计中不可使用这种风格的代码。
所谓阻塞的概念是指在同一个always块中,其后面的赋值语句从概念上是在前一句赋值语句结束之后再开始赋值的。

2、非阻塞赋值

  非阻塞赋值用小于等于号(<=)表示。为什么称这种赋值为非阻塞赋值呢?因为在赋值开始时计算RHS表达式,赋值操作时刻结束时更新LHS。在计算非阻塞赋值的RHS表达式和更新LHS期间,其他的Verilog语句,包括其他的非阻塞赋值语句都可能计算RHS表达式和更新LHS。非阻塞赋值允许其他的Verilog语句同时进行操作。非阻塞赋值可以看作两个步骤的过程:
  (1)在赋值开始时,计算非阻塞赋值RHS表达式;
  (2)在赋值结束时,更新非阻塞赋值LHS表达式。
  非阻塞赋值操作只能用于对寄存器类型变量进行赋值,因此只能用在“initial”块和“always”块等过程块中,而非阻塞赋值不允许用于连续赋值。

*重点:

1)时序电路建模时,用非阻塞赋值;
2)锁存器电路建模时,用非阻塞赋值;
3)用always块建立组合逻辑模型时,用阻塞赋值;
4)在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值;
5)在同一个alway块中,不要即用非阻塞又用阻塞赋值;
6)不要在一个以上的always块中为同一个变量赋值;
7)用$strobe系统任务来显示用非阻塞赋值的变量值;
8)在赋值时不要使用#0延迟。*
9)在描述组合逻辑的always块中用阻塞赋值,则综合成组合逻辑的电路结构;
10)在描述时序逻辑的always块中用非阻塞赋值,则综合成时序逻辑的电路结构。
  图2 44中是最基本的门电路设计的上升沿、下降沿捕获电路。由图可知,Trigger作为外部触发信号的输入,通过FPGA内部的clk与rst_n全局时钟信号,同步寄存输出。上升沿与下降沿的边沿捕获信号与当前的输入信号(Trigger)、上一时刻的寄存信号(Trigger_r)有关,主要关系如表2 9所示。
在这里插入图片描述

          表2 9 上升沿、下降沿边沿检测条件

在这里插入图片描述

  边沿检测的实现很好理解,当上一时刻为低电平,而当前时刻为高电平时,显而易见这是外部信号的上升沿;反之,当上一时刻为高电平,而当前时刻为低电平时,为外部信号的下降沿。

  与上述原理图中使用了2输入与门,通过反相器的辅助,作为2个时钟信号的对比。可以采用逆向思维进行推断;当pos_edge为高,即捕获到上升沿时,2输入与门输入了2个“1”,由反相器可知D触发器寄存输出后的信号为0,而当前信号为1。结果分析与设计的一样。neg_edge的设计也是如此。

  外部输入的信号有效时,会保持一段时间的高电平,但FPGA不能通过判断高电平使能信号去进行逻辑分析。由于在FPGA中不便于处理类似的触发信号(除非外部输入信号作为全局时钟使用),所以通过边沿采样技术实现上升沿时刻的使能信号的捕捉,进而实现外部信号的上升沿触发功能。

  一级的D触发器寄存在比较时,前一时刻的信号已经同步到同一时钟域,而当前时刻直接从外部输入,与FPGA整体逻辑电路不再同一时钟域。但希望我们的设计能够全部通过同步电路设计,以提高系统的可靠性,因此可以采用2级D触发器作为信号的寄存,同时比较第一级与第二级D触发器输出的信号,来检测外部输入信号的上升沿或下降沿。根据原理图设计的电路如图2 45所示。
在这里插入图片描述

      图2 45 二级寄存器实现的上升沿、下降沿捕获电路
  对于电平检测模块要实现的电路如下:
在这里插入图片描述
              图2 46 电平检测模块建模图
在这里插入图片描述
              代码2 17 detect_module.v

1.	module detect_module  
2.	(  
3.	CLK, RSTn, Pin_In, H2L_Sig, L2H_Sig  
4.	);  
5.	  
6.	input CLK;  
7.	input RSTn;  
8.	input Pin_In;  
9.	output H2L_Sig;  
10.	output L2H_Sig;  
11.	/**********************************/  
12.	// 开发板使用的晶振为 50MHz, 50M*0.0001-1=4_999  
13.	parameter T100US = 11'd4999;  
14.	/**********************************/  
15.	reg [10:0]Count1;  
16.	reg isEn;  
17.	always @ ( posedge CLK or negedge RSTn )  
18.	if( !RSTn )  
19.	    begin  
20.	        Count1 <= 11'd0;  
21.	        isEn <= 1'b0;  
22.	    end  
23.	else if( Count1 == T100US )  
24.	        isEn <= 1'b1;  
25.	else  
26.	        Count1 <= Count1 + 1'b1;  
27.	/********************************************/  
28.	reg H2L_F1;  
29.	reg H2L_F2;  
30.	reg L2H_F1;  
31.	reg L2H_F2;  
32.	always @ ( posedge CLK or negedge RSTn )  
33.	if( !RSTn )  
34.	    begin  
35.	        H2L_F1 <= 1'b1;  
36.	        H2L_F2 <= 1'b1;  
37.	        L2H_F1 <= 1'b0;  
38.	        L2H_F2 <= 1'b0;  
39.	    end  
40.	else  
41.	    begin  
42.	        H2L_F1 <= Pin_In;  
43.	        H2L_F2 <= H2L_F1;  
44.	        L2H_F1 <= Pin_In;  
45.	        L2H_F2 <= L2H_F1;  
46.	end  
47.	/***********************************/  
48.	assign H2L_Sig = isEn ? ( H2L_F2 & !H2L_F1 ) : 1'b0;  
49.	assign L2H_Sig = isEn ? ( !L2H_F2 & L2H_F1 ) : 1'b0;  
50.	/***********************************/  
51.	endmodule  

  第 13 行定义了 100us 的常量,而第 17~26 行是用于延迟 100us。因为电平检测模块是非常敏感,在复位的一瞬间,电平容易处于不稳定的状态,我们需要延迟 100us。 isEn = 1 寄存器是表示 100us 的延迟已经完成( 26 行)。
  第 28~31 行,声明了四个寄存器。 H2L_F1, H2L_F2,是针对检测电平由高变低。相反的 L2H_F1, L2H_F2,则是针对检测电平由低变高。在 35~38 行,对各个寄存器初始化了,由于 H2L_Fx 是为了检测由高变低的电平,所以初始化为逻辑 1。 L2H_Fx 是为了检测由低变高的电平,初值被设置为逻辑 0。
                  代码2 18

1.	//初始化  
2.	H2L_F1 <= 1'b1;  
3.	H2L_F2 <= 1'b1;  
4.	//每一个时间的操作  
5.	H2L_F1 <= Pin_In;  
6.	H2L_F2 <= H2L_F1;  
7.	//每一个时间的布尔运算输出  
8.	Pin_Out = H2L_F2 & !H2L_F1  

  上面代码是用来检测电平由高变低。 H2L_F1 和 H2L_F2 的初值都是逻辑 1。假设第一个时间 Pin_In 为低电平, H2L_F1 就会被赋值位逻辑 0,而 H2L_F1 则是被赋值为 H2L_F1上一个时间的值(也就是 H2L_F1 的初值)。
  我们知道在第一个时间, H2L_F1 为逻辑 0, H2L_F2 位逻辑 1。由于对 H2L_F1 的取反操作, H2L_F1 与 H2L_F2“求与”运算,所以这个表达式的输出是逻辑 1。
  再假设第二个时间 Pin_In 保持为低电平, H2L_F1 同样会被赋值为逻辑 0, 而 H2L_F2则是被赋值为 H2L_F1 上一个时间的值,亦即逻辑 0 (第一个时间的值)。再经过布尔表达式的运算, 在第二个时间, H2L_F1 是逻辑 0, H2L_F2 是逻辑 0, 所以输出的结果是逻辑 0.
                表2 10 逻辑说明
在这里插入图片描述
  第 41~56 行,正是执行如上的操作,无论是检测电平由高变低或者由低变高,思路基本都是一样。而最后的 48~49 行,是关于“电平检测”的布尔表达式。但是有一点不同的是,Pin_Out 的输出,是发生在 100us 之后,因为 100us 之前被 isEn 寄存器所限制(原因之前已经说了)。换一句话说,电平检测模块的有效是发生在 100us 的延迟之后。

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章