DE23-Lite开发板提供了一个UART接口((下图的Type C接口)用户能够通过主机与Agilex 3 FPGA进行串口通信。

image

 

该接口通过DE23-Lite板载的USB Blaster III电路中的FT2232H芯片实现。将USB线连接到DE23-Lite板的Type-C接口和主机之间,即可启用USB Blaster III和FPGA UART功能,此时无需串行驱动程序,但用户在使用UART功能前需要确保已安装USB Blaster III驱动程序。

image

 

连接USB线后,通常在PC设备管理器中会显示USB Blaster III和一个COM端口号。

 

image

 

UART的功能是发送数据时,将并行的数据转换成串行数据进行传输,在接收数据时将接收到的串行数据转换成并行数据。

 

设计

实验任务:上位机发送数据给开发板串口,串口接收数据后又通过串口发送给上位机。

 要求:数据位为8位, 停止位1位,无校验位,波特率115200bps

DE23-Lite的串口回环设计主要是2个模块:串口发送模块和串口接收模块。

0972e199482abdb5e384f80862136697

 

image

 系统时钟是50MHz,波特率是115200bps,那么串口发送和接收时,数据的每个位将占用50000000/115200 ≈ 434个时钟周期。

在串口接收模块设置一个4状态的状态机:

空闲状态:在空闲状态下,检测起始位(低电平)。一旦检测到起始位,进入START状态,并设置计数器在半位时间后采样,这样可以确保在位的中心点采样,提高抗噪能力。

起始位检测状态:等待半个位周期后,再次检查线路状态。如果仍然是低电平,确认是有效的起始位,进入DATA状态;否则认为是噪声干扰,返回IDLE状态。

数据位接收状态:在每个位周期的中心点采样数据位,并存入移位寄存器。接收完8位数据后,进入STOP状态。

停止位处理状态:等待一个完整的位周期(停止位),然后将接收到的数据输出,并产生一个时钟周期的接收完成信号。

 

image

 

工程代码:

module uart_rx(
    input clk,
    input rst_n,
    input uart_rx,
    output reg [7:0] rx_data,
    output reg rx_done
);

    parameter CLK_FREQ = 50000000;
    parameter BAUD_RATE = 115200;
    
    // 波特率计数器
    localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE;
    localparam HALF_BAUD_CNT = BAUD_CNT_MAX / 2;
    reg [15:0] baud_cnt;
    
    // 状态定义
    localparam IDLE = 2'd0;
    localparam START = 2'd1;
    localparam DATA = 2'd2;
    localparam STOP = 2'd3;
    
    reg [1:0] state;
    reg [2:0] bit_cnt;
    reg [7:0] rx_reg;
    reg uart_rx_sync1, uart_rx_sync2;
    
    // 同步输入信号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        uart_rx_sync1 <= 1'b1;
        uart_rx_sync2 <= 1'b1;
    end else begin
        uart_rx_sync1 <= uart_rx;
        uart_rx_sync2 <= uart_rx_sync1;
    end
end
    
    // 状态机
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        rx_data <= 8'd0;
        rx_done <= 1'b0;
        baud_cnt <= 0;
        bit_cnt <= 0;
        rx_reg <= 0;
    end
    else begin
        rx_done <= 1'b0;     
        case (state)
            IDLE: begin
                if (uart_rx_sync2 == 1'b0) begin  // 检测起始位
                    state <= START;
                    baud_cnt <= HALF_BAUD_CNT - 1;  // 半位时间后采样
                end
            end
                
            START: begin
                if (baud_cnt == 0) begin
                    if (uart_rx_sync2 == 1'b0) begin  // 确认起始位
                        state <= DATA;
                        baud_cnt <= BAUD_CNT_MAX - 1;
                        //bit_cnt <= 0;
                    end
                    else begin
                        state <= IDLE;  // 假起始位
                    end
                end else begin
                    baud_cnt <= baud_cnt - 1;
                end
            end
                
            DATA: begin
                if (baud_cnt == 0) begin
                    rx_reg[bit_cnt] <= uart_rx_sync2;
                    if (bit_cnt == 3'd7) begin
                        state <= STOP;
                    end
                    else begin
                        bit_cnt <= bit_cnt + 1;
                    end
                    baud_cnt <= BAUD_CNT_MAX - 1;
                end 
                else begin
                    baud_cnt <= baud_cnt - 1;
                end
            end
                
            STOP: begin
                if (baud_cnt == 0) begin
                    rx_data <= rx_reg;
                    rx_done <= 1'b1;
                    state <= IDLE;
                end
                else begin
                    baud_cnt <= baud_cnt - 1;
                end
            end
        endcase
    end
end

endmodule

 

 串口发送模块同样设置了一个4状态的状态机:

空闲状态:空闲状态保持高电平。

起始位发送状态:发送起始位,低电平。

数据发送状态:数据位从最低位(LSB)开始发送,这是UART的标准格式。

停止位发送状态:发送停止位,高电平。

image

串口发送模块的工程代码:

 

module uart_tx(
    input clk,
    input rst_n,
    input tx_start,
    input [7:0] tx_data,
    output reg uart_tx,
    output tx_busy
);

    parameter CLK_FREQ = 50000000;
    parameter BAUD_RATE = 115200;
    
    // 波特率计数器
    localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE;
    reg [15:0] baud_cnt;
    wire baud_tick = (baud_cnt == 0);
    
    // 状态定义
    localparam IDLE = 2'd0;
    localparam START = 2'd1;
    localparam DATA = 2'd2;
    localparam STOP = 2'd3;
    
    reg [1:0] state;
    reg [2:0] bit_cnt;
    reg [7:0] tx_reg;
    
    // 波特率计数器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        baud_cnt <= 0;
    end
    else if (state != IDLE) begin
        if (baud_cnt == 0) begin
            baud_cnt <= BAUD_CNT_MAX - 1;
        end
        else begin
            baud_cnt <= baud_cnt - 1;
        end
    end
    else begin
        baud_cnt <= 0;
    end
end
    
    // 状态机
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        uart_tx <= 1'b1;
        bit_cnt <= 0;
        tx_reg <= 0;
    end
    else begin
        case (state)
            IDLE: begin
                uart_tx <= 1'b1;
                if (tx_start) begin
                    state <= START;
                    tx_reg <= tx_data;
                end
            end
                
            START: begin
                if (baud_tick) begin
                    uart_tx <= 1'b0;
                    state <= DATA;
                    bit_cnt <= 0;
                end
            end
                
            DATA: begin
                if (baud_tick) begin
                    uart_tx <= tx_reg[bit_cnt];
                    if (bit_cnt == 3'd7) begin
                        state <= STOP;
                    end
                    else begin
                        bit_cnt <= bit_cnt + 1;
                    end
                end
            end
                
            STOP: begin
                if (baud_tick) begin
                    uart_tx <= 1'b1;
                    state <= IDLE;
                end
            end
        endcase
    end
end
    
assign tx_busy = (state != IDLE);

endmodule

 

top文件代码:

  1. 只有在检测到接收完成信号的上升沿时才启动发送

  2. 只有在发送器不忙时才启动新的发送

  3. 实现了接收数据到发送数据的无缝衔接

 

module tsp_uart_loop(
    input clk,
    input rst_n,
    input uart_rx,
    output uart_tx
);

    wire [7:0] rx_data;
    wire rx_done;
    wire rx_done_rise,tx_start;
    wire tx_busy;
    
    // UART接收模块
    uart_rx #(
        .CLK_FREQ(50000000),
        .BAUD_RATE(115200)
    ) uart_rx_inst (
        .clk(clk),
        .rst_n(rst_n),
        .uart_rx(uart_rx),
        .rx_data(rx_data),
        .rx_done(rx_done)
    );
    
    // UART发送模块
    uart_tx #(
        .CLK_FREQ(50000000),
        .BAUD_RATE(115200)
    ) uart_tx_inst (
        .clk(clk),
        .rst_n(rst_n),
        .tx_start(tx_start),
        .tx_data(rx_data),
        .uart_tx(uart_tx),
        .tx_busy(tx_busy)
    );
    
    // 回环控制逻辑
    reg rx_done_reg;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rx_done_reg <= 1'b0;
        end else begin
            rx_done_reg <= rx_done;
        end
    end
    
    // 检测接收完成的上升沿
    assign rx_done_rise = rx_done && !rx_done_reg;
    
    // 发送启动信号
    assign tx_start = rx_done_rise && !tx_busy;

endmodule

 

仿真代码:

 

`timescale 1ns/1ps

module uart_loopback_tb;

    // 输入
    reg clk;
    reg rst_n;
    reg uart_rx;
    
    // 输出
    wire uart_tx;
    
    // 测试参数
    parameter CLK_PERIOD = 20;  // 50MHz时钟周期
    parameter BIT_PERIOD = 8680; // 115200波特率的位周期(1/115200 ≈ 8.68μs)
    
    // 实例化顶层模块
tsp_uart_loop uut (
    .clk(clk),
    .rst_n(rst_n),
    .uart_rx(uart_rx),
    .uart_tx(uart_tx)
    );
    
    // 时钟生成
    always begin
        clk = 0;
        #(CLK_PERIOD/2);
        clk = 1;
        #(CLK_PERIOD/2);
    end
    
    // 测试任务:发送一个字节
    task send_byte;
        input [7:0] data;
        integer i;
        begin
            // 发送起始位
            uart_rx = 0;
            #(BIT_PERIOD);        
            // 发送8个数据位
            for (i = 0; i < 8; i = i + 1) begin
                uart_rx = data[i];
                #(BIT_PERIOD);
            end
            
            // 发送停止位
            uart_rx = 1;
            #(BIT_PERIOD);
        end
    endtask
    
    // 主测试程序
    initial begin
        // 初始化
        rst_n = 0;
        uart_rx = 1;
        
        // 复位
        #100;
        rst_n = 1;
        #100;
        
        // 测试1:发送字节 8'h55
        send_byte(8'h55);
        
        // 测试2:发送字节 8'hAA
        send_byte(8'hAA);
        
        // 测试3:发送字节 8'hF0
        send_byte(8'hF0);
        
        // 测试4:发送字节 8'h0F
        send_byte(8'h0F);
        
        // 结束仿真
        #10000;
        $stop;
    end

endmodule

 

modelsim仿真波形:

image

 

可以看到:

第一个波特位时间内,rx先发送低电平起始位,然后发送8bit数据01010101(低位在前,8'h55),最后发送高电平停止位;tx则一直是高电平。

第二个波特位时间内,rx先发送低电平起始位,然后rx发送第二个测试数据10101010(低位在前,8'hAA),最后发送高电平停止位;tx则接收到8bit数据01010101。

第三个波特位时间内,rx先发送低电平起始位,然后rx接收第二个测试数据11110000(低位在前,8'hF0),最后发送高电平停止位;tx则接收到8bit数据10101010。

第四个波特位时间内,rx先发送低电平起始位,然后rx接收第二个测试数据00001111(低位在前,8'h0F),最后发送高电平停止位;tx则接收到8bit数据11110000。

第五个波特位时间内,rx保持高电平;tx则接收到8bit数据00001111。

 

Quartus选择:25.1,参考文章 最新版Quartus Prime Pro 25.1 的安装和使用演示(含Questa仿真)

 

引脚分配:

 

c0666a38be44e20f26cb9475ba28d216

 

下板测试:

 

b94b2da378d43dc50755cc03909f04ef

 

ce00eb028d4eaf17f7c4b5cc966a892e