Verilog realizes serial communication (UART)

Posted by tasistro on Thu, 30 Dec 2021 21:51:46 +0100

Verilog realizes serial communication (UART)

This code refers to the relevant tutorials of wildfire to realize the sending and receiving loop. At the same time, the LED light can be controlled on and off through the serial port data. When the computer sends data, it is necessary to select the HEX sending mode and send hexadecimal data for control.

In UART protocol, it is high level when idle. In the common setting of one bit stop bit and no check bit, the start bit is low level, followed by 8-bit data bit, and finally a high-level stop bit.

Receiving module

The main design of the receiving module is to take the detection of the falling edge of the start bit as the start signal of the receiving system. Through the combination of the start signal and the bit count signal, we can get an enable signal throughout the overall operation process, and the control of the system can be realized through the judgment of the enable signal.

Another key problem is the time of level acquisition, which should be collected in the middle of a bit, which can effectively avoid the problem of unstable acquisition level.

The code of the receiving module is as follows:

module uart_receive (
    input  clk,
    input  rst_n,
    input  uart_rx,
    output reg receive_done,
    output reg  [7:0]   uart_data
);
parameter  SYSTERM_CLK = 50_000_000;               //System clock frequency
parameter  UART_BPS    = 115200;                     //Serial baud rate
localparam BPS_COUNT_MAX   = SYSTERM_CLK/UART_BPS;     //To get the specified baud rate
                                                   //BPS needs to be counted on the system clock_ Count times
reg      [7:0]       reg_data;//Accept data cache
reg      [3:0]       bit_count;//Used to count the number of bits received when receiving data
reg      [12:0]      bps_count;//Used to calculate the time of one byte according to the clock
reg                  start_bit;//After the falling edge of the start bit is detected, the high level of a clock is triggered
reg                  reg1     ;
reg                  reg2     ;
reg                  reg3     ;           
reg                  bit_flag ;//A high level flag is generated in the middle of a level
reg                  work_en  ;//At the high level of this flag, the acceptance work starts and the work ends at the low level
reg                  rx_flag  ;//A high level is generated after the data buffer is full

//Insert two-level registers for data synchronization to eliminate metastability
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reg1 <= 1'b1;//Because the idle state of UART is high level, the level should be set to high level during reset
    end
    else        begin
        reg1 <= uart_rx;
    end
end    
//Insert two-level registers for data synchronization to eliminate metastability
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reg2 <= 1'b1;//Because the idle state of UART is high level, the level should be set to high level during reset
    end
    else        begin
        reg2 <=reg1;
    end        
end

//Insert two-level registers for data synchronization to eliminate metastability
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reg3 <= 1'b1;//Because the idle state of UART is high level, the level should be set to high level during reset
    end
    else        begin
        reg3 <=reg2;
    end        
end
//start_bit is used to detect the falling edge of the start bit
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        start_bit <= 1'b0;
    end
    else    if ((reg3)&&(!reg2)) begin
        start_bit <= 1'b1;
    end
    else
        start_bit <= 1'b0;
end
//work_en, at the high level of this flag, the acceptance work starts and the work ends at the low level
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        work_en <= 1'b0;
    end
    else    if (start_bit) begin
        work_en <= 1'b1;
    end
    else    if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
        work_en <= 1'b0;
    end
    else
        work_en <= work_en;  
end
//bps_count is used to count the baud rate
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bps_count <= 13'b0;
    end
    else    if ((bps_count == BPS_COUNT_MAX -1) || (work_en == 0)) begin
        bps_count <= 13'b0;//Accounting is only performed when the work is enabled
    end
    else    if (work_en == 1) begin
        bps_count <= bps_count + 1'b1;
    end
    else
        bps_count <= bps_count;    
end
//bit_flag, which outputs the high level of a clock at the midpoint of a byte
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_flag <= 1'b0;
    end
    else    if (bps_count == BPS_COUNT_MAX/2-1) begin
         bit_flag <= 1'b1;
    end
    else
        bit_flag <= 1'b0;       
end
//bit_count, which is used to count the bit currently received
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
       bit_count <= 4'b0;  
    end
    else    if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
        bit_count <= 4'b0;
    end
    else    if (bit_flag == 1'b1) begin
        bit_count <= bit_count + 1'b1;
    end
    else
        bit_count <= bit_count;       
end
//reg_data represents the cache of received data
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reg_data <= 8'b0;
    end
    else    if ((bit_flag == 1) && (work_en == 1) && (bit_count <= 4'd8) && (bit_count >= 4'd1)) begin
        reg_data <= {reg3,reg_data[7:1]};
    end
    else
        reg_data <= reg_data;     
end
//rx_flag outputs the high level of a clock after the receiving buffer is full
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rx_flag <= 1'b0;
    end
    else    if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
        rx_flag <= 1'b1;
    end
    else
        rx_flag <= 1'b0;       
end
//uart_data exports data from the data buffer
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        uart_data <= 8'b0;
    end
    else    if (rx_flag == 1'b1) begin
        uart_data <= reg_data;
    end
    else
        uart_data <= uart_data;       
end
//receive_done export received signal
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        receive_done <= 1'b0;
    end
    else    if (rx_flag == 1'b1) begin
        receive_done <= 1'b1;
    end
    else
        receive_done <= 1'b0;       
end



endmodule //uart_receive

Sending module

The implementation process of UART transmitting module is slightly simpler than the receiving process, because the transmitting module does not need to consider the position of acquisition level, but only needs to send the data in sequence by calculating the time of each bit after receiving the transmission enable.

The code is as follows:

module uart_send (
    input   wire                clk,
    input   wire                rst_n,
    input   wire      [7:0]     uart_in_data,
    input   wire                uart_in_flag,
    output  reg                 uart_tx
);
parameter  SYSTERM_CLK = 50_000_000;               //System clock frequency
parameter  UART_BPS    = 115200;                     //Serial baud rate
localparam BPS_COUNT_MAX   = SYSTERM_CLK/UART_BPS;     //To get the specified baud rate
                                                   //BPS needs to be counted on the system clock_ Count times
reg      [12:0]      bps_count;//Used to calculate the time of one byte according to the clock
reg      [3:0]       bit_count;//Used to count the number of bits received when receiving data
reg                  bit_flag ;//A high level flag is generated in the middle of a level
reg                  work_en  ;//At the high level of this flag, the acceptance work starts and the work ends at the low level

//bps_ Baud rate counter
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bps_count <= 13'b0;
    end
    else    if ((bps_count ==BPS_COUNT_MAX - 1'b1) || (work_en == 1'b0)) begin
        bps_count <= 13'b0;
    end
    else    if (work_en == 1'b1) begin
        bps_count <= bps_count + 1'b1;
    end
    else
        bps_count <= bps_count;     
end
//work_en send work enable
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        work_en <= 1'b0;
    end
    else    if ((bit_count == 4'd9) && (bit_flag == 1'b1)) begin
        work_en <= 1'b0;
    end
    else    if (uart_in_flag == 1'b1) begin
        work_en <= 1'b1;
    end
    else
        work_en <= work_en;       
end
//bit_flag signal of each bit
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_flag <= 1'b0;
    end
    else    if (bps_count == 13'b1) begin
         bit_flag <= 1'b1;
    end
    else
        bit_flag <= 1'b0;
end
//bit_count byte count
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_count <= 4'b0;
    end
    else    if ((bit_flag == 1'b1) && (work_en == 1'b1)) begin
        bit_count <= bit_count + 1'b1;
    end
    else    if ((bit_count == 4'd9) && (bit_flag == 1'b1)) begin
        bit_count <= 4'b0;
    end
    else
        bit_count <= bit_count;        
end
//uart_tx
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        uart_tx <= 1'b1;//Serial port transmission, high level when idle
    end
    else    if (bit_flag == 1'b1) begin
        case (bit_count)
            0:  uart_tx <= 1'b0           ;
            1:  uart_tx <= uart_in_data[0]; 
            2:  uart_tx <= uart_in_data[1]; 
            3:  uart_tx <= uart_in_data[2]; 
            4:  uart_tx <= uart_in_data[3]; 
            5:  uart_tx <= uart_in_data[4]; 
            6:  uart_tx <= uart_in_data[5]; 
            7:  uart_tx <= uart_in_data[6]; 
            8:  uart_tx <= uart_in_data[7]; 
            9:  uart_tx <= 1'b1           ;
            default uart_tx <= 1'b1       ;               
        endcase
    end
end
endmodule //uart_send

Top level module

The top layer only needs to instantiate the sending and receiving modules, and add led pins at the same time, which can realize the control of four LED lights in the process of sending and receiving loopback.

The code is as follows:

module uart_top (
    input  wire clk,
    input  wire rst_n,
    input  wire uart_rx,
    output wire uart_tx,
    output wire [3:0] led
);
parameter  SYSTERM_CLK = 26'd50_000_000;               //System clock frequency
parameter  UART_BPS    = 17'd115200;                     //Serial baud rate

wire       flag;
wire [7:0] data;
assign led = data[3:0];
uart_receive 
#(
    .SYSTERM_CLK   (SYSTERM_CLK   ),
    .UART_BPS      (UART_BPS      )
)
u_uart_receive(
    .clk          (clk          ),
    .rst_n        (rst_n        ),
    .uart_rx      (uart_rx      ),
    .receive_done (flag ),
    .uart_data    (data    )
);

uart_send 
#(
    .SYSTERM_CLK   (SYSTERM_CLK   ),
    .UART_BPS      (UART_BPS      )
)
u_uart_send(
    .clk          (clk          ),
    .rst_n        (rst_n        ),
    .uart_in_data (data ),
    .uart_in_flag (flag ),
    .uart_tx      (uart_tx      )
);


endmodule //uart_top

Topics: Verilog FPGA