FPGA教程系列-Vivado AXI串口仿真测试

FPGA教程系列-Vivado AXI串口仿真测试

其实看完了高速接口,再返回来看串口,有点倒反天罡的意思了,不过还是想重新看一下串口,另外,看下大神是如何编写串口的。

照例放上讲的非常好的原文,https://blog.csdn.net/wuzhikaidetb/article/details/132725767?spm=1001.2014.3001.5501

UART串口简介

复制代码
    串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。

    串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

    "异步"两个字即意味着在数据传递的两个模块之间使用的不是同步时钟。实际上在异步串口的传输中是不需要时钟的,而是通过特定的时序来标志传输的开始(起始位--由高到低)和结束(结束位,拉高)。

通信方式在日常的应用中一般分为串行通信(serial communication)和并行通信(parallel communication)。

并行通信: 指多比特数据同时通过并行线进行传送,一般以字或字节为 单位并行进行传输。这种传输方式用的通信线多、成本高,故不宜进行远距离通信,因此并行通信一般用 于近距离的通信,通常传输距离小于30米。

串行通信:指数据在一条数据线上,一比特接一比特地按顺序传送 的方式,这一点与并行通信是不同的。这里我们以传输一个字节(8位)数据为例,在并行通信中,一个字 节的数据是在 8 条并行传输线上同时由源地传送到目的地;而在串行通信中,因为数据是在一条传输线上 一位接一位地顺序传送的,所以一个字节的数据要分8次进行传送。如果我们以T为一个时间单位的话, 那么并行通信发送一个字节的数据只需要1T的时间,而串行通信需要8T的时间,由此可以总结出串行通 信的的特点:一是节省传输线,大大降低了使用成本,二是数据传送速度慢,这一点在大位宽的数据传输 上尤为明显。综上可知,串行通信主要应用于长距离、低速率的通信场合。

串行通信一般有 2 种通信方式:同步串行通信(synchronized serial communication)异步串行通信 (asynchronous serial communication)

同步串行通信需要通信双方在同一时钟的控制下同步传输数据;异 步串行通信是指具有不规则数据段传送特性的串行数据传输。在常见的通信总线协议中,I2C,SPI 属于同 步通信而UART属于异步通信。同步通信的通信双方必须先建立同步,即双方的时钟要调整到同一个频率, 收发双方不停地发送和接收连续的同步比特流。异步通信在发送字符时,发送端可以在任意时刻开始发送 字符,所以,在UART通信中,数据起始位和停止位是必不可少的。

UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter), 它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。 UART 串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收,如图所示。 对于 PC来说它的TX要和对于FPGA来说的RX连接,同样PC的RX要和FPGA的TX连接,如果是两 个TX或者两个RX连接那数据就不能正常被发送出去或者接收到。

UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位,如下 图所示。

起始位 :当不传输数据时,UART 数据传输线通常保持高电压电平。若要开始数据传输,发送 UART 会将传输线从高电平拉到低电平并保持1个波特率周期。当接收UART检测到高到低电压跃迁时,便开始 以波特率对应的频率读取数据帧中的位。

数据帧 :数据帧包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是5位到8位。如果 不使用奇偶校验位,数据帧长度可以是9位。在大多数情况下,数据以最低有效位优先方式发送。

奇偶校验:奇偶性描述数字是偶数还是奇数。通过奇偶校验位,接收UART判断传输期间是否有数据 发生改变。电磁辐射、不一致的波特率或长距离数据传输都可能改变数据位。接收UART读取数据帧后, 将计数值为1的位,检查总数是偶数还是奇数。如果奇偶校验位为0(偶数奇偶校验),则数据帧中的1或 逻辑高位总计应为偶数。如果奇偶校验位为1(奇数奇偶校验),则数据帧中的1或逻辑高位总计应为奇数。 当奇偶校验位与数据匹配时,UART认为传输未出错。但是,如果奇偶校验位为0,而总和为奇数,或者奇 偶校验位为1,而总和为偶数,则UART认为数据帧中的位已改变。

停止位:为了表示数据包结束,发送UART将数据传输线从低电压驱动到高电压并保持1到2位时间。

UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样 的设置。数据位可选择为5、6、7、8位,其中8位数据位是最常用的,在实际应用中一般都选择8位数据 位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认),1.5或2位。

串口通信的速 率用波特率 表示,它表示每秒传输二进制数据的位数,单位是bps(位/秒),常用的波特率有9600、19200、 38400、57600 以及115200 等。波特率:即每秒传输的位数(bit) 。一般选波特率都会有9600,19200,115200等 选项。其实意思就是每秒传输这么多个比特位数(bit)。在信息传输通道中,携带数据信息的信号单元叫作码 元(因为串口是1bit进行传输的,所以其码元就代表一个二进制数),每秒通过信号传输的码元数称为码 元的传输速率,简称"波特率",常用符号"Baud"表示,其单位为"波特每秒"(Bps)。串口常见的波特率有 4800、9600、115200 等。 通信信道每秒传输的信息量称为位传输速率,简称"比特率 ",其单位为"每秒比特数 "(bps)。比特率 可由波特率计算得出,公式为比特率=波特率×单个调制状态对应的二进制位数。 如果使用的是115200的波特率,其串口的比特率为115200Bps×1bit = 115200bps,由计算得串口发送或 者接收1bit数据的时间为一个波特,即1/115200s。

在设置好数据格式及传输速率之后,UART 负责完成数据的串并转换,而信号的传输则由外部驱动电 路实现。电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准有RS232、RS422、 RS485 等,它们定义了接口不同的电气特性,如RS-232是单端输入输出,而RS-422/485为差分输入输出等。 RS-232 标准的串口最常见的接口类型为DB9,样式如图所示,工业控制领域中用到的工控机一 般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串口, 它们一般通过USB转串口线来实现与外部设备的串口通信。

Ps:DB9公头与母头的定义一定要注意!!

AXI串口的实现

普通的串口已经在网上泛滥了,最近发现Alex 大神的一些项目,有串口的通信,而且是用AXI协议的串口,本着向大神学习的念头,站在巨人的肩膀好成事。

文件结构很简单,

三个文件,一个封装,一个发射一个接收。

代码网上都有,但是测试是用的cocotb,暂时没安装,测试也不复杂,所以简单的贴上testbench。另外程序的解读留待以后的。

verilog 复制代码
`timescale 1ns / 1ps

module tb_uart;

    // 参数定义
    parameter DATA_WIDTH = 8;
    parameter CLK_PERIOD = 10; // 100MHz 时钟

    // 输入信号 (Reg)
    reg clk;
    reg rst;
    reg [DATA_WIDTH-1:0] s_axis_tdata;
    reg s_axis_tvalid;
    reg m_axis_tready;
    reg [15:0] prescale;

    // 输出信号 (Wire)
    wire s_axis_tready;
    wire [DATA_WIDTH-1:0] m_axis_tdata;
    wire m_axis_tvalid;
    wire txd;
    wire tx_busy;
    wire rx_busy;
    wire rx_overrun_error;
    wire rx_frame_error;

    // 模拟的回环连接信号
    wire rxd;

    // ----------------------------------------------------------------
    // 实例化被测模块 (Unit Under Test - UUT)
    // 引用源文件 [3], [4], [5]
    // ----------------------------------------------------------------
    uart #(
        .DATA_WIDTH(DATA_WIDTH)
    ) uut (
        .clk(clk),
        .rst(rst),
        
        // AXI Input (发送端接口)
        .s_axis_tdata(s_axis_tdata),
        .s_axis_tvalid(s_axis_tvalid),
        .s_axis_tready(s_axis_tready),
        
        // AXI Output (接收端接口)
        .m_axis_tdata(m_axis_tdata),
        .m_axis_tvalid(m_axis_tvalid),
        .m_axis_tready(m_axis_tready),
        
        // UART 物理接口
        .rxd(rxd),
        .txd(txd),
        
        // 状态信号
        .tx_busy(tx_busy),
        .rx_busy(rx_busy),
        .rx_overrun_error(rx_overrun_error),
        .rx_frame_error(rx_frame_error),
        
        // 配置
        .prescale(prescale)
    );

    // ----------------------------------------------------------------
    // 关键逻辑:硬件回环 (Loopback)
    // 将发送引脚 TX 连接到接收引脚 RX
    // ----------------------------------------------------------------
    assign rxd = txd;

    // 时钟生成
    initial begin
        clk = 0;
        forever #(CLK_PERIOD/2) clk = ~clk;
    end

    // ----------------------------------------------------------------
    // 测试流程
    // ----------------------------------------------------------------
    initial begin
        // 1. 初始化信号
        rst = 1;
        s_axis_tdata = 0;
        s_axis_tvalid = 0;
        m_axis_tready = 0;
        prescale = 16'd10; // 设置较小的分频值以加快仿真速度 [2], [1]

        // 2. 复位释放
        #(CLK_PERIOD * 10);
        rst = 0;
        #(CLK_PERIOD * 10);

        $display("--- Simulation Start ---");
        $display("Prescale set to: %d", prescale);

        // 3. 开启接收端 Ready 信号 (允许接收数据)
        // 源码 [6] 显示如果不拉高 ready,valid 信号可能无法正确握手清除
        m_axis_tready = 1;

        // 4. 发送测试数据 0x55 (二进制 01010101)
        send_byte(8'h55);
        
        // 5. 等待并检查接收
        check_received_byte(8'h55);

        // 6. 发送测试数据 0xA3 (随机值)
        send_byte(8'hA3);
        check_received_byte(8'hA3);

        // 7. 发送测试数据 0xFF (全1)
        send_byte(8'hFF);
        check_received_byte(8'hFF);

        // 结束仿真
        #(CLK_PERIOD * 100);
        $display("--- Test Passed: All bytes match ---");
        $finish;
    end

    // ----------------------------------------------------------------
    // 任务:发送一个字节 (AXI Stream Master 行为)
    // 基于 uart_tx 的输入逻辑 [7]
    // ----------------------------------------------------------------
    task send_byte;
        input [DATA_WIDTH-1:0] data;
        begin
            @(posedge clk);
            s_axis_tdata <= data;
            s_axis_tvalid <= 1;
            
            // 等待 UUT 的 ready 信号
            wait(s_axis_tready);
            @(posedge clk);
            
            // 握手完成,拉低 valid
            s_axis_tvalid <= 0;
            $display("[TX] Sent data: 0x%h at time %t", data, $time);
        end
    endtask

    // ----------------------------------------------------------------
    // 任务:检查接收到的字节 (AXI Stream Slave 行为)
    // 基于 uart_rx 的输出逻辑 [8], [6]
    // ----------------------------------------------------------------
    task check_received_byte;
        input [DATA_WIDTH-1:0] expected_data;
        begin
            // 等待数据变为 Valid
            // 注意:由于是串行传输,这里需要等待较长时间 (Bit time * 10)
            wait(m_axis_tvalid);
            @(posedge clk);

            if (m_axis_tdata === expected_data) begin
                $display("[RX] Received Match: 0x%h at time %t", m_axis_tdata, $time);
            end else begin
                $display("[RX] ERROR: Expected 0x%h, Got 0x%h at time %t", expected_data, m_axis_tdata, $time);
                $stop; // 遇到错误停止
            end
            
            // 等待 valid 信号被拉低 (由 m_axis_tready 控制的握手完成)
            wait(!m_axis_tvalid);
        end
    endtask

endmodule

下面来看下仿真:

一共发送了3次,第一次是01010101。这里有个点,串口是低位优先的,所以看时序应该反着来,而01010101看起来跟高位优先差不多,主要看第二组数据10100011.如图所示,就能看出来是低位先行了。

再仔细看下握手。仿真是做了一个回环的设计。

之所以 TX 是从机(Slave),RX 是主机(Master),是因为这里的"主/从"是针对 系统内部数据流(AXI Stream 总线) 的方向定义的,而不是针对 外部 UART 线缆 的方向。

需要从数据是谁产生的,流向哪里这个角度来理解:

  1. 为什么 TX(发送模块)是 Slave?
  • UART TX 模块的任务是接收来自系统的数据,然后把它"消化"掉(串行化发送出去)。
  • 在 AXI Stream 协议中,接收数据 的一方是 Slave(从机),发送数据的一方是 Master(主机)。
  • 因此,系统(或 DMA)是 Master,它把数据"喂"给 TX 模块,而 TX 模块作为 Slave 被动接收。
  1. 为什么 RX(接收模块)是 Master?
  • UART RX 模块的任务是产生 数据(从外部引脚解串出来的),然后把这些数据给系统。
  • 在 AXI Stream 协议中,发起/输出数据的一方是 Master。
  • 因此,RX 模块变成了 Master,它主动告诉系统:"我收到新数据了,给你!"。

为了方便记忆,可以使用打印机和扫描仪的类比:

  • TX (发送端) 就像打印机

    • 打印机是 Slave 。它不会自己写文章,它必须等待电脑(Master)发送文件给它,它才能打印。
  • RX (接收端) 就像扫描仪

    • 扫描仪是 Master 。当你扫描一张照片时,扫描仪主动产生图片数据,并发送给电脑(Slave)保存。
相关推荐
Wishell20151 天前
FPGA教程系列-乒乓操作
仿真
Terasic友晶科技1 天前
6-DE10-Nano的HDMI方块移动案例——使用Modelsim仿真I2C控制器
fpga开发·仿真·modelsim·hdmi·i2c_controller
Wishell20153 天前
FPGA教程系列-Vivado AXI串口程序解析
仿真
云雾J视界7 天前
SPICE仿真进阶:AI芯片低功耗设计中的瞬态/AC分析实战
低功耗·仿真·spice·ai芯片·ac·均值估算
FPGA小迷弟8 天前
京微齐力FPGA联合modelsim仿真操作
fpga开发·ic·verilog·fpga·仿真
Wishell20159 天前
FPGA教程系列-Vivado Aurora 8B/10B 例程修改
仿真
Wishell201510 天前
FPGA教程系列-Vivado AXI4-Full 仿真测试
仿真
Wishell201511 天前
日拱一卒之FPGA学习计划
仿真
Wishell201512 天前
日拱一卒之quartus芯片移植查看
仿真