Xilinx-Spartan6-学习笔记(21):UART时序分析
由于黑金教程只给出了代码和最终结果,没有中间分析的过程。经过犹豫后,还是决定花功夫进行一下时序分析,为了后续复习方便,也同时监督自己认真对待每一行代码。
话不多说,黑金教程里讲的咱们全部都跳过,直接上干货。
1、clkdiv.v文件
这个文件主要目的是通过50Mhz的系统时钟,分出一个较低频率的时钟用于UART串口驱动。取16倍波特率的目的是对每比特数据有16个时钟进行采样,从而确保不会漏采或者错采。
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0) begin
clkout <= 1'b0;
cnt <= 16'd0;
end
else
if(cnt == 16'd162) begin
clkout <= 1'b1;
cnt <= cnt + 16'd1;
end
else if(cnt == 16'd325) begin
clkout <= 1'b0;
cnt <= 16'd0;
end
else begin
cnt <= cnt + 16'd1;
end
assign w_clkout = clkout;
分频系数的计算公式为:500000000/(16*9600)=325.52~=326。50Mhz代表每秒50M个时钟周期,而我们需要每秒16*9600个时钟周期,相当于每326个50Mhz的时钟周期构成了一个新的时钟周期,即162个周期翻转一次。因此我们可以定义一个wire型时钟变量w_clkout,通过计数器的方法实现0 ~162个周期w_clkout为高,163 ~325个周期w_clkout为低,最终得到的w_clkout信号为最终所需的时钟周期。
2、uartrx.v文件
always @(posedge sclk)
begin
rxbuf <= rx;
rxfall <= rxbuf & (~rx); //检测线路的下降沿
end
这一小段用来检测rx的下降沿,通过定义一个寄存器变量打一拍的方式即可检测rx是否发生下降沿,如果发生的话rxfall拉高一个周期。
always @(posedge sclk)
if(rxfall && (~idle)) //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
receive <= 1'b1;
else if(cnt == 8'd168) //接收数据完成(8bit数据,每个bit需要16个时钟)
receive <= 1'b0;
这里表示当数据线为空闲状态时(idle为1),且检测到了rx有下降沿出现,将receive拉高。此时计数器cnt保持计数,当到168时,receive拉低,表示接收完毕。计数器记到168是由于8bit数据,每bit数据需要16个时钟周期采样,即8*16=168。
always @(posedge sclk)
if(receive == 1'b1) begin
case(cnt)
8'd0:begin
idle <= 1'b1;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
……
else begin
cnt <= 8'd0;
idle <= 1'b0;
rdsig <= 1'b0;
end
这一小段表示将rx数据线上的数据接收到dataout中的过程。当receive信号被拉高后,接收端状态机开始执行。状态机的切换以cnt作为标志,将rx上的值放到dataout中(正好取得是tx的中间位置,避免了错误取值),当8bit数据发送完毕后,发送停止位,rdsig信号拉高(接收的时候rdsig为低),标志结束。
最终dataout输出连接到了uartctrl的rxdata(后续再分析)。
3、uarttx.v文件
always @(posedge sclk)
begin
wrsigbuf <= wrsig; //检查wrsig的上升沿
wrsigrise <= (~wrsigbuf) & wrsig;
end
这一小段同样是用来检测wrsig的上升沿的,当wrsig上升沿到来时,wrsigrise拉高。
always @(posedge sclk)
if(wrsigrise && (~idle)) //当发送命令有效且线路为空闲时,启动新的数据发送进程
send <= 1'b1;
else if(cnt == 8'd168) //一帧数据发送结束
send <= 1'b0;
这里表示当数据线为空闲状态时(idle为1),且检测到了wrsig有下降沿出现,则send拉高。此时计数器cnt保持计数,当到168时,send拉低,表示发送完毕。计数器记到168与接收原理相同。
always @(posedge sclk)
if(send == 1'b1) begin
case(cnt) //产生起始位
8'd0:begin
tx <= 1'b0;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
……
else begin
tx <= 1'b1;
cnt <= 8'd0;
idle <= 1'b0;
end
这一小段表示将预发送的数据datain放到tx数据线上的过程。当send信号被拉高后,发送端状态机开始执行。状态机的切换以cnt作为标志,将datain中的数据放到tx线上,当8bit数据发送完毕后,idle信号拉高(发送时候idle为低),标志结束。
其中datain输入连接到了uartctrl的txdata(后续再分析)。
4、uartctrl.v文件
这个文件的作用主要是协调配合TX和RX两个模块的,当RX接收到数据时,发送数据。如果没有接收到新的数据,那么循环发送预先设置好的数据,此部分数据在本文件中进行配置。对于这个选择开关,掌握在信号data_sel手里。
assign dataout = data_sel ? dataout_reg:rxdata;
assign wrsig = data_sel ? wrsig_reg:rdsig;
当data_sel为1时,dataout被赋值为dataout_reg,wrsig被赋值为wrsig_reg。
当data_sel为0时,dataout被赋值为rxdata,wrsig被赋值为rdsig。
这意味着data_sel为1时,输出预先存储好的数据,并且循环发送;data_set为0时,输出RX端接收到的数据。
always @(posedge sclk)
if(rdsig == 1'b1) begin
uart_cnt <= 0;
uart_stat <= 3'b000;
data_sel <= 1'b0;
k <= 0;
end
else begin
case(uart_stat)
3'b000: begin
if(rx_data_valid == 1'b1) begin
uart_stat <= 3'b001;
data_sel <= 1'b1;
end
end
3'b001: begin
if(k == 18) begin
if(uart_cnt == 0) begin
dataout_reg <= store[18];
uart_cnt <= uart_cnt + 1'b1;
wrsig_reg <= 1'b1;
end
else if(uart_cnt == 254) begin
uart_cnt <= 'd0;
wrsig_reg <= 1'b0;
uart_stat <= 3'b010;
k <= 0;
end
else begin
uart_cnt <= uart_cnt + 1'b1;
wrsig_reg <= 1'b0;
end
end
else begin
if(uart_cnt == 0) begin
dataout_reg <= store[k];
uart_cnt <= uart_cnt + 1'b1;
wrsig_reg <= 1'b1;
end
else if(uart_cnt == 254) begin
uart_cnt <= 'd0;
wrsig_reg <= 1'b0;
k <= k + 1'b1;
end
else begin
uart_cnt <= uart_cnt + 1'b1;
wrsig_reg <= 1'b0;
end
end
end
3'b010: begin
uart_stat <= 3'b000;
data_sel <= 1'b0;
end
default: uart_stat <= 3'b000;
endcase
end
从此段代码可以看出,data_sel在rdsig为1时被置为0,也就是8bit信号发送完毕后,此时将所有状态清零。然后rdsig信号又变为0,进入到通过uart_stat控制的状态机中,如果rx_data_valid满足,表示可以发送预存好的数据。将store中的数据一个一个地赋给dataout_reg。
always @(negedge sclk)
if(rdsig == 1'b1) begin
uart_wait <= 0;
rx_data_valid<=1'b0;
end
else begin
if(uart_wait == 18'h3ffff) begin
uart_wait <= 0;
rx_data_valid <= 1'b1;
end
else begin
uart_wait <= uart_wait + 1'b1;
rx_data_valid <= 1'b0;
end
end
此段代码中uart_wait的作用是设定发送时间间隔的(发送内部定义数据)。当计数到18’h3ffff的时候,rx_data_valid置为1,表示可以发送内部数据。
5、数据流梳理
将所有模块综合起来,梳理如下:
(1)当发送内部数据时:
首先根据9600*16的时钟节拍div_clk,在uartctrl模块中将数据保存在了store中,此时rdsig信号为0时,data_sel为1,将store中的数据一位一位地赋给了dataout_reg寄存器,最终将dataout_reg寄存器中的值赋给了dataout,通过端口给了txdata。txdata进入到了uarttx模块中,通过datain端口输入。当wrsig信号上升沿出现(uartctrl模块中内部数据保存完毕后)且数据线处于idle状态下,send信号被拉高,datain输入进来的数据按照div_clk时钟节拍,一位一位地将数据放到tx数据线上发送出去,tx数据线连接到了FPGA板卡的D12端口,即通过UART输出。
(2)当发送接收到的数据时:
首先在模块uartrx中,当rx数据线上出现下降沿时且数据线处于idle状态时,receive信号被拉高,将数据线rx上的数据按照状态机一位一位地接收到dataout寄存器中,通过端口给了rxdata,rxdata进入到了uartctrl中。当发送完成后rdsig信号被拉高,同样输入到了uartctrl模块中。当rdsig为高时,data_sel置为0,那么wrsig被赋值为rdsig,wrsig信号触发uarttx使得数据进行发送。与此同时,dataout也被赋值为了rxdata,即实现了将接收到的数据给了发送端,通过发送端发送出去(发送的方法与发送内部数据相同)。