1. 从零开始:搭建你的第一个VHDL动态扫描工程
大家好,我是老张,在数字电路和FPGA这块摸爬滚打了十几年。今天想和大家聊聊一个非常经典、但又让很多新手朋友头疼的实战项目:用VHDL实现数码管的动态扫描显示,并且用Quartus和ModelSim把它完整地仿真出来。我知道,光是看到“动态扫描”、“仿真”这些词,可能有些朋友就开始发怵了,觉得是不是特别复杂?别担心,我今天就用最“大白话”的方式,带你走一遍整个流程,把我自己踩过的坑、总结的技巧都分享给你,保证你跟着做一遍就能上手。
咱们先搞清楚这个项目到底要干嘛。想象一下,你手头有一个FPGA开发板,上面有8个数码管。如果你想让8个数码管同时显示不同的数字,比如显示你的学号“20240001”,最“笨”的办法是给每个数码管单独配一套驱动电路,但这需要占用大量的硬件引脚,非常不经济。而动态扫描就是一种“聪明”的省资源办法。它的原理很像我们人眼的视觉暂留现象:在极短的时间内,依次快速点亮每一个数码管,只要这个轮转的速度足够快(比如每秒几十次),由于人眼的视觉残留,我们看起来就像是8个数码管在同时稳定地显示。这个项目,就是用VHDL语言,在Quartus里编写出实现这个“快速轮转点亮”逻辑的电路,然后用ModelSim这个仿真工具,像看“慢动作回放”一样,验证我们的电路设计到底对不对,波形是不是按照我们预想的那样在变化。
所以,这个实战的核心就是两件事:一是用VHDL把动态扫描的逻辑写对,二是用仿真工具确保它真的对。很多朋友代码写完了,下载到板子上发现显示乱码或者闪烁,根本原因往往就是仿真没做到位,脑子里想的和电路实际干的不一致。接下来,我就手把手带你过一遍,从打开Quartus到在ModelSim里看到完美的波形图。
2. 工程创建与VHDL代码编写实战
2.1 项目初始化与文件保存的“坑”
首先,你得确保Quartus Prime(我用的是18.1版本,但13.0、17.1等版本流程大同小异)和ModelSim-Altera(现在叫Intel FPGA Starter Edition)已经安装好。这里有个小建议,安装路径尽量用英文,不要有空格和中文,这是为了避免后续一些莫名其妙的工具链链接错误。
打开Quartus,第一步不是急着写代码,而是先创建工程。点击 File -> New Project Wizard。这里会有一系列向导页面,我挑重点说。第一个“坑”就在目录选择上。我强烈建议你为这个项目单独创建一个全新的文件夹,比如我放在 D:\FPGA_Project\dynamic_scan。在“Project name”里,给你的工程起个名,比如 scan_demo。工程名和顶层实体名强烈建议保持一致,比如都叫 scan_demo,这样能避免很多混淆。在“Top-level design entity”那一栏就填 scan_demo。
一路“Next”,直到选择器件型号。如果你是针对具体的开发板(比如DE2-115),就选择对应的Cyclone IV EP4CE115F29C7。如果只是做仿真学习,可以选一个比较通用的、资源足够的器件,比如Cyclone 10 LP系列的某个型号,或者直接选“Auto device selected by the Filter”。这一步对纯仿真影响不大,但好的习惯要从开始养成。
工程创建好后,我们再新建VHDL文件。File -> New -> VHDL File。现在,我们来构思代码。一个典型的动态扫描模块需要几个部分:一个时钟输入 clk,一个位选输出 sel(决定当前点亮哪个数码管),一个段选输出 seg(决定当前点亮的数码管显示什么字形)。核心是一个计数器,用时钟 clk 来驱动,计数器的值循环变化(比如0到7),这个值一方面通过译码产生 sel 信号,另一方面作为查找表的索引,输出对应的 seg 值。
我写一个显示固定数字序列“12345678”的示例代码,里面包含了一些关键技巧:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all; -- 使用无符号数运算
entity scan_demo is
port (
clk : in std_logic; -- 系统时钟,比如50MHz
sel : out std_logic_vector(2 downto 0); -- 3位位选,可控制2^3=8个数码管
seg : out std_logic_vector(6 downto 0) -- 7段输出,从a到g
);
end entity scan_demo;
architecture behavior of scan_demo is
-- 内部信号声明
signal counter : std_logic_vector(19 downto 0) := (others => '0'); -- 分频计数器
signal scan_clk : std_logic; -- 扫描时钟(分频后)
signal scan_cnt : std_logic_vector(2 downto 0) := "000"; -- 扫描计数器(0-7)
-- 数字0-9的七段码(共阳极数码管,0点亮,1熄灭)。这里以共阳极为例。
type seg_rom is array (0 to 9) of std_logic_vector(6 downto 0);
constant seg_data : seg_rom := (
"0000001", -- 0
"1001111", -- 1
"0010010", -- 2
"0000110", -- 3
"1001100", -- 4
"0100100", -- 5
"0100000", -- 6
"0001111", -- 7
"0000000", -- 8
"0000100"


903

被折叠的 条评论
为什么被折叠?



