03【Verilog实战】UART通信协议,半双工通信方式(附源码)

  时间:2022-05-18 16:16:07  阅读量:142  评论数:0  作者:

该篇文章03【Verilog实战】UART通信协议,半双工通信方式(附源码)除了讲述概念知识,更举例源码分析教学,对这方面技术有需求的朋友可以参考着学习,实用性较强。

虚拟机:VMware -14.0.0.24051
环 境:ubuntu 18.04.1
脚 本:makefile(点击直达
应用工具:vcs 和 verdi


在这里插入图片描述



一、Overview

(1)Theory

  UART(Universal Asynchronous Receiver/Transmitter),通用异步收发器,是一种异步全双工串行通信协议,可实现单工通信、半双工通信和全双工通信。
在这里插入图片描述

单工通信方式:主机只能发送数据,从机只能接收数据,数据流向始终由发送端流向接收端;
半双工通信方式:同一时刻,只能由主机向从机或从机向主机发送数据;
全双工通信方式:同一时刻,主机和从机都可以互发数据。


  UART由Tx和Rx两根数据线组成,因为没有参考时钟信号,所以通信的双方必须约定串口波特率、数据位宽、奇偶校验位、停止位等配置参数,从而按照相同的速率进行通信。

UART协议相关概念:

  1. 波特率(baud rate)
    单位bps(bit per second),每秒传输的二进制位数
  2. 起始位(start bit)
    开始发起传输的标志
  3. 停止位(stop bit)
    结束传输的标志
  4. 校验位(check bit)
    为了保证传输可靠性增加的校验信息。奇校验: 数据+校验位中总的1的个数为奇数;偶校验反之。

(2)Baud Rate

  • 每bit时间宽度及每bit时钟周期数
    bit_width = 1/(baud rate);
    bit_clk_num = bit_width/T,注意单位要统一

(3)Check Bit

  • data中”1”个数计算
    奇校验:^data[7:0]==1’b1,1的个数为奇数;
    偶校验:^data[7:0]==1’b0,1的个数为偶数

(4)Demand

  1. 接收上一级模块并行数据(cmd_i),将数据按照UART协议发送出去
模块工作类型信号名方向位宽描述
mastertxoutput1发送信号
masterrxinput1接收信号
slavetxinput1接收数据
slaverxoutput1发送数据
  1. 采用奇校验
  2. 1bit停止位
  3. 波特率115200,波特率可配置
  4. 工作时钟50MHz
  5. 当接收到rx数据时,将并行数据反馈给上一级模块(半双工通信)
  6. 规定数据包一帧数据是8bit,两次数据传输之间要求有delay,且delay可配置
    在这里插入图片描述

二、Interface Description

Signal NameWidthDirectionDescription
clk1inputSystem clk signal, 50Mhz
rst_n1inputSystem reset signal,negedge
cmd_i16input[15]:读写指示;1:写,0:读
[14:8]:地址位
[7:0]:数据位
cmd_rdy1output握手信号ready
cmd_vld1input握手信号valid
tx1outputuart发送数据端
rx1inputuart接收数据端
read_vld1output读数据valid
read_data8output读到的数据

三、Block Diagram

在这里插入图片描述


四、Timeing

(1)write timing

在这里插入图片描述

(2)read timing

在这里插入图片描述


五、Design and Functional Verification

(1)RTL

/*-- modified by xlinxdu, 2022/04/30
  -- cmd_i 16bit,rw_flag:1bit,data addr:7bit,data:8bit
  -- clock freq:50MHz
  -- band rate :115200bps
  -- check bit :odd
  -- stop bit  :1bit
  -- clock num of uart :freq/band_rate = 50M/115200 = 434clk
*/

module uart#(
  parameter CMD_ADDR_WIDTH = 7,
  parameter CMD_DATA_WIDTH = 8,
  parameter RW_FLAG        = 1,
  parameter CMD_WIDTH      = CMD_ADDR_WIDTH +CMD_DATA_WIDTH+RW_FLAG,
  parameter DELAY_CLK      = 1

  )(
  input                       clk_i        ,
  input                       rst_n_i      ,
//-- tx
  input       [CMD_WIDTH-1:0] cmd_data_i   ,
  input                       cmd_vld_i    ,
  output reg                  cmd_rdy_o    ,
  output reg                  tx_o         ,
//-- rx
  input                       rx_i         ,
  output reg                  rd_vld_o     ,
  output reg  [CMD_DATA_WIDTH-1:0] rd_data_o
  );
  reg                    uart_en      ;
  reg    [          8:0] tx_bit_cnt   ;
  reg    [          3:0] tx_bit_num   ;
  reg    [CMD_WIDTH-1:0] cmd_data_buf ;
  wire   [          7:0] cmd_data_high;
  wire   [          7:0] cmd_data_low ;
  wire                   rw_flag      ;
  reg                    wr_data_flag ;
  reg                    tx_uart_done ;

  reg                    dly_cnt_en   ;
  reg    [          6:0] dly_cnt      ;
  reg                    rd_uart_en   ;
  reg    [          8:0] rx_bit_cnt   ;
  reg    [          3:0] rx_bit_num   ;
  reg    [CMD_DATA_WIDTH-1:0] rx_data_buf  ;
  reg    [          2:0] rx_dly       ;
  wire                   negedge_rx   ;
  wire                   rx_sync      ;
  wire                   rx_done      ;
  reg                    rx_uart_done ;
  
  wire                   rd_vld ;//assign --wire-->reg_o
  wire   [CMD_DATA_WIDTH-1:0] rd_data;
//-- cmd_rdy_o
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    cmd_rdy_o <= 1'b1;
  end
  else if (tx_uart_done) begin
    cmd_rdy_o <= 1'b1;
  end
  else if(cmd_vld_i) begin
    cmd_rdy_o <= 'b0;
  end
end

//-- uart_en
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    uart_en <= 1'b0;
  end
  else if(rw_flag && tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433 || dly_cnt_en) begin
    uart_en <= 1'b0;
  end
  else if(tx_uart_done) begin
    uart_en <= 1'b0;
  end
  else if(cmd_vld_i)begin
    uart_en <= 1'b1;
  end
end

//-- tx_bit_cnt
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    tx_bit_cnt <= 9'd0;
  end
  else if(uart_en) begin
    if(tx_bit_cnt == 9'd433) begin
      tx_bit_cnt <= 9'd0;
    end
    else begin
      tx_bit_cnt <= tx_bit_cnt + 1'd1;
    end
  end
  else if(dly_cnt_en) begin
    tx_bit_cnt <= 9'd0; 
  end
end

//-- tx_bit_num
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    tx_bit_num <= 4'd0;
  end
  else if(uart_en) begin 
    //--uart_en keep high,tx_bit_num=11~12-->delay 2bit
    if(tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433) begin
      tx_bit_num <= 4'd0;
    end
    else if(tx_bit_cnt == 9'd433)begin
      tx_bit_num <= tx_bit_num +1'd1;
    end  
  end
  else if(dly_cnt_en)begin
      tx_bit_num <= 4'd0;
  end
end

//-- cmd_data_buf
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    cmd_data_buf <= {CMD_WIDTH{1'B0}};
  end
  else if(cmd_vld_i && cmd_rdy_o)begin
    cmd_data_buf <= cmd_data_i;
  end
end

//-- 
assign {cmd_data_high,cmd_data_low} = cmd_data_buf;
assign rw_flag = cmd_data_buf[CMD_WIDTH-1];

//-- wr_data_flag
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    wr_data_flag <= 1'b0;
  end
  else if (tx_uart_done)begin
    wr_data_flag <= 1'b0;
  end
  else if(rw_flag && tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433) begin
    wr_data_flag <= 1'b1;
  end
end

//-- TX
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    tx_o <= 1'd1;
  end
  else if(uart_en) begin
    //-- send start bit
    if(tx_bit_num == 4'd0)begin
      tx_o <= 1'b0;
    end
    //-- send data bit
    else if(tx_bit_num >= 4'd1 && tx_bit_num <= 4'd8)begin
      //--1:send cmd_data_high and cm_data_low
      if(rw_flag)begin
        if(wr_data_flag)begin//1:low
          tx_o <= cmd_data_low[tx_bit_num-1];
        end
        else begin
          tx_o <= cmd_data_high[tx_bit_num-1];
        end
      end
      //--0:only send cmd_data_high
      else begin
        tx_o <= cmd_data_high[tx_bit_num-1];
      end
    end
    //-- send check bit
    else if(tx_bit_num == 4'd9) begin
      if(rw_flag)begin//-- 1:write
        tx_o <= wr_data_flag ? ~(^cmd_data_low) : ~(^cmd_data_high);
      end
      else begin
        tx_o <= ~(^cmd_data_high);
      end
    end
    //-- send stop bit
    else if(tx_bit_num == 4'd10) begin
      tx_o <= 1'b1;
    end
  end
  else begin
    if(dly_cnt_en) begin
      tx_o <= 1'b1;
    end
  end
end

//-- tx_uart_done
assign tx_uart_done = (~rw_flag || wr_data_flag) && tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433;

//assign tx_uart_done = (~rw_flag && tx_bit_num == 4'd10&& tx_bit_cnt == 9'd433)||
//                   ( rw_flag && tx_bit_num == 4'd10&& tx_bit_cnt == 9'd433 && wr_data_flag);


//-- dly_cnt_en
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    dly_cnt_en <= 1'b0;
  end
  else if(rw_flag && tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433)begin
    dly_cnt_en <= 1'b1;
  end
  else if(dly_cnt == 7'd99)begin
    dly_cnt_en <= 1'b0;
  end
end

//-- dly_cnt
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    dly_cnt <= 7'd0;
  end
  else if(dly_cnt_en)begin
    dly_cnt <= dly_cnt +1'b1;
  end
  else begin
    dly_cnt <= 7'd0;
  end
end


//-- RX
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rx_dly <= 3'b000;
  end
  else begin
    rx_dly <= {rx_dly[1:0],rx_i};
  end
end
assign negedge_rx = {rx_dly[2:1] == 2'b10};
assign rx_sync = rx_dly[2];

//-- rx_uart_done
assign rx_done = ~rw_flag && rx_bit_num == 4'd10&& rx_bit_cnt == 9'd433;
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rx_uart_done <= 1'b0;
  end
  else begin
    rx_uart_done <= rx_done;
  end
end
//-- rd_uart_en
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rd_uart_en <= 1'b0;
  end
  else if(negedge_rx)begin
    rd_uart_en <= 1'b1;
  end
  else if(rx_done)begin
    rd_uart_en <= 1'b0;
  end
end
//-- rx_bit_cnt
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rx_bit_cnt <= 9'd0;
  end
  else if(rd_uart_en) begin
    if(rx_bit_cnt == 9'd433) begin
      rx_bit_cnt <= 1'd0;
    end
    else begin
      rx_bit_cnt <= rx_bit_cnt + 1'd1;
    end
  end
end

//-- rx_bit_num
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rx_bit_num <= 4'd0;
  end
  else if(rd_uart_en) begin 
    //--rd_uart_en keep high,tx_bit_num=11~12-->delay 2bit
    if(rx_bit_num == 4'd10 && rx_bit_cnt == 9'd433) begin
      rx_bit_num <= 4'd0;
    end
    else if(rx_bit_cnt == 9'd433)begin
      rx_bit_num <= rx_bit_num +1'd1;
    end  
  end
  else begin
      rx_bit_num <= 4'd0;
  end
end
//-- rx_data_buf

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rx_data_buf <= 8'd0;
  end
  else if(rx_bit_cnt == 9'd216 && rx_bit_num >= 4'd1 && rx_bit_num <= 4'd8)begin
    rx_data_buf <= {rx_sync,rx_data_buf[7:1]};
  end
end

//-- check bit
assign check_flag = rx_bit_num == 4'd9 &&rx_bit_cnt ==9'd216 && rx_sync == ~^rx_data_buf;

assign rd_vld = check_flag;
assign rd_data = rd_vld?rx_data_buf:rd_data;

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rd_vld_o <= 1'b1;
  end
  else if(rd_vld)begin 
    rd_vld_o <= 1'b1;
  end
  else if(negedge_rx && rx_bit_num == 4'd0)begin
    rd_vld_o <= 1'b0;
  end
end
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rd_data_o <= 8'd0;
  end
  else if(rx_done)begin 
    rd_data_o <= rd_data;
  end
end
endmodule

(2)Test Bench

module uart_tb;
  reg          clk_i      ; 
  reg          rst_n_i    ;

  reg          cmd_vld_i  ;  
  reg  [15:0]  cmd_data_i ; 
  wire         cmd_rdy_o  ;
  wire         tx_o       ;
  
  reg          rx_i       ;
  wire         rd_vld_o   ;
  wire [ 7:0]  rd_data_o  ;

initial begin
  clk_i = 1;
end

always begin
  #10 clk_i = ~clk_i;
end
initial begin
  rst_n_i    = 1;
  cmd_vld_i  = 0;
  cmd_data_i= 16'b1000_0010_0011_0010;//16'h8232
  rx_i = 1; 
  #5 rst_n_i = 0;
  #5 rst_n_i = 1;
  #10 cmd_vld_i=1;
  #180000 cmd_data_i = 16'b0110_1011_0001_1000;//16'h6b14
  #90000 cmd_vld_i =0;
end
initial begin
  #360020 
  rx_i = 0;//start bit
  
  #8660 rx_i = 0;
  #8660 rx_i = 1;
  #8660 rx_i = 0;
  #8660 rx_i = 0;
  #8660 rx_i = 1;
  #8660 rx_i = 1;
  #8660 rx_i = 0;
  #8660 rx_i = 0;
  
  #8660 rx_i = 0;//check bit
  #8660 rx_i = 1;//stop bit
  #8660 rx_i = 1;//delay bit
  #8660;
end


uart u_uart
(
  .clk_i     (clk_i     ),
  .rst_n_i   (rst_n_i   ),
  .cmd_vld_i (cmd_vld_i ),
  .cmd_data_i(cmd_data_i),
  .cmd_rdy_o (cmd_rdy_o ),
  .tx_o      (tx_o      ),
  .rx_i      (rx_i      ),
  .rd_vld_o  (rd_vld_o  ),
  .rd_data_o (rd_data_o )
);

initial begin
	#700000 $finish;
  $fsdbDumpfile("uart.fsdb");
  $fsdbDumpvars             ;
  $fsdbDumpMDA              ;
end

endmodule

六、Result

(1)write

在这里插入图片描述

  • 发送16’h8232(1000_0010_0011_0010)和16’h6B18(0110_1011_0001_1000)
    1、 M1-M3发送0100_0001;
    2、 M3-M4发送校验位1;
    3、 M4-M5为停止位,M5-M6为延时
    4、 M7-M9发送0100_1100;
    5、 M9-M10发送校验位0;
    6、 M13-M15发送1101_0110;只发送地址位。

(2)read

在这里插入图片描述

  • 接收数据8’h32(0011_0010)
    1、 M18下降沿来临,开始接收数据。
    2、 M19-M20接收到数据0100_1100;
    3、 M20-M21接收校验位0。
    4、 C1:在接收完stop位后,如果校验位和传过来的校验位一致,将数据输出到rd_data_o。此处数据为8’h32。校验成功。

作者:xlinxdu
版权:本文是作者原创,版权归作者所有。
转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。

关键词:fpga开发 verilog uart 串口 协议,03,verilog,实战,通信,通信协议,协议,半双工,双工通信,通信方式,方式,源码