FPGA设计中RS232串口的Verilog实现(RX控制器)

 

一.RX控制器(接收数据)的Verilog实现

对串口来讲,TX和RX方向数据传输的格式都是一样的。毕竟对于参与传输的双方来讲,一方是TX,对应的一方就是RX。

对于TX方向,串口控制器要根据波特率产生TX方向的时钟(TX_CLK),其时钟的相位是没有要求的,由设计本身决定。而在RX方向,时钟(RX_CLK)的生成则有所不同。首先时钟频率也是根据波特率产生,这对于TX和RX都是一样的,但RX_CLK的相位则需要与TX_CLK相近,最佳的实现是RX_CLK与TX_CLK同步。但对于RX(接收数据的一方)来讲,是没有办法预测TX_CLK的相位的,没有办法做到RX_CLK与TX_CLK的绝对同步,不过近似的同步则可以实现。

虽然RX_CLK和TX_CLK原则上都是由波特率决定的,但是由于很多系统在由波特率计算这两个时钟的时候,误差会比较大,当系统长时间的工作的时候会带来较大的误差。所以最好的办法是在每次开始传输的时候,进行一次TX_CLK与RX_CLK的近似同步。

下面给出一种RX_CLK与TX_CLK近似同步的方法以及RX控制器的verilog实现。

先给出一个较高频率的基准时钟(RX_BCLK),这个时钟的频率越高,RX_CLK与TX_CLK就越接近于同步,这里选择RX_BCLK的频率为48MHz。串口的波特率假设为115200。

RX控制器的设计以RX_BCLK为基准,控制器逻辑都在RX_BCLK的时钟域内,而RXD属于TX_CLK时钟域,与RX_BCLK是异步的,要先用CDC电路,将RXD转换到RX_BCLK时钟域里,下面是verilog的具体实现:

reg RXD_T, RXD_2T;

always @(posedge RX_BCLK or negedge RESET)

begin

if(~RESET)

{RXD_T, RXD_2T} <= 2'b11;

else

{RXD_T, RXD_2T} <= {RXD, RXD_T};

end

RXD_2T可以认为是RX_BCLK时钟域的信号了,它相对于RXD的最大延迟为两个RX_BCLK的周期。下面是仿真的波形。

clip_image001

同TX方向状态机的实现一样,RX方向也用一个4状态的状态机,其定义与TX方向状态机一一对应,分别为UARTRX_IDLE,UARTRX_START,UARTRX_DATA和UARTRX_STOP。

当没有数据传输时,系统处于UARTRX_IDLE空闲状态,当RXD从高电平变为低电平时,状态机进入UARTRX_START状态,表明一次传输的开始。然后状态机直接进入UARTRX_DATA状态,在这个状态要保持8个RX_CLK的时间,用于接收8bit的数据。当数据接收完毕后,状态机进入UARTRX_STOP状态。当状态机在UARTRX_STOP状态时,RXD_2T从高电平转为低电平的话,意味着下一次传输已经开始了,所以状态机直接进入UARTRX_START状态,否则状态机回到UARTRX_IDLE状态。下面是状态机的实现:

parameter UARTRX_IDLE = 4'b0001,

UARTRX_START = 4'b0010,

UARTRX_DATA = 4'b0100,

UARTRX_STOP = 4'b1000;

reg [3:0] UARTRXSM, UARTRXSMNXT;

wire PHASE_UARTRX_IDLE = UARTRXSM[0];

wire PHASE_UARTRX_START = UARTRXSM[1];

wire PHASE_UARTRX_DATA = UARTRXSM[2];

wire PHASE_UARTRX_STOP = UARTRXSM[3];

wire PHASENXT_UARTRX_IDLE = UARTRXSMNXT[0];

wire PHASENXT_UARTRX_START = UARTRXSMNXT[1];

wire PHASENXT_UARTRX_STOP = UARTRXSMNXT[3];

reg [2:0] UARTRX_DATA_CNT;

always @(posedge RX_CLK or negedge RESET)

begin

if(~RESET)

UARTRX_DATA_CNT <= 3'b0;

else if(PHASE_UARTRX_DATA)

UARTRX_DATA_CNT <= UARTRX_DATA_CNT + 3'b1;

else

UARTRX_DATA_CNT <= 3'b0;

end

always @(posedge RX_CLK or negedge RESET)

begin

if(~RESET)

UARTRXSM <= UARTRX_IDLE;

else

UARTRXSM <= UARTRXSMNXT;

end

always @( UARTRXSM or RXD_2T or UARTRX_DATA_CNT) begin

UARTRXSMNXT = UARTRXSM;

case (UARTRXSM)

UARTRX_IDLE:

if(~RXD_2T)

UARTRXSMNXT = UARTRX_START;

else

UARTRXSMNXT = UARTRX_IDLE;

UARTRX_START:

UARTRXSMNXT = UART_DATA;

UARTRX_DATA:

if(UARTRX_DATA_CNT == 3'b111)

UARTRXSMNXT = UARTRX_STOP;

else

UARTRXSMNXT = UARTRX_DATA;

UARTRX_STOP:

if(~RXD_2T)

UARTRXSMNXT = UARTRX_START;

else

UARTRXSMNXT = UARTRX_IDLE;

default:

UARTRXSMNXT = UARTRX_IDLE;

endcase

end

RX_CLK由RX_BCLK经分频生成,分频数根据RX_BCLK的频率和波特率计算得出。比如RX_BCLK为48MHz,波特率为115200时,分频数为416。为了便于串口数据的获取,在生成RX_CLK的同时,生成一个与RX_CLK反相的RX_CLK_。根据之前的讨论,需要在每次传输开始的时候,进行RX_CLK与TX_CLK的近似同步。从上面的状态机可以看到,有两种情况可以认为是一个传输的开始,一个是状态机在UARTRX_IDLE状态遇到RXD_2T为0,另外一个是状态机在UARTRX_STOP状态遇到RXD_2T为0。根据这两种情况,来生成RX_CLK的同步使能信号RX_CLK_EN。

reg PHASE_UARTRX_STOP_Q;

always @(posedge RX_CLK or negedge RESET)

begin

if(~RESET)

PHASE_UARTRX_STOP_Q <= 1'b0;

else

PHASE_UARTRX_STOP_Q <= PHASE_UARTRX_STOP;

end

reg RX_CLK_EN_PRE, RX_CLK_EN_Q;

always @(posedge RX_BCLK or negedge RESET)

begin

if(~RESET)

RX_CLK_EN_PRE <= 1'b0;

else if( (PHASE_UARTRX_IDLE | (~PHASE_UARTRX_STOP & PHASE_UARTRX_STOP_Q) ) & ~RXD_2T)

RX_CLK_EN_PRE <= 1'b1;

else

RX_CLK_EN_PRE <= 1'b0;

End

always @(posedge RX_BCLK or negedge RESET)

begin

if(~RESET)

RX_CLK_EN_Q <= 1'b0;

else

RX_CLK_EN_Q <= RX_CLK_EN_PRE;

end

wire RX_CLK_EN = RX_CLK_EN_PRE & ~RX_CLK_EN_Q;

下面是第一种情况下RX_CLK_EN信号的仿真波形:

clip_image003

那么重点就是根据RX_CLK_EN来生成RX_CLK和RX_CLK_了。见下面的verilog代码。在RX_CLK_EN有效的时候,先让RX_CLK为0,然后延时50个RX_BCLK周期,再让RX_CLK变为0,是为了使RX_CLK在传输开始以后有一个上升沿,从而保证状态机可以正确跳转。

reg [13:0] RX_CLK_CNT;

always @(posedge RX_BCLK or negedge RESET)

begin

if(~RESET)

begin

{RX_CLK, RX_CLK_} <= 2'b01;

RX_CLK_CNT <= 14'b0;

end

else if(RX_CLK_EN)

begin

{RX_CLK, RX_CLK_} <= 2'b01;

RX_CLK_CNT <= 14'b0;

end

else if(PHASENXT_UARTRX_START & (RX_CLK_CNT == 14'd50) )

begin

{RX_CLK, RX_CLK_} <= 2'b10;

RX_CLK_CNT <= RX_CLK_CNT + 14'b1;

end

else if(RX_CLK_CNT == 14'd207)

begin

{RX_CLK, RX_CLK_} <= { RX_CLK_, RX_CLK};

RX_CLK_CNT <= RX_CLK_CNT + 14'b1;

end

else if( RX_CLK_CNT == 14'd416)

begin

{RX_CLK, RX_CLK_} <= { RX_CLK_, RX_CLK};

RX_CLK_CNT <= 14'd0;

end

else

begin

{RX_CLK, RX_CLK_} <= { RX_CLK, RX_CLK_};

RX_CLK_CNT <= RX_CLK_CNT + 14'b1;

end

end

从上面的设计可以看到,RX有效数据的跳变都是在RX_CLK的上升沿发生的,我们在RX_CLK_的上升沿去采集RX的数据,是最安全的。可以用下面的代码实现对8bit数据的采集和存储:

reg [7:0] UART_RX_DATA;

always @(posedge RX_CLK_ or negedge RESET)

begin

if(~RESET)

UART_RX_DATA <= 8'b0;

else if(PHASE_UARTRX_DATA)

UART_RX_DATA <= {RXD_2T, UART_RX_DATA[7:1]};

else if (PHASE_UARTRX_IDLE)

UART_RX_DATA <= 8'b0;

else

UART_RX_DATA <= UART_RX_DATA;

end

UART_RX_DATA即为从串口获取的一个字节的数据。可以采用异步FIFO把数据存储并转换到其他时钟域。具体的实现跟TX的异步FIFO相同,不再做详细的介绍。

下面是一次传输的仿真波形。

clip_image005

二.TX和RX方向的硬件验证

用loopback测试的模式,在硬件上验证串口的TX和RX控制器。将FPGA的串口接到PC机的串口上,用串口调试助手从PC机发数据给FPGA,FPGA的RX控制器接到数据后,再用TX控制器将数据发回给PC。

下面是波特率为115200时,连续发送1000次数据“11”的结果,数据没有错误。

image

下面是波特率为115200时,连续发送1000次数据“5a”的结果,数据没有错误。

clip_image009

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值