ModelSim 仿真记录:基于地址帧 UART 的 FPGA 多外设控制系统
1. 项目概述本设计完成了一个基于 FPGA 的多外设控制系统核心功能是通过 UART 串口协议接收控制命令再根据命令控制开发板上的 LED、蜂鸣器、数码管、电机、8×8 点阵以及 LCD1602 本地菜单模块。本文主要记录 ModelSim 仿真过程包括仿真工程搭建、波形观察方法、UART 帧解析方法以及各模块关键信号的观察思路。Quartus 综合、引脚分配和下载到开发板的部分放在后续文章中。系统采用地址帧通信方式串口接收的数据不再是单字节直接控制而是按照固定格式组成一帧完整命令帧头 地址 命令 校验例如66 01 11 10其中66帧头用于标识一帧数据的开始01设备地址用于区分不同从机11控制命令例如控制 LED 进入某种显示模式10校验值用于判断地址和命令是否在传输过程中出错。2. 开发板与外设资源本设计使用 QC-FPGA-C1 FPGA 开发板套件。开发板板载 FPGA 芯片、时钟晶振、LED、数码管、蜂鸣器、点阵、按键、LCD1602 接口以及电机驱动接口适合完成基础数字系统和嵌入式 FPGA 综合实验。开发板主要资源如下FPGACyclone 系列 FPGA工程目标器件为EP1C3T144时钟板载 48 MHz 有源晶振原理图中 OSC1 输出CLK0下载器USB-Blaster串口通信可使用 USB 转串口线或蓝牙透明串口模块显示模块LED、四位七段数码管、8×8 LED 点阵、LCD1602输入模块拨码开关、矩阵按键执行模块无源蜂鸣器、L9110 电机驱动接口。图 1 QC-FPGA-C1 开发板及主要外设资源在仿真阶段重点不是观察真实硬件现象而是验证 RTL 逻辑是否正确。例如 UART 是否能正确接收帧校验是否有效命令是否能正确转换为各模块的控制信号。3. ModelSim 仿真的作用在 FPGA 开发流程中ModelSim 主要用于 RTL 仿真。它可以在不下载到开发板的情况下提前观察代码逻辑是否符合设计要求。本设计中 ModelSim 主要用于检查以下内容UART 接收器是否能识别起始位、数据位、校验位和停止位地址帧协议是否能正确识别66 地址 命令 校验校验正确时是否产生cmd_valid有效脉冲LED、蜂鸣器、数码管、电机、点阵等模块是否根据命令切换模式LCD1602 与按键菜单逻辑是否能独立产生本地控制命令顶层模块连接是否正确。ModelSim 仿真通过测试文件 testbench 主动产生时钟、复位和 UART 输入数据再观察输出信号是否符合预期。4. 仿真文件结构工程中通常包含两类文件src/ fpga_rs232_top.vhd 顶层模块 uart_rx.vhd UART 接收模块 uart_tx.vhd UART 发送模块 uart_frame_parser.vhd 地址帧解析模块 command_controller.vhd 命令控制模块 led_ctrl.vhd LED 控制模块 buzzer_ctrl.vhd 蜂鸣器控制模块 seg_ctrl.vhd 数码管控制模块 motor_ctrl.vhd 电机控制模块 dot_matrix_ctrl.vhd 点阵控制模块 lcd1602_ctrl.vhd LCD1602 显示模块 key_scan.vhd 矩阵按键扫描模块 sim/ tb_fpga_rs232_top.vhd 顶层仿真文件 run_msim_gui.do ModelSim 自动仿真脚本其中tb_fpga_rs232_top.vhd不会下载到 FPGA 中它只用于仿真。真正烧入开发板的是src目录下的设计文件。5. UART 地址帧协议5.1 串口字节格式UART 是异步串行通信方式。空闲时串口线保持高电平发送一个字节时先拉低产生起始位然后从低位到高位依次发送 8 位数据再发送校验位最后发送停止位。本设计采用 8E1 格式1 位起始位 8 位数据位 1 位偶校验位 1 位停止位也就是Start D0 D1 D2 D3 D4 D5 D6 D7 Even Parity Stop需要注意UART 的 8 位数据是串行传输的但接收完成后会在 FPGA 内部形成一个 8 位并行数据rx_data。因此报告中出现“8 位并行数据”并不是说 UART 线上同时传输了 8 根数据线而是指接收模块完成串并转换后的内部数据。5.2 地址帧格式单个 UART 字节只能表示一个数据。为了让通信更可靠也方便以后扩展多机通信系统增加了帧头、地址和校验。本设计使用的帧格式为66 ADDR CMD CHECK例如 LED 跑马灯命令66 01 11 10校验采用异或方式CHECK ADDR xor CMD所以01 xor 11 10接收模块只有在帧头、地址和校验都正确时才会输出有效命令。这样可以避免串口误码或错误数据直接控制外设。6. ModelSim 仿真基本流程6.1 打开 ModelSim进入工程目录后可以在 ModelSim 中执行.do脚本也可以手动创建工程并添加文件。为了减少重复操作推荐使用脚本方式。常用脚本示例如下vlib work vmap work work vcom ../src/uart_rx.vhd vcom ../src/uart_tx.vhd vcom ../src/uart_frame_parser.vhd vcom ../src/command_controller.vhd vcom ../src/led_ctrl.vhd vcom ../src/buzzer_ctrl.vhd vcom ../src/seg_ctrl.vhd vcom ../src/motor_ctrl.vhd vcom ../src/dot_matrix_ctrl.vhd vcom ../src/fpga_rs232_top.vhd vcom tb_fpga_rs232_top.vhd vsim work.tb_fpga_rs232_top如果 Transcript 窗口中显示Errors: 0说明语法编译通过可以继续观察波形。6.2 添加波形信号进入仿真后需要把关键信号加入 Wave 窗口。常见信号如下add wave -divider Clock and Reset add wave /tb_fpga_rs232_top/clk add wave /tb_fpga_rs232_top/rst_n add wave -divider UART add wave /tb_fpga_rs232_top/uart_rxd add wave /tb_fpga_rs232_top/uart_txd add wave /tb_fpga_rs232_top/uut/rx_data add wave /tb_fpga_rs232_top/uut/rx_valid add wave -divider Frame Parser add wave /tb_fpga_rs232_top/uut/cmd_data add wave /tb_fpga_rs232_top/uut/cmd_valid add wave /tb_fpga_rs232_top/uut/resp_code add wave -divider Outputs add wave /tb_fpga_rs232_top/uut/led_mode add wave /tb_fpga_rs232_top/LedOut add wave /tb_fpga_rs232_top/uut/buzzer_mode add wave /tb_fpga_rs232_top/Bell add wave /tb_fpga_rs232_top/uut/seg_value add wave /tb_fpga_rs232_top/dataout add wave /tb_fpga_rs232_top/en add wave /tb_fpga_rs232_top/uut/dot_mode add wave /tb_fpga_rs232_top/dot_row add wave /tb_fpga_rs232_top/dot_col添加完成后运行run 4 ms如果仿真时间太短可能只能看到 UART 接收过程看不到 LED、蜂鸣器、点阵等慢速模块的明显变化。对于慢速显示模块可以适当延长仿真时间或者在 testbench 中使用加速参数。7. Wave 窗口观察方法7.1 波形放大与缩小ModelSim 的 Wave 窗口中常用观察方法如下点击放大镜放大当前时间区域点击放大镜-缩小当前时间区域点击Zoom Full显示完整仿真时间用鼠标在时间轴上拖选一段区域再选择Zoom Range使用黄色 Cursor 定位某一时刻观察左侧信号当前值。UART 的每一位持续时间较短而 LED 和数码管扫描时间相对较长所以不能用同一个缩放比例观察所有模块。观察 UART 时要放大到微秒级观察 LED 或蜂鸣器时可以缩小到毫秒级。7.2 单独显示 Wave 窗口ModelSim 默认会把 Source、Objects、Wave 等窗口放在同一个界面中。为了截图更清晰可以把 Wave 窗口单独显示打开菜单View - Wave找到下方或中间的Wave标签页拖动 Wave 标签到屏幕空白区域形成独立窗口或者使用Window - Tile Vertically将窗口平铺调整左侧变量名区域宽度保证变量名完整显示。截图时建议只保留当前模块相关信号不要把所有信号全部放在一张图里。这样变量名、数值和波形变化都会更清楚。7.3 设置信号显示进制总线信号默认可能显示为二进制也可能显示为十六进制。观察 UART 命令时建议设置为十六进制。设置方法在 Wave 左侧选中信号右键选择RadixUART 数据、命令码选择HexadecimalLED、点阵行列可以选择Binary计数器可以选择Unsigned。例如rx_data显示为8h66、8h01、8h11LedOut显示为3b001、3b010dot_row、dot_col显示为二进制更直观。8. UART 接收波形分析UART 接收时重点观察uart_rxd、rx_data和rx_valid。当 testbench 发送命令66 01 11 10波形中可以看到uart_rxd先后出现 4 个字节的串行波形。每个字节都包含起始位、8 位数据位、偶校验位和停止位。接收完成一个字节后rx_data更新为当前收到的字节rx_valid产生一个时钟周期的高电平帧解析模块继续判断该字节属于帧头、地址、命令还是校验。图 2 UART 接收 66 01 11 10 地址帧波形图中需要重点观察uart_rxd串口输入线空闲为高电平rx_data接收完成后的 8 位数据rx_valid字节接收完成标志cmd_data解析出的命令字节cmd_valid完整帧校验通过后的命令有效脉冲。如果只看rx_data会感觉数据突然变成了8h66或8h11。这是正常现象因为rx_data是串并转换后的结果真正一位一位传输的过程要看uart_rxd。9. 地址帧解析波形分析帧解析模块收到 UART 字节后会按照状态机依次判断等待帧头 - 接收地址 - 接收命令 - 接收校验 - 输出有效命令以命令66 01 11 10为例解析过程如下收到66认为一帧开始收到地址01与本机地址一致收到命令11暂存为待执行命令收到校验10计算结果正确输出cmd_valid 1同时cmd_data 8h11。图 3 地址帧解析与 cmd_valid 输出波形cmd_valid一般只保持一个系统时钟周期它的作用是通知后级控制模块“当前命令有效可以执行”。如果校验错误cmd_valid不会拉高外设状态也不会变化。10. LED 控制波形分析LED 模块根据led_mode选择不同显示方式。常见模式包括跑马灯、流水灯和呼吸灯。例如发送66 01 11 10解析成功后led_mode进入对应模式LedOut开始按照设定节奏变化。跑马灯模式流水灯模式呼吸灯模式图 4 LED 控制模式与 LedOut 输出波形观察 LED 波形时需要注意cmd_valid出现后led_mode才会更新LedOut不一定马上快速变化因为 LED 显示通常经过分频如果仿真时间太短可能只能看到模式切换看不到完整流水效果在仿真中可以缩短 LED 分频计数值使波形更容易观察。硬件上 48 MHz 时钟非常快人眼无法直接分辨每个时钟周期因此 LED 模块必须使用计数器分频。假设某模式计数到N后移动一次则流水灯频率约为f_led 48 MHz / N实际报告中应根据代码里的计数值计算具体频率。11. 蜂鸣器控制波形分析无源蜂鸣器不能直接写入一个固定高低电平来播放不同音调而是需要 FPGA 输出一定频率的方波。不同频率对应不同音高。蜂鸣器模块一般包含两类功能报警模式输出固定频率的间歇方波电子琴模式根据音符命令输出不同频率的方波。例如进入电子琴模式后再发送音符命令66 01 22 23 66 01 31 30其中22表示进入电子琴模式31表示播放某个音符。图 5 蜂鸣器模式与 Bell 输出波形观察时重点看buzzer_mode是否进入对应模式Bell是否输出周期性方波不同音符命令下Bell的周期是否发生变化。频率计算公式为f_bell 48 MHz / (2 × half_period)其中half_period是蜂鸣器方波半周期计数值。因为一个完整方波包含高电平和低电平两个半周期所以公式中需要乘以 2。12. 数码管控制波形分析四位数码管通常采用动态扫描方式。FPGA 在很短时间内轮流点亮不同位数码管由于人眼视觉暂留看起来像四位同时显示。数码管相关信号一般包括seg_value需要显示的数据dataout段选信号en位选信号。数码管段码由 a、b、c、d、e、f、g、dp 等段组成。例如显示数字0时一般需要点亮 a、b、c、d、e、f关闭 g显示数字1时只需要点亮 b、c。具体高低电平是否取反要根据开发板原理图判断数码管是共阳还是共阴。图 6 数码管输出波形仿真中重点不是看人眼显示效果而是看en是否周期性切换不同位dataout是否随当前位显示内容改变输入数字命令后seg_value是否正确更新。13. 点阵控制波形分析8×8 点阵同样采用扫描显示。FPGA 逐行或逐列刷新点阵通过快速扫描形成完整图案。点阵相关信号通常包括dot_mode点阵显示模式dot_row行选择dot_col列数据。图 10 8×8 点阵扫描波形点阵代码中通常会把图案写成 8 个字节每个字节对应一行或一列。例如某一行数据为00111100表示该行中间 4 个 LED 点亮两侧 LED 熄灭。如果显示爱心、笑脸等图案本质上就是把每一行的亮灭状态提前写入 ROM 数组然后由扫描计数器循环输出。14. 常见波形问题分析14.1 为什么波形里只看到8hA1、8hA3之类的数据这些通常是系统返回码不是乱码。例如命令执行成功后发送模块可能返回66 01 A1 A0其中A1表示 LED 命令应答校验字节仍然由地址和返回码计算得到。14.2 为什么rx_data看不到每一位变化rx_data是接收完成后的并行结果。如果要看 UART 每一位的传输过程需要观察uart_rxd如果要看接收完成后的字节结果就观察rx_data和rx_valid。14.3 为什么时钟看起来太密集系统时钟为 48 MHz周期约为1 / 48 MHz ≈ 20.83 ns在毫秒级波形窗口里时钟当然会密成一整条。观察 UART 或外设状态时不需要一直盯着clk只需要确认时钟存在即可。分析重点应放在rx_valid、cmd_valid、cmd_data和外设输出信号上。14.4 为什么运行很久 LED 也没明显变化原因通常有两个LED 分频计数较大仿真时间不够多条 UART 命令发送间隔太短模式刚切换又被下一条命令覆盖。解决方法对 LED 单独写 testbench在仿真参数中缩短分频计数延长命令之间的等待时间对目标时间段使用 Wave 缩放观察。14.5 UART 接收为什么需要很多个时钟UART 必须按照波特率采样。如果系统时钟是 48 MHz波特率是 9600则每个 UART bit 需要的系统时钟数约为48,000,000 / 9600 5000所以“5000 个时钟”不是额外等待而是 9600 波特率本身对应的采样周期。如果波特率改为 115200则每 bit 约为48,000,000 / 115200 ≈ 416.7因此波特率越高每个 bit 对应的系统时钟数越少通信速度也越快。15. 仿真结论通过 ModelSim 仿真可以确认UART 接收模块能够正确识别起始位、数据位、偶校验位和停止位地址帧协议能够正确解析66 地址 命令 校验校验正确时系统能够输出cmd_valid有效脉冲LED、蜂鸣器、数码管、点阵、电机等模块能够根据命令切换工作状态LCD1602 本地菜单模块可以通过按键产生本地控制逻辑ModelSim 波形能够在下载开发板前提前暴露时序、协议和模块连接问题。ModelSim 仿真的重点不是“看到和实物完全一样的效果”而是通过波形确认每一级逻辑是否按预期工作。对于 UART 这类串行协议重点观察起始位、数据位、校验位、停止位以及接收完成后的rx_data和rx_valid对于外设模块重点观察模式寄存器和最终输出信号是否正确变化。后续进入 Quartus 流程时可以在 ModelSim 仿真通过的基础上继续完成综合、引脚分配、时序检查和 FPGA 下载。

相关新闻