1. NEXYS4_DDR开发板与数码管显示基础NEXYS4_DDR是迪芝伦Digilent推出的一款基于Xilinx Artix-7 FPGA型号XC7A100TCSG324-1的开发板它自带8位共阳极数码管非常适合用来学习FPGA的数字逻辑设计。我第一次拿到这块板子时最想实现的功能就是让数码管显示数字——这看似简单但要做好却需要解决几个关键技术问题。数码管显示的核心原理其实不难理解。每个数码管由8个LED组成7段加小数点共阳极意味着所有LED的阳极连接在一起接高电平通过控制阴极信号低电平点亮来显示不同数字。比如要显示数字8就需要让abcdefg这7段全部点亮对应的阴极信号就是0000000低电平有效。但实际开发中你会发现直接控制单个数码管容易要同时控制8位数码管并且保证显示稳定不闪烁就需要用到动态扫描技术。动态扫描的本质是利用人眼的视觉暂留效应。假设我们让8个数码管轮流点亮每个管只显示1ms然后快速切换到下一个只要切换速度够快一般60Hz人眼就会觉得所有数码管是同时点亮的。这种设计能大幅减少FPGA的引脚占用只需要8个段选信号seg和8个位选信号sel就能控制64个LED。2. BCD8421编码的硬件实现技巧要让数码管显示十进制数字首先需要解决二进制到十进制的转换问题。在软件编程中我们可以直接用除法取余但在FPGA里除法器非常消耗资源。更聪明的做法是使用BCD8421编码它通过移位加3算法就能实现二进制到十进制的转换。移位加3算法的精妙之处在于它完全用硬件逻辑实现了除10取余的效果。我以十进制数59二进制00111011为例说明转换过程首先在数据高位补零变成00000000 00111011每次左移一位当某4位二进制值大于4时就对其加3经过8次移位后高4位变成0101十位5低4位变成1001个位9Verilog实现时需要注意几个细节。首先是移位次数对于n位二进制数需要移位n次。其次是数据位宽要确保移位过程中不会溢出。我在bcd_8421.v模块中使用了59位寄存器27位原始数据32位补零通过状态机控制移位和加3操作always(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n 1b0) data_shift 59d0; else if(cnt_shift 5d0) data_shift {32b0,data}; else if((cnt_shift 5d27) (shift_flag 1b0)) begin // 对每4位判断是否大于4 if(data_shift[30:27] 4) data_shift[30:27] data_shift[30:27] 3; if(data_shift[34:31] 4) data_shift[34:31] data_shift[34:31] 3; // ...其他位同理 end3. 动态扫描的时序同步问题实现动态扫描显示时最容易出现的问题就是sel信号和seg信号不同步。我在最初调试时就遇到过显示乱码的情况本该显示12345678却变成了23456781。经过逻辑分析仪抓取波形发现是seg信号比sel信号晚了一个时钟周期。这个问题看似微小却会导致严重的显示错误。因为当sel已经切换到第二位时seg还在输出第一位的数值结果就是每个数码管都显示了下一位的数字。解决方法是在生成sel信号时确保seg数据已经稳定always(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n 1b0) sel 8b0111_1111; else if(flag_1ms 1b1) begin // 这个条件判断是关键 case(cnt_sel) 3d0: sel 8b1111_1110; 3d1: sel 8b1111_1101; // ...其他位选择 endcase end另一个常见问题是显示闪烁。这通常是因为扫描频率太低导致的。根据我的实测当刷新率低于60Hz时即每个数码管显示时间长于1.6ms人眼就能明显感觉到闪烁。但频率也不是越高越好超过1kHz可能会增加功耗。经验值是保持1ms的扫描间隔这样整体刷新率在125Hz左右1ms×8位既稳定又省电。4. 完整系统集成与调试将各个模块整合成完整系统时时钟域处理尤为重要。NEXYS4_DDR提供100MHz的系统时钟而我们的数码管只需要1ms的时间基准。在data_gen.v中我使用27位计数器实现100ms的数据自增parameter CNT_100MS_MAX 27d9_999_999; // 100MHz时钟下计数1亿次1秒 always(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n 1b0) cnt_100ms 27d0; else if(cnt_100ms CNT_100MS_MAX) cnt_100ms 27d0; else cnt_100ms cnt_100ms 1b1;管脚约束文件(.xdc)的配置也需要注意电平标准。NEXYS4_DDR的IO电压是3.3V LVCMOS每个数码管信号都要正确定义set_property IOSTANDARD LVCMOS33 [get_ports {sel[0]}] set_property PACKAGE_PIN J17 [get_ports {sel[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {seg[0]}] set_property PACKAGE_PIN H15 [get_ports {seg[0]}]调试时建议分阶段验证先用常量测试数码管每个段是否能点亮然后测试BCD转换是否正确最后再整合动态扫描功能。遇到问题时可以临时缩短计数器最大值加速仿真比如把1ms计数从100000改为100这样在仿真中就能快速观察到多个扫描周期。