FPGA self-study notes--serial communication implementation (vivado&verilog version)

Recently, I have done exercises on the implementation of two communication protocols (uart, spi). This article introduces the verilog implementation of the uart serial port protocol (serial port transmission) and the writing of testbench. Considering that some students still use vhdl, the vhdl version will be released later. In the future series, there will be articles introducing the spi protocol, recording the difficulties and correct solutions I encountered in my own learning, and the simulation environment is vivado 2018.3.

1. Serial communication protocol (uart)

        As one of the three commonly used low-speed buses (UART, SPI, IIC), the serial port plays an important role in the design of many communication interfaces and debugging. The full name of the serial port (UART) is Universal Asynchronous Receiver/Transmitter (Universal Asynchronous Receiver/Transmitter), which is mainly used for serial transmission between data and is a full-duplex transmission mode. It converts parallel data into serial data for transmission when sending data, and converts received serial data into parallel data when receiving data.
This article is currently only implementing the serial port sending process, that is, converting parallel data into serial data and sending it to other devices.

As a novice, I think the following points should be paid attention to in serial communication:

1. What is asynchronous? Asynchronous, which means that a non-synchronous clock is used between the two modules for data transfer. In fact, no clock is needed in the transmission of the asynchronous serial port, but a specific timing is used to mark the start of the transmission (start bit-from high to low) and the end (end bit, pull high). The SPI protocol we will talk about later is the synchronization method, which is to cooperate with the master and slave devices through a sclk signal. 

2. The physical layer, as shown in the figure below. In fact, we only need to care about the RXD, TXD, and GND ports. In fact, we don’t need to know too much about the others. Students who are interested can Baidu by themselves.

3. Understand the timing diagram sent by uart. (Very important!!!!)

2. Timing diagram of serial port sending

1. First of all, we must understand the data format sent by the serial port. Because it is asynchronous, the data format is particularly important. It is to know the start flag and termination flag of the communication, that is, when the communication starts and when it ends.

        A frame of data in the process of UART sending or receiving consists of 4 parts, start bit, data bit, parity bit and stop bit, as shown in the figure above. Among them, 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 valid data in a frame of data.

        Start bit: start bit flag. Low is active.

        Data bit: valid data in a frame of data. Note that the serial port protocol stipulates that the data bit length can only be 6 7 8 bits. If you want to send multi-bit data, you can only divide it into multiple processes and send it.

        Parity bit: It is used to verify the correctness of the data. Parity check is generally not used, if it is used, both odd check (Odd) and even check (Even) can be done. In fact, if the odd parity bit is 1 when sending, the number of 1s in the surface data is an odd number, and then check whether the odd parity bit at the receiving end is still 1. If yes, the verification is successful. The correctness of the transmission is guaranteed at a certain probability. Of course, the transmission is only probabilistically correct. This trial is different parity.

        Stop bit: The stop bit marks the end of a frame of data, high effective.

        When idle, transmit 1.

        Baud rate setting: Since it is asynchronous communication, there is no unified clock, and both parties need to agree on the transmission rate. Common baud rates are: 300, 1200, 2400, 9600, 19200, 115200, etc. That is, how many bits of data are sent in one second.

        So we need to implement the timing diagram below. When the enable signal arrives, the start bit, data bit, and stop bit are sent, and a frame of data transmission ends.

 Three, FPGA realization.

 Task: The uart_tx module is enabled by the single pulse signal send_go, reads data[7:0] into the uart_tx module, and outputs the single pulse tx_done after the sending is completed.

   Module block diagram:

Designing Documents:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/04/28 09:19:27
// Design Name: 
// Module Name: uart_tx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

module uart_byte_tx (
    clk,
    reset,
    send_go,      // 改成单脉冲启动信号  启动一个内部 send_en 信号
    data,
    baud_set,
    uart_tx,
    tx_done
);

    input clk;
    input reset;
    input send_go;
    input [7:0]data;
    input [2:0]baud_set;
    output reg uart_tx;
    output reg tx_done;
    //  every data remain time 
    
    //  baud_set = 0 bps = 9600  bps_max = 1000 000 000 / 9600 / 20 
    
    reg [17:0]bps_max;
    always@(*)
        case(baud_set)
            0:bps_max = 1000000000/9600/20;  // clk = 50MHZ
            0:bps_max = 1000000000/19200/20;
            0:bps_max = 1000000000/38400/20;
            0:bps_max = 1000000000/57600/20;
            0:bps_max = 1000000000/115200/20;
            default:bps_max = 1000000000/9600/20;
        endcase
        
     reg send_en;
       
     always@(posedge clk or negedge reset)
        if (!reset)
            send_en <= 0;
        else if(send_go)
            send_en <= 1;
        else if(tx_done)
            send_en <= 0;
     //  一旦开始发送,send_go 有效,就将外部的data 锁存起来,,这样防止外部数据变化而采错data【i】
    reg [7:0]r_data;
     always@(posedge clk or negedge reset)
        if (send_go)
            r_data <= data;
        else
            r_data <= r_data;
        
    wire bps_clk;
    assign bps_clk = (div_cnt == 1);
    reg [17:0]div_cnt;
    always @(posedge clk or negedge reset) begin
        if(!reset)
            div_cnt <= 0;
        else if(send_en)
        begin
            if(div_cnt == bps_max - 1)
                div_cnt <= 0;
            else
                div_cnt <= div_cnt + 1'b1;
        end
       else
            div_cnt <= 0; 
    end
    
    reg [3:0]bps_cnt;   // 11 
    always @(posedge clk or negedge reset) begin
        if(!reset)
            bps_cnt <= 0;
        else if(send_en)begin
            if(bps_clk)begin
            //if(div_cnt == 1)begin                                // 当send_en有效事  bps_cnt 才开始加   bps_cnt 不要等到最大再加一 这样会滞后
                if(bps_cnt == 12)
                    bps_cnt <= 0;                         
                else
                    bps_cnt <= bps_cnt + 1'b1;
               end
              end
         else
                bps_cnt <= 0; 
    end
    
    // send
    always @(posedge clk or negedge reset) 
        if(!reset)begin
            uart_tx <= 1'b1;
            tx_done <= 1'b0;
        end
        else begin
            case(bps_cnt)
//                0:begin uart_tx <= 1'b0;tx_done <= 1'b0;end        这样写,bps_cnt 上面是 send_en 为零的时候就 为0  用0 不科学
//                1:uart_tx <= data[0];
//                2:uart_tx <= data[1];
//                3:uart_tx <= data[2];
//                4:uart_tx <= data[3];
//                5:uart_tx <= data[4];
//                6:uart_tx <= data[5];
//                7:uart_tx <= data[6];
//                8:uart_tx <= data[7];
//                9:uart_tx <= 1'b1;
//                10:begin uart_tx <= 1'b1;tx_done <= 1'b1;end
//                default:uart_tx <= 1'b1;
                0:tx_done<=1'b0;
                1:uart_tx <= 1'b0;               
                2:uart_tx <= r_data[0];
                3:uart_tx <= r_data[1];
                4:uart_tx <= r_data[2];
                5:uart_tx <= r_data[3];
                6:uart_tx <= r_data[4];
                7:uart_tx <= r_data[5];
                8:uart_tx <= r_data[6];
                9:uart_tx <= r_data[7];
                10:uart_tx <= 1'b1;
                11:begin uart_tx <= 1'b1;end
                default:uart_tx <= 1'b1;
            endcase
        end
        // 为了保证 tx_done 只持续一个时钟周  单独写一个process
        always @(posedge clk or negedge reset) 
            if(!reset)
                tx_done <= 1'b0;
            else if ((bps_clk == 1) && (bps_cnt == 10))
                tx_done <= 1'b1;
            else
                tx_done <= 1'b0; 
endmodule

testbench file:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/05/01 14:23:59
// Design Name: 
// Module Name: uart_byte_tx_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module uart_byte_tx_tb(
    );
    reg clk;
    reg reset;
    reg send_go;
    reg [7:0]data;
    wire uart_tx;
    wire tx_done;
    
uart_byte_tx uart_byte_tx (
    .clk(clk),
    .reset(reset),
    .send_go(send_go),      // 改成单脉冲启动信号  启动一个内部 send_en 信号
    .data(data),
    .baud_set(3'd4),
    .uart_tx(uart_tx),
    .tx_done(tx_done)
);
    initial clk = 1;
   always#10 clk = ~clk;
    
    initial begin
        reset = 0;
        #201;
        reset = 1;
        send_go = 1;
        data = 8'h57;
        #20
        send_go = 0;
        
        @(posedge tx_done);
        #201;
        reset = 1;
        send_go = 1;
        data = 8'h75;
        #20
        send_go = 0;
        
        @(posedge tx_done);
        #20000;
        $stop;
    end
endmodule

Simulation results: 

From the results in the above figure, we can see: When the send_go signal comes (single pulse), as for why it is designed as a single pulse, because the single pulse is more in line with the handshake habit between modules. This mainly refers to the video of Brother Xiaomei on station B. Now we continue to look at the simulation results, first send the data 01010111, the serial port is specified from low to high, so add the start flag and end flag, it is 0111010101 ten-digit data, which proves the correctness of the timing. Every time a piece of data is sent, a tx_done single pulse is generated. Project files can be directly downloaded from my resources (0 points) serial communication implementation (verilog with testbench files) - embedded document resources - CSDN library .

Guess you like

Origin blog.csdn.net/lgk1996/article/details/124523461