软件版本: Quartus II 9.1 (32-Bit) 项目: 三层电梯控制器 VHDL 设计 适用对象: 本课程设计项目的仿真与验证
- 双击桌面 Quartus II 9.1 图标,或通过开始菜单启动
- 等待软件完全加载(首次启动可能需要 30 秒左右)
- 点击菜单栏 File → New Project Wizard...
- 弹出 New Project Wizard 向导窗口,点击 Next
- What is the working directory for this project?(工程目录)
- 点击右侧 ... 按钮浏览到你想存放工程的文件夹
- 例如:
D:\FPGA-Elevator-controller\quartus - 如果文件夹不存在,请先在文件资源管理器中创建它
- What is the name of this project?(工程名称)
- 输入:
elevator_top - 重要: 工程名称必须与顶层模块名称一致!
- 输入:
- What is the name of the top-level design entity for this project?(顶层实体名)
- 输入:
elevator_top(应自动填充,与工程名相同)
- 输入:
- 点击 Next
- 出现 "Add Files" 页面
- 可以在这里添加文件,也可以之后再添加
- 如果要现在添加,点击 ... 按钮,浏览到
src/文件夹,依次选择所有.vhd文件 - 点击 Next
- Family(器件系列)下拉框选择你实验箱对应的 FPGA 系列:
- 如果是 Cyclone II 系列 → 选
Cyclone II - 如果是 Cyclone III 系列 → 选
Cyclone III - 如果是 MAX II (CPLD) → 选
MAX II - 不确定就先选
Cyclone II(最常见的教学板)
- 如果是 Cyclone II 系列 → 选
- Available devices 列表中选择具体型号:
- Cyclone II 常用:
EP2C5T144C8或EP2C8T144C8 - Cyclone III 常用:
EP3C5E144C8 - MAX II 常用:
EPM240T100C5或EPM570T144C5 - 仿真阶段可以随意选择一个,不影响功能仿真结果
- Cyclone II 常用:
- 点击 Next
- 保持默认设置即可(全部
<None>) - 点击 Next
- 确认工程信息摘要
- 点击 Finish
- 工程创建完成,你会看到主界面左侧的 Project Navigator 面板
- 打开文件资源管理器
- 将
src/文件夹下的所有 9 个.vhd文件复制到工程目录quartus/下:elevator_top.vhd clk_divider.vhd debounce.vhd request_handler.vhd elevator_ctrl.vhd door_controller.vhd display_driver.vhd alarm_controller.vhd buzzer_driver.vhd
- 点击菜单栏 Project → Add/Remove Files in Project...
- 弹出 Settings 对话框,左侧选中 Files
- 点击右侧 ... 按钮(File name 右边)
- 浏览到工程目录,选择所有 9 个
.vhd文件:- 可以按住 Ctrl 键多选
- 或者一个一个添加
- 每选择一个文件后点击 Add
- 确认右侧文件列表中有全部 9 个文件
- 点击 OK
- 在左侧 Project Navigator 面板中,确认 Entity 标签页下能看到
elevator_top - 如果看不到或显示错误:
- 点击菜单 Project → Set as Top-Level Entity
- 在弹出框中输入
elevator_top
- 点击菜单 Project → Add/Remove Files in Project...
- 确认 Files 列表中有 9 个
.vhd文件 - 每个文件的 Type 列应显示
VHDL
先做分析检查语法错误,再完整编译。
- 点击菜单 Processing → Start → Start Analysis & Elaboration
- 或者点击工具栏上的 ▶ Start Analysis & Elaboration 按钮(蓝色小三角旁边带放大镜图标)
- 等待编译完成(下方 Messages 窗口会滚动显示日志)
- 检查结果:
- ✅ 成功: Messages 窗口最后一行显示
Info: Quartus II Analysis & Elaboration was successful - ❌ 失败: 出现红色 Error 信息,需要根据错误提示修改代码
- ✅ 成功: Messages 窗口最后一行显示
如果出现错误:
- 在 Messages 窗口中找到红色 Error 行
- 双击错误信息,会自动跳转到出错的 VHDL 文件和行号
- 常见错误及解决方法:
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
Error: VHDL syntax error at xxx.vhd(N) |
语法错误 | 检查第 N 行附近的拼写、分号、括号 |
Error: can't find matching entity |
component 与 entity 不匹配 | 检查端口名称和类型是否完全一致 |
Error: width mismatch |
信号宽度不一致 | 检查 std_logic_vector 的范围定义 |
Warning: inferred latch |
组合逻辑产生了锁存器 | 确保 case 有 when others,所有输出有默认值 |
语法检查通过后,进行完整编译:
- 点击菜单 Processing → Start Compilation
- 或点击工具栏上的 ▶ Start Compilation 按钮(紫色大三角)
- 或按快捷键 Ctrl + L
- 等待编译完成(可能需要 1-3 分钟)
- 编译成功后,在 Compilation Report 中可以看到:
- Flow Summary: 总资源使用
- Fitter: 布局布线结果
- Timing Analyzer: 时序分析结果
- 在 Messages 窗口查看 Warning 信息
- 必须关注的警告:
Warning (10631): VHDL Process Statement warning... inferred latch→ 必须修复!锁存器问题Warning (13024): Output pins stuck at VCC or GND→ 未使用的输出引脚,可忽略Warning (15714): Some pins have incomplete I/O assignments→ 引脚未分配,仿真阶段可忽略
- 可以忽略的警告:引脚分配相关(仿真阶段不需要分配引脚)
- 点击菜单 File → New...
- 在弹出的 New 对话框中展开 Verification/Debugging Files 分类
- 选择 Vector Waveform File
- 点击 OK
- 打开一个空白的波形编辑器窗口
因为实际时钟是 50MHz(周期 20ns),但电梯运行需要秒级观察。如果用真实时钟仿真,观察一次完整开门-等待-关门流程需要模拟 10 秒 = 500,000,000 个时钟周期,非常耗时。
推荐方案:直接使用 1Hz 慢时钟作为仿真时钟
为了快速验证功能逻辑,我们可以:
- 将
clk信号的周期设为一个较大值(如 100ns) - 修改
clk_divider.vhd中的分频常量为很小的值(仅用于仿真) - 或者单独仿真各子模块,用 1Hz 时钟直接驱动
方案 A:仿真顶层模块(推荐新手)
如果仿真顶层模块 elevator_top,需要修改 clk_divider.vhd 的分频常量来加速仿真。
临时修改 clk_divider.vhd 用于仿真(仿真结束后改回!):
-- 仿真用分频常量(改小以加速仿真)
-- 原始: CNT_1HZ_MAX = 24999999 (50MHz->1Hz)
-- 仿真: CNT_1HZ_MAX = 4 (将 clk 设为 10Hz 时,5个周期产生1Hz)
constant CNT_1HZ_MAX : std_logic_vector(24 downto 0) := (2 => '1', others => '0'); -- 4
-- 同理修改其他常量
constant CNT_100HZ_MAX : std_logic_vector(17 downto 0) := (0 => '1', others => '0'); -- 1
constant CNT_500HZ_MAX : std_logic_vector(15 downto 0) := (0 => '1', others => '0'); -- 1
constant CNT_1KHZ_MAX : std_logic_vector(14 downto 0) := (0 => '1', others => '0'); -- 1方案 B:直接仿真核心子模块 elevator_ctrl(推荐高效验证)
直接仿真 elevator_ctrl 模块,手动控制 clk_1hz、req_up/req_dn/req_go、door_opened/door_closed 等输入信号。这样不需要修改任何代码,仿真速度快,且能精确控制每个时钟周期的输入。
本教程以方案 B 为主进行演示。
⚠️ 如果你打算仿真子模块(方案 B),必须先完成此步骤,否则 Node Finder 中将找不到信号!
- 点击菜单 Assignments → Settings...
- 左侧 Category 选择 General(通常是默认选中的第一项)
- 找到右侧的 Top-level entity 输入框(当前显示
elevator_top) - 将内容改为
elevator_ctrl(可以直接修改,也可以点击右边的 ... 按钮浏览选择) - 下方的 Recently selected top-level entities 不需要修改(这只是历史记录)
- 点击 OK
- 重新编译:点击 Processing → Start → Start Analysis & Elaboration,等待编译成功
- 编译成功后,继续下面的步骤
💡 仿真完成后如需恢复,重复以上步骤将 Top-level entity 改回
elevator_top即可。
- 在波形编辑器中,点击菜单 Edit → End Time...
- 弹出 End Time 对话框,你会看到:
- Time: 默认值为
1.0,右边下拉框为us - Default extension options: 默认
Last clock pattern - End time extension per signal: 空列表
- Time: 默认值为
- 将 Time 输入框中的值从
1.0改为5.0,单位保持 us 不变- 5 us = 5000 ns,每个时钟周期设为 100ns,则有 50 个时钟周期,足够完成一个完整场景
- 下方的 Default extension options 和 End time extension per signal 不需要修改
- 点击 OK
- 点击菜单 Edit → Grid Size...
- 设置 Period: 50 ns(半个时钟周期)
- 点击 OK
方法一:通过 Node Finder 添加(推荐)
- 在波形编辑器窗口的左侧空白区域(信号名列表区),双击空白处
- 或者右键点击 → Insert → Insert Node or Bus...
- 弹出 Insert Node or Bus 对话框
- 点击 Node Finder... 按钮
- 在 Node Finder 对话框中:
- Filter: 选择
Pins: all(显示所有端口引脚) - Look in: 确认是
elevator_ctrl(或你要仿真的模块名) - 点击 List 按钮
- Filter: 选择
- 左侧 Nodes Found 列表会显示该模块的所有输入输出端口
- 选中需要的信号,点击 > 按钮将其移到右侧 Selected Nodes 列表
- 可以按住 Ctrl 多选
- 或点击 >> 全部添加
- 点击 OK → 再点击 OK
方法二:手动输入信号名
- 在 Insert Node or Bus 对话框中
- 在 Name 栏直接输入信号全名(如
clk_1hz) - 点击 OK
仿真 elevator_ctrl 模块时,按以下顺序添加信号:
| 序号 | 信号名 | 方向 | 类型 | 说明 |
|---|---|---|---|---|
| 1 | clk_1hz |
Input | Clock | 主时钟 |
| 2 | rst_n |
Input | 单 bit | 复位信号 |
| 3 | req_up[2..0] |
Input | 3-bit Bus | 上行请求 |
| 4 | req_dn[2..0] |
Input | 3-bit Bus | 下行请求 |
| 5 | req_go[2..0] |
Input | 3-bit Bus | 轿厢目标 |
| 6 | any_request |
Input | 单 bit | 请求有效标志 |
| 7 | door_closed |
Input | 单 bit | 门已关闭 |
| 8 | door_opened |
Input | 单 bit | 门已打开 |
| 9 | alarm_active |
Input | 单 bit | 报警信号 |
| 10 | current_floor[1..0] |
Output | 2-bit Bus | 当前楼层 |
| 11 | direction |
Output | 单 bit | 运动方向 |
| 12 | motor_en |
Output | 单 bit | 电机使能 |
| 13 | door_open_cmd |
Output | 单 bit | 开门命令 |
| 14 | door_close_cmd |
Output | 单 bit | 关门命令 |
| 15 | clear_req[2..0] |
Output | 3-bit Bus | 请求清除 |
| 16 | state_out[3..0] |
Output | 4-bit Bus | 状态编码 |
- 在波形窗口中点击选中
clk_1hz信号(点击左侧信号名使整行高亮) - 右键点击 → 选择 Value → Clock...
- 或点击工具栏上的时钟图标 🕐
- 在 Clock 设置对话框中:
- Period:
100 ns(一个完整时钟周期) - Duty cycle:
50 % - Offset:
0 ns - Phase: 保持默认
- Period:
- 点击 OK
- 时钟波形出现在波形窗口中(方波)
-
选中
rst_n信号 -
设置为初始低电平(复位有效),然后拉高:
- 方法: 用鼠标在波形上选择时间段,然后设置电平值
详细操作:
a. 先设置整段为高电平:
- 点击
rst_n信号名使整行选中 - 点击工具栏上的 Forcing High (1) 按钮(或右键 → Value → Forcing High)
b. 再设置前两个周期为低电平(复位):
- 用鼠标在
rst_n波形上从 0ns 拖动到 200ns(选中前两个时钟周期) - 释放鼠标后,选中区域会变为高亮蓝色
- 点击工具栏上的 Forcing Low (0) 按钮
结果:
rst_n在 0~200ns 为低电平(复位),200ns 之后为高电平(正常工作)
所有输入信号需要设置初始值,否则仿真结果不确定!
req_up[2..0]: 选中 → Forcing Low (0)(全部设为 "000",无请求)req_dn[2..0]: 选中 → Forcing Low (0)req_go[2..0]: 选中 → Forcing Low (0)any_request: 选中 → Forcing Low (0)door_closed: 选中 → Forcing High (1)(初始门为关闭状态)door_opened: 选中 → Forcing Low (0)alarm_active: 选中 → Forcing Low (0)(无报警)
- 点击 File → Save As...
- 保存到工程目录下,文件名如
elevator_ctrl_test1.vwf - 点击 Save
- 点击菜单 Assignments → Settings...
- 左侧导航树选择 Simulator Settings
- 设置:
- Simulation mode: 选择
Functional(功能仿真) - Simulation input: 点击 ... 选择刚才保存的
.vwf文件
- Simulation mode: 选择
- 点击 OK
- 点击菜单 Processing → Generate Functional Simulation Netlist
- 等待生成完成
- 成功提示:
Info: Quartus II Generate Functional Simulation Netlist was successful
- 点击菜单 Processing → Start Simulation
- 或按快捷键 Ctrl + I 虽然是9.1版本不一定有
- 等待仿真完成(通常几秒钟)
- 成功后自动弹出仿真结果波形窗口
仿真完成后会弹出 Simulation Report 窗口,其中包含 Simulation Waveforms 部分。
- 展开 Simulation Waveforms 节点
- 双击波形图可以放大查看
- 使用工具栏的缩放工具调整波形显示:
- 🔍+ (Zoom In): 放大波形
- 🔍- (Zoom Out): 缩小波形
- 🔍 (Zoom Fit): 适应窗口大小
- 在波形窗口中单击鼠标左键,放置参考光标(黄色竖线)
- 光标位置和时间显示在窗口底部
- 对比不同时刻的信号值来验证功能
- 将鼠标指针悬停在波形上,会显示当前时刻的信号值
- 或者在左侧信号列表中查看当前光标位置的信号值
对于总线信号(如 state_out[3..0]),可以切换显示方式:
- 右键点击总线信号名
- 选择 Properties...
- 在 Radix 下拉框中选择显示格式:
Binary— 二进制(如 "0101")Hexadecimal— 十六进制(如 "5")Unsigned Decimal— 无符号十进制(如 "5")
- 对于
state_out推荐用十六进制,方便对照状态编码表 - 对于
current_floor推荐用无符号十进制
| state_out (HEX) | state_out (BIN) | 状态名 | 含义 |
|---|---|---|---|
| 0 | 0000 | S_IDLE | 空闲等待 |
| 1 | 0001 | S_DOOR_OPENING | 开门中 |
| 2 | 0010 | S_DOOR_OPEN | 门开等待 |
| 3 | 0011 | S_DOOR_CLOSING | 关门中 |
| 4 | 0100 | S_CHECK_REQ | 检查请求 |
| 5 | 0101 | S_MOVING_UP | 上升运行 |
| 6 | 0110 | S_MOVING_DOWN | 下降运行 |
| 7 | 0111 | S_ARRIVED | 到达楼层 |
| 8 | 1000 | S_RETURN_HOME | 返回1楼 |
| 9 | 1001 | S_ALARM | 报警 |
| current_floor (DEC) | current_floor (BIN) | 实际楼层 |
|---|---|---|
| 0 | 00 | 1楼 |
| 1 | 01 | 2楼 |
| 2 | 10 | 3楼 |
以下提供 8 个完整仿真场景,每个场景详细说明输入信号设置和预期输出。
测试目的: 验证复位后电梯在 1 楼,门关闭,状态为 IDLE
输入信号设置:
| 信号 | 0~200ns | 200ns~5us |
|---|---|---|
| clk_1hz | 100ns 周期方波 | 同左 |
| rst_n | 0(复位) | 1(释放) |
| req_up | 000 | 000 |
| req_dn | 000 | 000 |
| req_go | 000 | 000 |
| any_request | 0 | 0 |
| door_closed | 1 | 1 |
| door_opened | 0 | 0 |
| alarm_active | 0 | 0 |
操作步骤:
- 所有请求信号保持 "000"
- door_closed 保持 "1"(门关闭)
- rst_n 前 200ns 为 "0",之后为 "1"
预期输出:
current_floor= "00"(1楼)在整个仿真过程中state_out= "0000"(S_IDLE)在 rst_n 释放后motor_en= '0'(不运行)door_open_cmd= '0'direction= '0'(默认上行方向)
测试目的: 从 1 楼去 3 楼,验证上行运动和楼层变化
输入信号设置:
| 信号 | 0~200ns | 200~300ns | 300~400ns | 400~500ns | 500ns+ |
|---|---|---|---|---|---|
| rst_n | 0 | 1 | 1 | 1 | 1 |
| req_go | 000 | 100 | 100 | 100 | 000(被清除后) |
| any_request | 0 | 1 | 1 | 1 | 0 |
| door_closed | 1 | 1 | 1 | 1 | 1 |
| door_opened | 0 | 0 | 0 | 0 | 0 |
在 VWF 中的具体操作:
-
设置 req_go[2..0]:
- 先整段设为 "000"
- 选中 200ns~2000ns 区间 → 右键 → Value → Arbitrary Value... → 输入
100(二进制)→ OK
-
设置 any_request:
- 先整段设为 0
- 选中 200ns~2000ns 区间 → 设为 1
如何在 VWF 中选择时间区间:
- 在波形上按住鼠标左键,从起始时间拖动到结束时间
- 选中区域变为蓝色高亮
- 然后设置该区间的值
预期输出时序:
| 时刻 (clk边沿) | state_out | current_floor | motor_en | 说明 |
|---|---|---|---|---|
| 复位释放 | 0 (IDLE) | 00 (1F) | 0 | 空闲在1楼 |
| 第1拍 | 5 (MOVING_UP) | 00 (1F) | 1 | 检测到3楼请求,开始上行 |
| 第2拍 | 7 (ARRIVED) | 01 (2F) | 0 | 到达2楼,检查是否需要停 |
| 第3拍 | 5 (MOVING_UP) | 01 (2F) | 1 | 2楼无请求,继续上行 |
| 第4拍 | 7 (ARRIVED) | 10 (3F) | 0 | 到达3楼,有请求 |
| 第5拍 | 1 (DOOR_OPENING) | 10 (3F) | 0 | 开门 |
注意: 此时需要手动模拟 door_controller 的响应(在子模块仿真中我们手动控制 door_opened 和 door_closed 信号)。
测试目的: 验证门控制器的手动开门/关门功能
对象模块: door_controller
步骤:
- 在 Quartus 中将 顶层实体临时改为
door_controller:- Assignments → Settings... → 左侧选 General → 将 Top-level entity 改为
door_controller→ 点击 OK - 重新编译(Analysis & Elaboration)
- 生成功能仿真网表
- Assignments → Settings... → 左侧选 General → 将 Top-level entity 改为
- 新建 VWF 文件,添加 door_controller 的所有端口
- 设置输入激励
输入信号设置:
| 信号 | 0~200ns | 200~300ns | 300ns+ | 800~900ns | 1200~1300ns | 1400~1500ns |
|---|---|---|---|---|---|---|
| clk_1hz | 方波100ns | 同 | 同 | 同 | 同 | 同 |
| rst_n | 0 | 1 | 1 | 1 | 1 | 1 |
| door_open_cmd | 0 | 1 | 1 | 1 | 0 | 0 |
| door_close_cmd | 0 | 0 | 0 | 0 | 0 | 0 |
| btn_open | 0 | 0 | 0 | 0 | 0 | 0 |
| btn_close | 0 | 0 | 0 | 1 | 0 | 0 |
说明:
- 200ns: door_open_cmd 拉高,触发开门
- 300ns 之后: 门打开,开始 10 秒计时(仿真中每个时钟周期=1秒)
- 800ns: btn_close 脉冲,手动提前关门
- 观察 door_status、door_opened、door_closed 的变化
预期输出:
- 200ns 后 1 个周期: DS_OPENING,door_status = 1
- 再 1 个周期: DS_OPEN_WAIT,door_opened = 1
- 800ns btn_close: DS_CLOSING
- 再 1 个周期: DS_CLOSED,door_closed = 1
对象模块: door_controller
与场景 3 类似,但不按 btn_close,让门自动在 10 秒后关闭。
输入信号设置:
- door_open_cmd 在 200ns 拉高并保持
- btn_close 和 btn_open 始终为 0
- 观察 timer 从 0 计到 9 的过程
预期输出:
- door_opened 在开门后持续为 1
- 经过 10 个时钟周期后,自动进入 DS_CLOSING
- 再 1 个周期后,door_closed = 1
设置仿真时间: 由于需要等 10+ 个周期,建议将 End Time 设为 2 us(20 个时钟周期 x 100ns)
测试目的: 验证 SCAN 调度算法——同方向优先
对象模块: elevator_ctrl
场景描述: 电梯在 1 楼,同时收到 2 楼上行请求和 3 楼轿厢请求
输入信号设置:
| 信号 | 复位后稳定 | 说明 |
|---|---|---|
| req_up | 010 | 2楼有上行请求 |
| req_go | 100 | 轿厢去3楼 |
| any_request | 1 | 有请求 |
| door_closed | 先1后模拟开关门 | 需要配合状态变化调整 |
预期行为:
- IDLE → MOVING_UP(检测到上方有请求)
- ARRIVED at 2F → need_stop=1(2楼有上行请求)→ DOOR_OPENING
- 模拟开关门流程后 → CHECK_REQ
- 3楼仍有请求 → MOVING_UP
- ARRIVED at 3F → need_stop=1 → DOOR_OPENING
- 完成后无请求 → RETURN_HOME → IDLE at 1F
关键验证点:
- 上行过程中,电梯先停 2 楼再停 3 楼(方向优先)
- 3 楼完成后自动返回 1 楼
对象模块: alarm_controller
步骤: 临时设置 alarm_controller 为顶层实体
输入信号设置:
| 信号 | 0~200ns | 400ns | 800ns |
|---|---|---|---|
| clk_1hz | 方波 | 同 | 同 |
| clk_100hz | 快方波(10ns) | 同 | 同 |
| rst_n | 0→1 | 1 | 1 |
| btn_alarm_edge | 0 | 1 脉冲 | 1 脉冲 |
| alarm_cancel | 0 | 0 | 0 |
注意: btn_alarm_edge 是单周期脉冲,需要在 clk_100hz 的一个周期内置高
预期输出:
- 400ns 按下报警: alarm_active → 1, alarm_led 开始闪烁, buzzer_en → 1
- 800ns 再次按下: alarm_active → 0, alarm_led → 0, buzzer_en → 0
测试目的: 验证空闲时自动返回 1 楼
场景描述: 电梯完成 3 楼请求后,无新请求,应自动返回 1 楼
输入信号设置: 先让电梯到达 3 楼(通过场景 2 的方式),然后清除所有请求(req_go 和 any_request 都为 0)
预期输出:
- CHECK_REQ 发现无请求且不在 1 楼 → RETURN_HOME
- motor_en = 1, direction = 1(下行)
- floor: 3F → 2F → 1F
- 到达 1F 后 → IDLE
测试目的: 上行中收到下行请求
场景描述: 电梯在 1 楼,有 3 楼轿厢请求,上行途中 2 楼出现下行请求
输入信号设置:
- 初始: req_go = "100"(去3楼),any_request = 1
- 电梯到达 2 楼 (ARRIVED) 时: req_dn 改为 "010"(2楼下行请求)
预期输出:
- 上行经过 2 楼时,由于是上行模式且 2 楼只有下行请求 → 不停靠
- 继续上行到 3 楼停靠
- 3 楼完成后 → CHECK_REQ → 切换下行 → MOVING_DOWN
- 到达 2 楼,下行模式匹配下行请求 → 停靠
测试目的: 验证整个系统(分频器 + 消抖 + 请求处理 + 状态机 + 门控制 + 数码管 + 蜂鸣器)的集成协同工作。
⚠️ 为什么要改? 原始常量是 50MHz→1Hz,分频比为 25,000,000。如果不改,仿真 1 秒钟电梯运行需要计算 5000 万个时钟边沿,Quartus II 可能需要跑几十分钟甚至卡死。改小后仿真几秒内就能完成。
- 在 Quartus II 左侧 Project Navigator 中,双击
clk_divider.vhd打开文件 - 找到以下 4 行常量定义(约在第 28~46 行):
修改前(原始值):
constant CNT_1HZ_MAX : std_logic_vector(24 downto 0) := "1011111010111100000111111"; -- 24999999
constant CNT_100HZ_MAX : std_logic_vector(17 downto 0) := "111101000010001111"; -- 249999
constant CNT_500HZ_MAX : std_logic_vector(15 downto 0) := "1100001101001111"; -- 49999
constant CNT_1KHZ_MAX : std_logic_vector(14 downto 0) := "110000110100111"; -- 24999修改后(仿真加速值):
constant CNT_1HZ_MAX : std_logic_vector(24 downto 0) := "0000000000000000000000100"; -- 4
constant CNT_100HZ_MAX : std_logic_vector(17 downto 0) := "000000000000000001"; -- 1
constant CNT_500HZ_MAX : std_logic_vector(15 downto 0) := "0000000000000001"; -- 1
constant CNT_1KHZ_MAX : std_logic_vector(14 downto 0) := "000000000000001"; -- 1- 按 Ctrl+S 保存文件
如果之前仿真 elevator_ctrl 时修改了顶层实体,必须改回来。
- 点击菜单 Assignments → Settings...
- 左侧选 General
- 将 Top-level entity 改为
elevator_top - 点击 OK
- 点击 Processing → Start Compilation(完整编译,Ctrl+L)
- 等待编译成功(约 1~3 分钟)
- 确认 Messages 中无 Error(Warning 可暂时忽略)
- 点击 File → New...
- 展开 Verification/Debugging Files
- 选择 Vector Waveform File → OK
- 弹出空白波形编辑器
- 点击 Edit → End Time...
- 将 Time 改为
50.0,单位选us - 点击 OK
💡 为什么是 50us?分频常量改为 4 后,clk 设为 100ns 周期(10MHz),每 (4+1)×2=10 个 clk 周期产生一个 1Hz 翻转,即 1Hz 周期 ≈ 1us。电梯从 1 楼到 3 楼需要约 4 个 1Hz 周期,开门等待 10 个周期,关门返回还要 10+ 个周期,总共约 30 个 1Hz 周期 = 30us,50us 留有余量。
- 点击 Edit → Grid Size...
- Period 设为
50 ns→ OK
- 在波形编辑器左侧空白区域双击
- 弹出 Insert Node or Bus 对话框 → 点击 Node Finder...
- 在 Node Finder 中:
- Filter:
Pins: all - Look in:
elevator_top - 点击 List
- Filter:
- 左侧 Nodes Found 出现所有 24 个信号
- 点击 >> 全部添加到右侧 Selected Nodes
- 点击 OK → OK
添加完成后,波形编辑器左侧应显示以下 24 个信号:
输入信号(12 个):
| 序号 | 信号名 | 类型 | 说明 |
|---|---|---|---|
| 1 | clk |
单 bit | 50MHz 系统时钟 |
| 2 | rst_n |
单 bit | 复位(低有效) |
| 3 | btn_up1 |
单 bit | 1楼上行按钮 |
| 4 | btn_up2 |
单 bit | 2楼上行按钮 |
| 5 | btn_dn2 |
单 bit | 2楼下行按钮 |
| 6 | btn_dn3 |
单 bit | 3楼下行按钮 |
| 7 | btn_go1 |
单 bit | 轿厢去1楼 |
| 8 | btn_go2 |
单 bit | 轿厢去2楼 |
| 9 | btn_go3 |
单 bit | 轿厢去3楼 |
| 10 | btn_open |
单 bit | 手动开门 |
| 11 | btn_close |
单 bit | 手动关门 |
| 12 | btn_alarm |
单 bit | 报警按钮 |
输出信号(12 个):
| 序号 | 信号名 | 类型 | 说明 |
|---|---|---|---|
| 13 | seg7[6..0] |
7-bit 总线 | 七段数码管段码 |
| 14 | led_up |
单 bit | 上行指示灯 |
| 15 | led_dn |
单 bit | 下行指示灯 |
| 16 | led_door |
单 bit | 门状态灯(亮=开门) |
| 17 | led_alarm |
单 bit | 报警灯 |
| 18 | buzzer |
单 bit | 蜂鸣器 |
- 点击
clk信号名(整行高亮) - 右键 → Value → Clock...(或点工具栏时钟图标)
- 设置:
- Period:
100 ns - Duty cycle:
50 % - Offset:
0 ns
- Period:
- 点击 OK → 出现 100ns 周期方波
- 点击
rst_n信号名 - 先设整段为高电平:右键 → Value → Forcing High (1)
- 用鼠标在
rst_n波形区域从 0ns 拖到 500ns(选中前段) - 右键 → Value → Forcing Low (0)
- 结果: 0~500ns 低电平(复位),500ns 之后高电平(正常工作)
💡 这里用 500ns 而不是 200ns,是因为顶层模块有分频器和消抖器,需要多几个时钟周期让所有模块都完成复位。
逐个选中以下信号,全部设为 Forcing Low (0):
btn_up1,btn_up2,btn_dn2,btn_dn3btn_go1,btn_go2,btn_go3btn_open,btn_close,btn_alarm
操作:点击信号名 → 右键 → Value → Forcing Low (0),重复 10 次。
- 点击
btn_go3信号名 - 用鼠标在波形区域从 2000ns(2us)拖到 15000ns(15us)
- 即选中 2us ~ 15us 这段时间区间
- 右键 → Value → Forcing High (1)
- 结果:
btn_go3在 2~15us 为高电平(模拟持续按住按钮),其余时间为低电平
💡 按键持续时间要足够长(至少几个 us),因为
debounce模块需要若干个 clk_100hz 周期来确认按键有效。分频常量改小后确认会很快。
- 点击 File → Save As...
- 文件名输入
elevator_top_test.vwf - 保存到工程目录
- 点击 Save
- 点击 Assignments → Settings...
- 左侧选 Simulator Settings
- 设置:
- Simulation mode:
Functional - Simulation input: 点击 ... → 选择刚保存的
elevator_top_test.vwf
- Simulation mode:
- 点击 OK
- 点击 Processing → Generate Functional Simulation Netlist
- 等待完成(显示
was successful)
- 点击 Processing → Start Simulation
- 等待仿真完成(分频常量改小后通常几秒就完成)
- 自动弹出 Simulation Report 波形窗口
查看 seg7 总线值:
- 右键点击
seg7[6..0]→ Properties... → Radix 选Binary - 这样能直接看到 7 位段码
seg7 段码对照表(共阳极,低电平点亮):
| 楼层 | seg7[6..0] (gfedcba) | 显示 |
|---|---|---|
| 1楼 | 1111001 |
1 |
| 2楼 | 0100100 |
2 |
| 3楼 | 0110000 |
3 |
预期输出的完整时间线:
| 阶段 | 大致时间 | 观察现象 |
|---|---|---|
| 复位期 | 0 ~ 500ns | seg7 显示 1 楼段码,所有 LED 低,buzzer 低 |
| 空闲等待 | 500ns ~ 2us | 系统正常运行但无按键输入,seg7 保持 1 楼 |
| 按键消抖 | 2us ~ 约 3us | btn_go3 拉高后,debounce 模块正在确认按键(需要若干 clk_100hz 周期) |
| 请求确认 | 约 3us | request_handler 接收到去 3 楼请求 |
| 上行启动 | 约 3~4us | led_up 变高(上行指示灯亮),电梯开始上行 |
| 途经 2 楼 | 约 5us | seg7 变为 2 楼段码 0100100,2 楼无请求不停 |
| 到达 3 楼 | 约 6~7us | seg7 变为 3 楼段码 0110000,led_up 变低 |
| 开门 | 约 7us | led_door 变高(门状态灯亮),buzzer 可能短响 |
| 门开等待 | 约 7~17us | led_door 保持高(门保持打开状态,等待 10 个 1Hz 周期) |
| 自动关门 | 约 17us | led_door 变低(门关闭) |
| 返回 1 楼 | 约 18~22us | led_dn 变高(下行灯亮),seg7 逐步变回 1 楼 |
| 空闲 | 约 22us+ | 所有 LED 灭,seg7 显示 1 楼,系统回到空闲 |
⚠️ 注意:上述时间为估算值,实际取决于分频常量和消抖延迟。波形中的精确时刻可能略有偏差,但事件顺序应该一致。
⚠️ 切记! 集成仿真通过后,准备烧录到硬件前,必须把clk_divider.vhd的常量全部改回原始值:
-- 恢复为 50MHz 分频的原始值
constant CNT_1HZ_MAX : std_logic_vector(24 downto 0) := "1011111010111100000111111"; -- 24999999
constant CNT_100HZ_MAX : std_logic_vector(17 downto 0) := "111101000010001111"; -- 249999
constant CNT_500HZ_MAX : std_logic_vector(15 downto 0) := "1100001101001111"; -- 49999
constant CNT_1KHZ_MAX : std_logic_vector(14 downto 0) := "110000110100111"; -- 24999保存后重新编译,再进行引脚分配和下载。否则板子上的时钟分频极快,电梯行为会乱。
- 确保已完成完整编译(不是仅 Analysis & Elaboration)
- 点击 Assignments → Settings...
- 左侧选择 Simulator Settings
- Simulation mode 改为
Timing(时序仿真) - Simulation input 选择同一个
.vwf文件 - 点击 OK
- 点击 Processing → Start Simulation
- 时序仿真比功能仿真慢,因为包含了实际延迟
- 等待完成
- 功能仿真: 信号变化在时钟边沿瞬间完成,无延迟
- 时序仿真: 信号变化有真实的传播延迟(纳秒级)
- 对比两者的波形,确认时序仿真中没有功能错误(如竞争冒险导致的毛刺)
原因: 顶层实体名与工程名不匹配,或文件未添加
解决:
- 确认
.vhd文件已添加到工程(Project → Add/Remove Files) - 确认 entity 名称拼写正确
- 确认 Project → Set as Top-Level Entity 设置正确
原因: 需要先成功编译(至少 Analysis & Elaboration)
解决:
- 先执行 Processing → Start → Start Analysis & Elaboration
- 确保无编译错误
- 再执行 Processing → Generate Functional Simulation Netlist
原因: 复位信号未正确设置
解决:
- 确认
rst_n在仿真开始时为 '0'(低电平有效复位) - 确认
rst_n在几个时钟周期后释放为 '1' - 确认复位持续时间至少 2 个时钟周期
原因: 信号未驱动或未连接
解决:
- 检查输入信号是否在 VWF 中设置了值
- 检查信号是否正确连接到端口
原因: 组合逻辑中有信号在某些条件分支下未赋值
解决:
- 在组合进程的 case 之前为所有输出设置默认值
- 确保 case 有
when others分支 - 检查所有 if-elsif-else 是否有完整的 else 分支
原因: VWF 仿真只能观察端口信号,不能观察内部信号
解决:
- 如需观察内部信号(如
current_state),将其通过端口引出 - 本设计已将
state_out端口引出用于调试 - 或者使用 ModelSim 仿真(如果可用)
原因: 器件选择过大,或设计中有不必要的高位宽计数器
解决:
- 选择较小的目标器件(仿真阶段不影响结果)
- 仿真时使用缩短的分频常量
原因: 可能没有成功编译,或选择了错误的筛选条件
解决:
- 先确保 Analysis & Elaboration 成功
- Node Finder 中 Filter 选择
Pins: all - 确认 Look in 选择的是正确的实体名
- 点击 List 刷新
不要一上来就仿真顶层模块。按以下顺序逐个验证:
- clk_divider → 验证分频输出频率
- debounce → 验证消抖和边沿检测
- display_driver → 验证七段码输出
- door_controller → 验证开关门流程和 10 秒定时
- alarm_controller → 验证报警 toggle 和 LED 闪烁
- request_handler → 验证请求寄存和清除
- elevator_ctrl → 验证状态机逻辑(核心!)
- elevator_top → 最终集成验证
每次仿真某个子模块时:
- Assignments → Settings... → General → 将 Top-level entity 改为该模块名 → OK
- 重新 Analysis & Elaboration
- 重新 Generate Functional Simulation Netlist
- 新建/使用对应的 VWF 文件
VWF 文件可以直接编辑后重新仿真,不需要重新编译:
- 修改波形编辑器中的输入信号值
- 保存 VWF 文件
- 直接运行 Processing → Start Simulation
| 快捷键 | 功能 |
|---|---|
| Ctrl + 鼠标滚轮 | 缩放波形 |
| Ctrl + Home | 回到起始位置 |
| Ctrl + End | 跳到结束位置 |
| Ctrl + F | 查找信号 |
仿真 elevator_top 时,如果分频器常量是 50MHz→1Hz 的真实值,仿真会极其缓慢。
仿真加速方法(二选一):
方法 A: 修改分频常量(推荐,最简单)
编辑 clk_divider.vhd,将常量改小:
-- 仿真用(记得之后改回来!)
constant CNT_1HZ_MAX : std_logic_vector(24 downto 0) := "0000000000000000000000100"; -- 4这样 clk 设为 10Hz(周期100ns)时,分频后就是 1Hz,仿真速度提升 500 万倍。
方法 B: 直接仿真子模块
跳过 clk_divider,直接给 elevator_ctrl 等模块提供 1Hz 时钟。
设置多位总线信号的任意值:
- 选中总线信号的某个时间区间
- 右键 → Value → Arbitrary Value...
- 在弹出框中输入值(选择 Binary 格式)
- 例如设置
req_go = "100"表示去3楼 - 点击 OK
可以将相关信号分组显示以便观察:
- 右键信号列表 → Insert → Insert Group...
- 输入分组名(如 "Requests")
- 将相关信号拖入分组
- 每次修改 VHDL 代码后,必须重新:编译 → 生成仿真网表 → 运行仿真
- 仿真完记得恢复分频常量!否则下载到板子上分频不正确
- VWF 文件与工程绑定,更换顶层实体后需要重新创建或修改 VWF
- 功能仿真不考虑延迟,通过功能仿真不代表时序一定正确
- 截图保存波形用于课程设计报告(使用系统截图工具或 Print Screen)
在提交课程设计前,确认以下所有仿真场景已通过:
| 序号 | 仿真场景 | 模块 | 状态 |
|---|---|---|---|
| 1 | 系统复位测试 | elevator_ctrl | ☐ 通过 |
| 2 | 单楼层上行 (1F→3F) | elevator_ctrl | ☐ 通过 |
| 3 | 单楼层下行 (3F→1F) | elevator_ctrl | ☐ 通过 |
| 4 | 多楼层顺序停靠 | elevator_ctrl | ☐ 通过 |
| 5 | 手动关门提前关门 | door_controller | ☐ 通过 |
| 6 | 手动开门(关门中重开) | door_controller | ☐ 通过 |
| 7 | 10 秒自动关门 | door_controller | ☐ 通过 |
| 8 | 无请求返回 1 楼 | elevator_ctrl | ☐ 通过 |
| 9 | 报警激活/解除 | alarm_controller | ☐ 通过 |
| 10 | 反向请求处理(SCAN) | elevator_ctrl | ☐ 通过 |
| 11 | 七段数码管显示 1/2/3 | display_driver | ☐ 通过 |
| 12 | 按键消抖和边沿检测 | debounce | ☐ 通过 |
| 13 | 顶层集成测试 | elevator_top | ☐ 通过 |
| 按钮 | 名称 | 功能 |
|---|---|---|
| 0 | Forcing Low | 设置选中区间为低电平 (0) |
| 1 | Forcing High | 设置选中区间为高电平 (1) |
| X | Forcing Unknown | 设置为未知态 |
| Z | High Impedance | 设置为高阻态 |
| 🕐 | Clock | 设置为时钟信号 |
| ↕ | Arbitrary Value | 设置任意总线值 |
| ⊝ | Count Value | 设置计数器递增值 |
| ~ | Random Values | 设置随机值 |
| 请求含义 | req_up | req_dn | req_go |
|---|---|---|---|
| 1楼上行请求 | 001 | 000 | 000 |
| 2楼上行请求 | 010 | 000 | 000 |
| 2楼下行请求 | 000 | 010 | 000 |
| 3楼下行请求 | 000 | 100 | 000 |
| 轿厢去1楼 | 000 | 000 | 001 |
| 轿厢去2楼 | 000 | 000 | 010 |
| 轿厢去3楼 | 000 | 000 | 100 |
| 2楼上行+3楼轿厢 | 010 | 000 | 100 |
[开始]
│
├── 1. File → New Project Wizard → 创建工程
│
├── 2. Project → Add/Remove Files → 添加9个VHD文件
│
├── 3. Processing → Start → Start Analysis & Elaboration
│ └── 检查有无 Error → 有则修改代码重新编译
│
├── 4. Processing → Start Compilation (完整编译)
│ └── 检查有无锁存器 Warning → 有则必须修复
│
├── 5. File → New → Vector Waveform File
│ ├── Edit → End Time → 设置仿真时长
│ ├── 双击空白 → Node Finder → 添加信号
│ ├── 设置时钟、复位、输入激励
│ └── File → Save As → 保存 VWF
│
├── 6. Assignments → Settings → Simulator Settings
│ ├── Mode: Functional
│ └── Input: 选择 VWF 文件
│
├── 7. Processing → Generate Functional Simulation Netlist
│
├── 8. Processing → Start Simulation
│
└── 9. 查看波形结果 → 分析 → 截图保存
│
└── [如需修改] → 修改VWF → 重新仿真 (不需要重新编译)
[如需改代码] → 修改VHD → 重新编译 → 重新生成网表 → 重新仿真