Data loopback and ram read / write control based on uart transmission packet protocol

preface:
First, understand what serial communication is (from punctual atomic data).
(1) Parallel communication VS serial communication



(2) Synchronous communication VS asynchronous communication


(3) . transmission direction


(4) Introduction to UART serial port
UART is a universal asynchronous receiver transmitter using asynchronous serial communication. It converts parallel data into serial data for transmission when sending data, and converts the received serial data into parallel data when receiving data. UART serial communication requires two signal lines, one for serial transmission and the other for serial reception. A frame of UART data in the process of sending or receiving is composed of four parts: start bit, data bit, parity bit and stop bit, as shown in the figure.

The start bit marks the beginning of a frame of data, the stop bit marks the end of a frame of data, and the data bit is the valid data in a frame of data. The check bits are divided into odd check and even check, which are used to check whether there are errors in the data transmission process. During odd check, the sender shall make the sum of the number of 1 in the data bit and the number of 1 in the check bit odd; When receiving data, the receiver checks the number of 1. If it is not an odd number, it indicates that there is an error in the data transmission process. Similarly, even check whether the number of 1 is even.

The data format and transmission rate during UART communication can be set. In order to correctly communicate, the sending and receiving parties shall agree and follow the same settings. 5, 6, 7 and 8 data bits can be selected, of which 8 data bits are the most commonly used. In practical application, 8 data bits are generally selected; The check bit can select odd check, even check or no check bit; The stop bit can be 1 bit (default), 1.5 or 2 bits. The rate of serial communication is expressed by baud rate, which represents the number of bits of binary data transmitted per second. The unit is bps (bit / second). The commonly used baud rates are 9600, 19200, 38400, 57600 and 115200.

After setting the data format and transmission rate, UART is responsible for the serial parallel conversion of data, and the signal transmission is realized by the external driving circuit. The transmission process of electrical signal has different level standards and interface specifications. The interface standards for asynchronous serial communication include RS232, RS422, RS485, etc. they define different electrical characteristics of the interface, such as RS-232 is single ended input and output, and RS-422/485 is differential input and output.

RS232 interface standard appeared earlier and can realize full duplex working mode, that is, data transmission and reception can be carried out at the same time. When the transmission distance is short (no more than 15m), RS232 is the most commonly used interface standard for serial communication. The most common interface type of RS-232 standard serial port is DB9. Industrial computers used in the field of industrial control are generally equipped with multiple serial ports, and many old desktop computers are also equipped with serial ports. However, notebook computers and newer desktop computers do not have serial ports. They generally realize serial communication with external devices through USB to serial port cable. Because the RS232 serial port of DB9 interface type occupies a large space, many systems have selected the USB to TTL scheme, use CH340 chip to realize the function of USB bus to UART, and communicate with the host computer through Mini USB interface.

1, Physical layer
Realize the data loop between the host computer and FPGA serial port. That is, the upper computer sends data to FPGA through the serial port debugging assistant, and FPGA receives data through the serial port and connects it
The received data is sent to the upper computer to complete the serial port data loopback.

First, the data format and baud rate of serial communication should be determined. Here, select a common mode of serial port. The data bit is 8 bits, the stop bit is 1 bit, there is no check bit, and the baud rate is 115200bps. The sequence diagram of transmitting one frame of data is shown in the figure:

Data reception:

module uart_recv(
    input			  sys_clk,                  
    input             sys_rst_n,                
    
    input             uart_rxd,  //Receive serial data              
    output  reg       uart_done, //Receive a frame of data completion flag               
    output  reg [7:0] uart_data  //Output parallel data               
    );
    

parameter  CLK_FREQ = 50000000;  //System clock frequency: 50M pulses per second              
parameter  UART_BPS = 115200;    //Serial port baud rate transmits 115200 data bits per second                 
localparam BPS_CNT  = CLK_FREQ/UART_BPS;  //Define internal variable BPS_CNT is the system clock frequency divided by the baud rate, that is, how many clock cycles it takes to send a data bit (1 bit).
                                          //Due to the slow transmission rate of the serial port, its baud rate cycle is obtained by counting the system clock.
                                          //Count BPS for system clock_ CNT times, you can get the baud rate cycle of the serial port.      
                                                

reg        uart_rxd_d0;
reg        uart_rxd_d1;
reg [15:0] clk_cnt;     //System clock counter, which counts BPS of system clock_ CNT times, i.e. 1 bit data is sent         
reg [ 3:0] rx_cnt;      //Receive data counter                        
reg        rx_flag;                             
reg [ 7:0] rxdata;                              

wire       start_flag;

//Capture UART from serial input port_ Falling edge of RXD
assign  start_flag = uart_rxd_d1 & (~uart_rxd_d0);    

always @(posedge sys_clk or negedge sys_rst_n) begin 
    if (!sys_rst_n) begin 
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;          
    end
    else begin
        uart_rxd_d0  <= uart_rxd;                   
        uart_rxd_d1  <= uart_rxd_d0;
    end   
end

//Rx for receiving process_ Flag signal is marked
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                                  
        rx_flag <= 1'b0;
    else begin	
        if(start_flag)                          
            rx_flag <= 1'b1;                    
        else if((rx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
            rx_flag <= 1'b0;                             //rx_ The flag continues to the middle of the 10th bit (stop bit) and stops the receiving process
        else
            rx_flag <= rx_flag;
    end
end

Enter the sending process and the counter works
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin                             
        clk_cnt <= 16'd0;                                  
        rx_cnt  <= 4'd0;
    end                                                      
    else if ( rx_flag ) begin                 //rx_ The flag signal is pulled high, the receiving process starts, and the counter works  
            if (clk_cnt < BPS_CNT - 1) begin
                clk_cnt <= clk_cnt + 1'b1;
                rx_cnt  <= rx_cnt;
            end
            else begin
                clk_cnt <= 16'd0;               
                rx_cnt  <= rx_cnt + 1'b1;     //clk_cnt count to BPS_CNT,rx_cnt plus 1 
            end
        end
     else begin                              
         clk_cnt <= 16'd0;   //rx_ The flag signal is pulled low, the receiving process is completed, and the counter is cleared
         rx_cnt  <= 4'd0;
     end
end

//The serial data is converted into parallel data and stored in the internal variable rxdata
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if ( !sys_rst_n)  
        rxdata <= 8'd0;                                     
    else if(rx_flag)                            
        if (clk_cnt == BPS_CNT/2) begin         
            case ( rx_cnt )
             4'd1 : rxdata[0] <= uart_rxd_d1;   
             4'd2 : rxdata[1] <= uart_rxd_d1;
             4'd3 : rxdata[2] <= uart_rxd_d1;
             4'd4 : rxdata[3] <= uart_rxd_d1;
             4'd5 : rxdata[4] <= uart_rxd_d1;
             4'd6 : rxdata[5] <= uart_rxd_d1;
             4'd7 : rxdata[6] <= uart_rxd_d1;
             4'd8 : rxdata[7] <= uart_rxd_d1;   //Start bit 0 and stop bit 1 are removed
             default:;                                    
            endcase
        end
        else 
            rxdata <= rxdata;
    else
        rxdata <= 8'd0;
end

//Receiving module output: parallel signal transmission, and the receiving enable is pulled high
always @(posedge sys_clk or negedge sys_rst_n) begin        
    if (!sys_rst_n) begin
        uart_data <= 8'd0;                               
        uart_done <= 1'b0;
    end
    else if(rx_cnt == 4'd9) begin               
        uart_data <= rxdata;                    
        uart_done <= 1'b1;                      
    end
    else begin
        uart_data <= 8'd0;                                   
        uart_done <= 1'b0; 
    end    
end

endmodule	

Data transmission:

module uart_send(
    input	      sys_clk,                  
    input         sys_rst_n,                
    
    input         uart_en,                  
    input  [7:0]  uart_din,                 
    output  reg   uart_txd                  
    );
    

parameter  CLK_FREQ = 50000000;             
parameter  UART_BPS = 115200;                 
localparam BPS_CNT  = CLK_FREQ/UART_BPS;    


reg        uart_en_d0; 
reg        uart_en_d1;  
reg [15:0] clk_cnt;                         
reg [ 3:0] tx_cnt;                          
reg        tx_flag;                         
reg [ 7:0] tx_data;                         


wire       en_flag;

//Capture UART_ The rising edge of en obtains a clock cycle pulse signal en_flag
assign en_flag = (~uart_en_d1) & uart_en_d0;
                                                 

always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
        uart_en_d0 <= 1'b0;                                  
        uart_en_d1 <= 1'b0;
    end                                                      
    else begin                                               
        uart_en_d0 <= uart_en;                               
        uart_en_d1 <= uart_en_d0;                            
    end
end

//TX send procedure_ Flag signal
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin                                  
        tx_flag <= 1'b0;
        tx_data <= 8'd0;
    end 
    else if (en_flag) begin                 
            tx_flag <= 1'b1;                
            tx_data <= uart_din;            
        end
        else 
        if ((tx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
        begin                               
            tx_flag <= 1'b0;                
            tx_data <= 8'd0;
        end
        else begin
            tx_flag <= tx_flag;
            tx_data <= tx_data;
        end 
end

//Enter the sending process and the counter works
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin                             
        clk_cnt <= 16'd0;                                  
        tx_cnt  <= 4'd0;
    end                                                      
    else if (tx_flag) begin                 
        if (clk_cnt < BPS_CNT - 1) begin
            clk_cnt <= clk_cnt + 1'b1;
            tx_cnt  <= tx_cnt;
        end
        else begin
            clk_cnt <= 16'd0;               
            tx_cnt  <= tx_cnt + 1'b1;       
        end
    end
    else begin                              
        clk_cnt <= 16'd0;
        tx_cnt  <= 4'd0;
    end
end

//Assign a value to the uart send port according to the send data counter
always @(posedge sys_clk or negedge sys_rst_n) begin        
    if (!sys_rst_n)  
        uart_txd <= 1'b1;        
    else if (tx_flag)
        case(tx_cnt)
            4'd0: uart_txd <= 1'b0;         
            4'd1: uart_txd <= tx_data[0];   
            4'd2: uart_txd <= tx_data[1];
            4'd3: uart_txd <= tx_data[2];
            4'd4: uart_txd <= tx_data[3];
            4'd5: uart_txd <= tx_data[4];
            4'd6: uart_txd <= tx_data[5];
            4'd7: uart_txd <= tx_data[6];
            4'd8: uart_txd <= tx_data[7];   
            4'd9: uart_txd <= 1'b1;         
            default: ;
        endcase
    else 
        uart_txd <= 1'b1;                   
end

endmodule	          

Top level module:

module uart_top(
    input           sys_clk,      //External 50M clock    
    input           sys_rst_n,    //External reset signal, low effective    
    
    input           uart_rxd,     //UART receive port    
    output          uart_txd      //UART send port    
    );
    

parameter  CLK_FREQ = 50000000;   //Define system clock frequency    
parameter  UART_BPS = 115200;     //Define serial baud rate     

wire uart_rx_vld_p;               //UART send enable
wire [7:0] uart_rx_data;          //UART send data            


uart_recv #(/ / serial port receiving module   
    .CLK_FREQ       (CLK_FREQ),    //Set the system clock frequency   
    .UART_BPS       (UART_BPS))    //Set serial port receiving baud rate   
u_uart_recv(                 
    .sys_clk        (sys_clk), 
    .sys_rst_n      (sys_rst_n),
    
    .uart_rxd       (uart_rxd),
    .uart_done      (uart_rx_vld_p),
    .uart_data      (uart_rx_data)
    );
    
uart_send #(/ / serial port sending module  
    .CLK_FREQ       (CLK_FREQ),       
    .UART_BPS       (UART_BPS))       
u_uart_send(                 
    .sys_clk        (sys_clk),
    .sys_rst_n      (sys_rst_n),
     
    .uart_en        (uart_rx_vld_p),
    .uart_din       (uart_rx_data),
    .uart_txd       (uart_txd)
    );

endmodule 

Test and modelsim simulation:
Instantiate the top-level module and the upper computer sending module and receiving module in testbench. The whole loop is simulated to simulate the serial communication between the host computer and FPGA to realize the data loop. Check whether the sending data and receiving data of the upper computer are consistent, and send multiple groups of randomly generated data.

 `timescale 1ns/1ps

module tb();

reg sys_clk = 0;
always #(500.0/50.0) sys_clk = ~sys_clk;

reg sys_rst_n = 0;
initial begin
    #1000
    sys_rst_n = 1;
    #1000
    sys_rst_n = 0;
    #1000
    sys_rst_n = 1;
end

parameter CLK_FRE=50000000;
parameter UART_BPS=115200;

reg         host_tx_en;  //host sent data enable
reg [7:0]   host_tx_data;//host sent data

wire 		host_uart_rxd;//host uart port receive 
wire 		host_uart_txd;//host uart port sent

wire         host_rx_done;//host receive finish
wire [7:0]   host_rx_data;//host receive data

uart_top UI_UART_TOP0( // this uart is used to simulate FPGA
    .sys_clk    (sys_clk    ),
    .sys_rst_n  (sys_rst_n  ),
    
    .uart_rxd   (host_uart_txd  ),
    .uart_txd   (host_uart_rxd  ) 
    );

uart_send UI_HOST_TX( // here simulate host tx
    .sys_clk    (sys_clk    ),
    .sys_rst_n  (sys_rst_n  ),                
    
    .uart_en	(host_tx_en),                  
    .uart_din	(host_tx_data),                 
    .uart_txd   (host_uart_txd)              
    );	

uart_recv UI_HOST_RX( // here simualte host rx
    .sys_clk    (sys_clk    ),
    .sys_rst_n  (sys_rst_n  ),                
    
    .uart_rxd	(host_uart_rxd),                 
    .uart_done	(host_rx_done),                
    .uart_data  (host_rx_data)            
    );
    
initial begin
    host_tx_en = 0;
    host_tx_data = 0;
    repeat(1000)@(posedge sys_clk);
    repeat(100)begin
        @(posedge sys_clk)begin 
            host_tx_en = 1;     //We set the enable signal as a pulse signal in tb, but actually en is connected with done as a level signal
            host_tx_data = $random % 256;//Generates a random number between - 255 and 255
        end
        @(posedge sys_clk)begin // Here, each rising edge generates a random number, and the transmission module is separated by a frame of data:
            host_tx_en = 0;     // Random numbers generate raw data, which will be uart_send lock in
        end                     // Then it is sent out and regenerated into a new random number after sending, which is not generated in each cycle
		
        wait(host_rx_done);
		wait(~host_rx_done);
      
    end
   
end

always @(posedge host_rx_done)
    if (host_tx_data != host_rx_data)begin
        $display("XXXXXXXXXXXXXXXXX");
    end


initial begin
    #1000_000
    $finish;
end

endmodule

You can see the sending random number host_rx_data and received data host_ tx_ The data is consistent, all 8'h63.

Added by lanmonkey on Sun, 02 Jan 2022 22:35:30 +0200