Skip to content

Latest commit

 

History

History
1196 lines (872 loc) · 43 KB

quartus-ii-simulation-tutorial.md

File metadata and controls

1196 lines (872 loc) · 43 KB

Quartus II 9.1 电梯控制器仿真教程

软件版本: Quartus II 9.1 (32-Bit) 项目: 三层电梯控制器 VHDL 设计 适用对象: 本课程设计项目的仿真与验证


目录

  1. 创建 Quartus II 工程
  2. 添加 VHDL 源文件
  3. 编译工程
  4. 创建波形仿真文件
  5. 设置输入激励信号
  6. 运行功能仿真
  7. 分析仿真波形
  8. 仿真场景实操
  9. 时序仿真
  10. 常见问题与解决
  11. 仿真技巧与注意事项

一、创建 Quartus II 工程

步骤 1.1:启动 Quartus II

  1. 双击桌面 Quartus II 9.1 图标,或通过开始菜单启动
  2. 等待软件完全加载(首次启动可能需要 30 秒左右)

步骤 1.2:新建工程

  1. 点击菜单栏 File → New Project Wizard...
  2. 弹出 New Project Wizard 向导窗口,点击 Next

步骤 1.3:设置工程路径和名称

  1. What is the working directory for this project?(工程目录)
    • 点击右侧 ... 按钮浏览到你想存放工程的文件夹
    • 例如: D:\FPGA-Elevator-controller\quartus
    • 如果文件夹不存在,请先在文件资源管理器中创建它
  2. What is the name of this project?(工程名称)
    • 输入: elevator_top
    • 重要: 工程名称必须与顶层模块名称一致!
  3. What is the name of the top-level design entity for this project?(顶层实体名)
    • 输入: elevator_top(应自动填充,与工程名相同)
  4. 点击 Next

步骤 1.4:添加文件(可跳过)

  1. 出现 "Add Files" 页面
  2. 可以在这里添加文件,也可以之后再添加
  3. 如果要现在添加,点击 ... 按钮,浏览到 src/ 文件夹,依次选择所有 .vhd 文件
  4. 点击 Next

步骤 1.5:选择目标器件

  1. Family(器件系列)下拉框选择你实验箱对应的 FPGA 系列:
    • 如果是 Cyclone II 系列 → 选 Cyclone II
    • 如果是 Cyclone III 系列 → 选 Cyclone III
    • 如果是 MAX II (CPLD) → 选 MAX II
    • 不确定就先选 Cyclone II(最常见的教学板)
  2. Available devices 列表中选择具体型号:
    • Cyclone II 常用: EP2C5T144C8 或 EP2C8T144C8
    • Cyclone III 常用: EP3C5E144C8
    • MAX II 常用: EPM240T100C5 或 EPM570T144C5
    • 仿真阶段可以随意选择一个,不影响功能仿真结果
  3. 点击 Next

步骤 1.6:EDA 工具设置

  1. 保持默认设置即可(全部 <None>)
  2. 点击 Next

步骤 1.7:完成创建

  1. 确认工程信息摘要
  2. 点击 Finish
  3. 工程创建完成,你会看到主界面左侧的 Project Navigator 面板

二、添加 VHDL 源文件

步骤 2.1:复制源文件到工程目录

  1. 打开文件资源管理器
  2. 将 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
    

步骤 2.2:在 Quartus 中添加文件

  1. 点击菜单栏 Project → Add/Remove Files in Project...
  2. 弹出 Settings 对话框,左侧选中 Files
  3. 点击右侧 ... 按钮(File name 右边)
  4. 浏览到工程目录,选择所有 9 个 .vhd 文件:
    • 可以按住 Ctrl 键多选
    • 或者一个一个添加
  5. 每选择一个文件后点击 Add
  6. 确认右侧文件列表中有全部 9 个文件
  7. 点击 OK

步骤 2.3:设置顶层实体

  1. 在左侧 Project Navigator 面板中,确认 Entity 标签页下能看到 elevator_top
  2. 如果看不到或显示错误:
    • 点击菜单 Project → Set as Top-Level Entity
    • 在弹出框中输入 elevator_top

步骤 2.4:验证文件添加成功

  1. 点击菜单 Project → Add/Remove Files in Project...
  2. 确认 Files 列表中有 9 个 .vhd 文件
  3. 每个文件的 Type 列应显示 VHDL

三、编译工程

步骤 3.1:分析与综合

先做分析检查语法错误,再完整编译。

  1. 点击菜单 Processing → Start → Start Analysis & Elaboration
    • 或者点击工具栏上的 ▶ Start Analysis & Elaboration 按钮(蓝色小三角旁边带放大镜图标)
  2. 等待编译完成(下方 Messages 窗口会滚动显示日志)
  3. 检查结果:
    • ✅ 成功: Messages 窗口最后一行显示 Info: Quartus II Analysis & Elaboration was successful
    • ❌ 失败: 出现红色 Error 信息,需要根据错误提示修改代码

步骤 3.2:查看和修复错误(如果有)

如果出现错误:

  1. 在 Messages 窗口中找到红色 Error 行
  2. 双击错误信息,会自动跳转到出错的 VHDL 文件和行号
  3. 常见错误及解决方法:
错误信息 原因 解决方法
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,所有输出有默认值

步骤 3.3:完整编译

语法检查通过后,进行完整编译:

  1. 点击菜单 Processing → Start Compilation
    • 或点击工具栏上的 ▶ Start Compilation 按钮(紫色大三角)
    • 或按快捷键 Ctrl + L
  2. 等待编译完成(可能需要 1-3 分钟)
  3. 编译成功后,在 Compilation Report 中可以看到:
    • Flow Summary: 总资源使用
    • Fitter: 布局布线结果
    • Timing Analyzer: 时序分析结果

步骤 3.4:检查编译警告

  1. 在 Messages 窗口查看 Warning 信息
  2. 必须关注的警告:
    • 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 → 引脚未分配,仿真阶段可忽略
  3. 可以忽略的警告:引脚分配相关(仿真阶段不需要分配引脚)

四、创建波形仿真文件(VWF)

步骤 4.1:新建 VWF 文件

  1. 点击菜单 File → New...
  2. 在弹出的 New 对话框中展开 Verification/Debugging Files 分类
  3. 选择 Vector Waveform File
  4. 点击 OK
  5. 打开一个空白的波形编辑器窗口

步骤 4.2:设置仿真时间参数

⚠️ 重要:仿真时间设置是关键步骤!

因为实际时钟是 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 为主进行演示。

步骤 4.2a:切换顶层实体为 elevator_ctrl(方案 B 必须)

⚠️ 如果你打算仿真子模块(方案 B),必须先完成此步骤,否则 Node Finder 中将找不到信号!

  1. 点击菜单 Assignments → Settings...
  2. 左侧 Category 选择 General(通常是默认选中的第一项)
  3. 找到右侧的 Top-level entity 输入框(当前显示 elevator_top)
  4. 将内容改为 elevator_ctrl(可以直接修改,也可以点击右边的 ... 按钮浏览选择)
  5. 下方的 Recently selected top-level entities 不需要修改(这只是历史记录)
  6. 点击 OK
  7. 重新编译:点击 Processing → Start → Start Analysis & Elaboration,等待编译成功
  8. 编译成功后,继续下面的步骤

💡 仿真完成后如需恢复,重复以上步骤将 Top-level entity 改回 elevator_top 即可。

步骤 4.3:设置仿真结束时间

  1. 在波形编辑器中,点击菜单 Edit → End Time...
  2. 弹出 End Time 对话框,你会看到:
    • Time: 默认值为 1.0,右边下拉框为 us
    • Default extension options: 默认 Last clock pattern
    • End time extension per signal: 空列表
  3. 将 Time 输入框中的值从 1.0 改为 5.0,单位保持 us 不变
    • 5 us = 5000 ns,每个时钟周期设为 100ns,则有 50 个时钟周期,足够完成一个完整场景
    • 下方的 Default extension options 和 End time extension per signal 不需要修改
  4. 点击 OK

步骤 4.4:设置网格时间间隔

  1. 点击菜单 Edit → Grid Size...
  2. 设置 Period: 50 ns(半个时钟周期)
  3. 点击 OK

五、设置输入激励信号

步骤 5.1:添加信号到波形窗口

方法一:通过 Node Finder 添加(推荐)

  1. 在波形编辑器窗口的左侧空白区域(信号名列表区),双击空白处
    • 或者右键点击 → Insert → Insert Node or Bus...
  2. 弹出 Insert Node or Bus 对话框
  3. 点击 Node Finder... 按钮
  4. 在 Node Finder 对话框中:
    • Filter: 选择 Pins: all(显示所有端口引脚)
    • Look in: 确认是 elevator_ctrl(或你要仿真的模块名)
    • 点击 List 按钮
  5. 左侧 Nodes Found 列表会显示该模块的所有输入输出端口
  6. 选中需要的信号,点击 > 按钮将其移到右侧 Selected Nodes 列表
    • 可以按住 Ctrl 多选
    • 或点击 >> 全部添加
  7. 点击 OK → 再点击 OK

方法二:手动输入信号名

  1. 在 Insert Node or Bus 对话框中
  2. 在 Name 栏直接输入信号全名(如 clk_1hz)
  3. 点击 OK

步骤 5.2:推荐添加的信号列表

仿真 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 状态编码

步骤 5.3:设置时钟信号

  1. 在波形窗口中点击选中 clk_1hz 信号(点击左侧信号名使整行高亮)
  2. 右键点击 → 选择 Value → Clock...
    • 或点击工具栏上的时钟图标 🕐
  3. 在 Clock 设置对话框中:
    • Period: 100 ns(一个完整时钟周期)
    • Duty cycle: 50 %
    • Offset: 0 ns
    • Phase: 保持默认
  4. 点击 OK
  5. 时钟波形出现在波形窗口中(方波)

步骤 5.4:设置复位信号

  1. 选中 rst_n 信号

  2. 设置为初始低电平(复位有效),然后拉高:

    • 方法: 用鼠标在波形上选择时间段,然后设置电平值

    详细操作:

    a. 先设置整段为高电平:

    • 点击 rst_n 信号名使整行选中
    • 点击工具栏上的 Forcing High (1) 按钮(或右键 → Value → Forcing High)

    b. 再设置前两个周期为低电平(复位):

    • 用鼠标在 rst_n 波形上从 0ns 拖动到 200ns(选中前两个时钟周期)
    • 释放鼠标后,选中区域会变为高亮蓝色
    • 点击工具栏上的 Forcing Low (0) 按钮

    结果: rst_n 在 0~200ns 为低电平(复位),200ns 之后为高电平(正常工作)

步骤 5.5:设置其他输入信号的默认值

所有输入信号需要设置初始值,否则仿真结果不确定!

  1. req_up[2..0]: 选中 → Forcing Low (0)(全部设为 "000",无请求)
  2. req_dn[2..0]: 选中 → Forcing Low (0)
  3. req_go[2..0]: 选中 → Forcing Low (0)
  4. any_request: 选中 → Forcing Low (0)
  5. door_closed: 选中 → Forcing High (1)(初始门为关闭状态)
  6. door_opened: 选中 → Forcing Low (0)
  7. alarm_active: 选中 → Forcing Low (0)(无报警)

六、运行功能仿真

步骤 6.1:保存 VWF 文件

  1. 点击 File → Save As...
  2. 保存到工程目录下,文件名如 elevator_ctrl_test1.vwf
  3. 点击 Save

步骤 6.2:设置仿真器

  1. 点击菜单 Assignments → Settings...
  2. 左侧导航树选择 Simulator Settings
  3. 设置:
    • Simulation mode: 选择 Functional(功能仿真)
    • Simulation input: 点击 ... 选择刚才保存的 .vwf 文件
  4. 点击 OK

步骤 6.3:生成功能仿真网表

⚠️ 必须先执行此步骤,否则仿真会报错!

  1. 点击菜单 Processing → Generate Functional Simulation Netlist
  2. 等待生成完成
  3. 成功提示: Info: Quartus II Generate Functional Simulation Netlist was successful

步骤 6.4:运行仿真

  1. 点击菜单 Processing → Start Simulation
    • 或按快捷键 Ctrl + I 虽然是9.1版本不一定有
  2. 等待仿真完成(通常几秒钟)
  3. 成功后自动弹出仿真结果波形窗口

步骤 6.5:查看仿真结果

仿真完成后会弹出 Simulation Report 窗口,其中包含 Simulation Waveforms 部分。

  1. 展开 Simulation Waveforms 节点
  2. 双击波形图可以放大查看
  3. 使用工具栏的缩放工具调整波形显示:
    • 🔍+ (Zoom In): 放大波形
    • 🔍- (Zoom Out): 缩小波形
    • 🔍 (Zoom Fit): 适应窗口大小

七、分析仿真波形

步骤 7.1:使用光标测量时间

  1. 在波形窗口中单击鼠标左键,放置参考光标(黄色竖线)
  2. 光标位置和时间显示在窗口底部
  3. 对比不同时刻的信号值来验证功能

步骤 7.2:查看信号值

  1. 将鼠标指针悬停在波形上,会显示当前时刻的信号值
  2. 或者在左侧信号列表中查看当前光标位置的信号值

步骤 7.3:信号分组显示

对于总线信号(如 state_out[3..0]),可以切换显示方式:

  1. 右键点击总线信号名
  2. 选择 Properties...
  3. 在 Radix 下拉框中选择显示格式:
    • Binary — 二进制(如 "0101")
    • Hexadecimal — 十六进制(如 "5")
    • Unsigned Decimal — 无符号十进制(如 "5")
  4. 对于 state_out 推荐用十六进制,方便对照状态编码表
  5. 对于 current_floor 推荐用无符号十进制

步骤 7.4:状态编码对照表(仿真时打印出来参考)

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 报警

步骤 7.5:楼层编码对照表

current_floor (DEC) current_floor (BIN) 实际楼层
0 00 1楼
1 01 2楼
2 10 3楼

八、仿真场景实操(完整演示)

以下提供 8 个完整仿真场景,每个场景详细说明输入信号设置和预期输出。

场景 1:系统复位测试

测试目的: 验证复位后电梯在 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

操作步骤:

  1. 所有请求信号保持 "000"
  2. door_closed 保持 "1"(门关闭)
  3. 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'(默认上行方向)

场景 2:单楼层上行 1楼→3楼

测试目的: 从 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 中的具体操作:

  1. 设置 req_go[2..0]:

    • 先整段设为 "000"
    • 选中 200ns~2000ns 区间 → 右键 → Value → Arbitrary Value... → 输入 100(二进制)→ OK
  2. 设置 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 信号)。


场景 3:手动开关门测试

测试目的: 验证门控制器的手动开门/关门功能

对象模块: door_controller

步骤:

  1. 在 Quartus 中将 顶层实体临时改为 door_controller:
    • Assignments → Settings... → 左侧选 General → 将 Top-level entity 改为 door_controller → 点击 OK
    • 重新编译(Analysis & Elaboration)
    • 生成功能仿真网表
  2. 新建 VWF 文件,添加 door_controller 的所有端口
  3. 设置输入激励

输入信号设置:

信号 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

场景 4:10 秒自动关门测试

对象模块: 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)


场景 5:多楼层请求(方向优先调度)

测试目的: 验证 SCAN 调度算法——同方向优先

对象模块: elevator_ctrl

场景描述: 电梯在 1 楼,同时收到 2 楼上行请求和 3 楼轿厢请求

输入信号设置:

信号 复位后稳定 说明
req_up 010 2楼有上行请求
req_go 100 轿厢去3楼
any_request 1 有请求
door_closed 先1后模拟开关门 需要配合状态变化调整

预期行为:

  1. IDLE → MOVING_UP(检测到上方有请求)
  2. ARRIVED at 2F → need_stop=1(2楼有上行请求)→ DOOR_OPENING
  3. 模拟开关门流程后 → CHECK_REQ
  4. 3楼仍有请求 → MOVING_UP
  5. ARRIVED at 3F → need_stop=1 → DOOR_OPENING
  6. 完成后无请求 → RETURN_HOME → IDLE at 1F

关键验证点:

  • 上行过程中,电梯先停 2 楼再停 3 楼(方向优先)
  • 3 楼完成后自动返回 1 楼

场景 6:报警功能测试

对象模块: 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

场景 7:无请求返回 1 楼

测试目的: 验证空闲时自动返回 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

场景 8:反向请求处理

测试目的: 上行中收到下行请求

场景描述: 电梯在 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 楼,下行模式匹配下行请求 → 停靠

场景 9:顶层模块 elevator_top 集成仿真(方案 A 完整实操)

测试目的: 验证整个系统(分频器 + 消抖 + 请求处理 + 状态机 + 门控制 + 数码管 + 蜂鸣器)的集成协同工作。


第一步:修改 clk_divider.vhd 分频常量(仿真加速)

⚠️ 为什么要改? 原始常量是 50MHz→1Hz,分频比为 25,000,000。如果不改,仿真 1 秒钟电梯运行需要计算 5000 万个时钟边沿,Quartus II 可能需要跑几十分钟甚至卡死。改小后仿真几秒内就能完成。

  1. 在 Quartus II 左侧 Project Navigator 中,双击 clk_divider.vhd 打开文件
  2. 找到以下 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
  1. 按 Ctrl+S 保存文件

第二步:切换顶层实体为 elevator_top

如果之前仿真 elevator_ctrl 时修改了顶层实体,必须改回来。

  1. 点击菜单 Assignments → Settings...
  2. 左侧选 General
  3. 将 Top-level entity 改为 elevator_top
  4. 点击 OK

第三步:重新编译

  1. 点击 Processing → Start Compilation(完整编译,Ctrl+L)
  2. 等待编译成功(约 1~3 分钟)
  3. 确认 Messages 中无 Error(Warning 可暂时忽略)

第四步:新建 VWF 文件

  1. 点击 File → New...
  2. 展开 Verification/Debugging Files
  3. 选择 Vector Waveform File → OK
  4. 弹出空白波形编辑器

第五步:设置仿真结束时间

  1. 点击 Edit → End Time...
  2. 将 Time 改为 50.0,单位选 us
  3. 点击 OK

💡 为什么是 50us?分频常量改为 4 后,clk 设为 100ns 周期(10MHz),每 (4+1)×2=10 个 clk 周期产生一个 1Hz 翻转,即 1Hz 周期 ≈ 1us。电梯从 1 楼到 3 楼需要约 4 个 1Hz 周期,开门等待 10 个周期,关门返回还要 10+ 个周期,总共约 30 个 1Hz 周期 = 30us,50us 留有余量。


第六步:设置网格

  1. 点击 Edit → Grid Size...
  2. Period 设为 50 ns → OK

第七步:添加信号到波形编辑器

  1. 在波形编辑器左侧空白区域双击
  2. 弹出 Insert Node or Bus 对话框 → 点击 Node Finder...
  3. 在 Node Finder 中:
    • Filter: Pins: all
    • Look in: elevator_top
    • 点击 List
  4. 左侧 Nodes Found 出现所有 24 个信号
  5. 点击 >> 全部添加到右侧 Selected Nodes
  6. 点击 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 蜂鸣器

第八步:设置输入信号

8.1 设置 clk 时钟
  1. 点击 clk 信号名(整行高亮)
  2. 右键 → Value → Clock...(或点工具栏时钟图标)
  3. 设置:
    • Period: 100 ns
    • Duty cycle: 50 %
    • Offset: 0 ns
  4. 点击 OK → 出现 100ns 周期方波
8.2 设置 rst_n 复位信号
  1. 点击 rst_n 信号名
  2. 先设整段为高电平:右键 → Value → Forcing High (1)
  3. 用鼠标在 rst_n 波形区域从 0ns 拖到 500ns(选中前段)
  4. 右键 → Value → Forcing Low (0)
  5. 结果: 0~500ns 低电平(复位),500ns 之后高电平(正常工作)

💡 这里用 500ns 而不是 200ns,是因为顶层模块有分频器和消抖器,需要多几个时钟周期让所有模块都完成复位。

8.3 设置所有按钮为默认值(不按)

逐个选中以下信号,全部设为 Forcing Low (0):

  • btn_up1, btn_up2, btn_dn2, btn_dn3
  • btn_go1, btn_go2, btn_go3
  • btn_open, btn_close, btn_alarm

操作:点击信号名 → 右键 → Value → Forcing Low (0),重复 10 次。

8.4 设置测试按键 btn_go3(去 3 楼)
  1. 点击 btn_go3 信号名
  2. 用鼠标在波形区域从 2000ns(2us)拖到 15000ns(15us)
    • 即选中 2us ~ 15us 这段时间区间
  3. 右键 → Value → Forcing High (1)
  4. 结果: btn_go3 在 2~15us 为高电平(模拟持续按住按钮),其余时间为低电平

💡 按键持续时间要足够长(至少几个 us),因为 debounce 模块需要若干个 clk_100hz 周期来确认按键有效。分频常量改小后确认会很快。


第九步:保存 VWF 文件

  1. 点击 File → Save As...
  2. 文件名输入 elevator_top_test.vwf
  3. 保存到工程目录
  4. 点击 Save

第十步:配置仿真器

  1. 点击 Assignments → Settings...
  2. 左侧选 Simulator Settings
  3. 设置:
    • Simulation mode: Functional
    • Simulation input: 点击 ... → 选择刚保存的 elevator_top_test.vwf
  4. 点击 OK

第十一步:生成功能仿真网表

  1. 点击 Processing → Generate Functional Simulation Netlist
  2. 等待完成(显示 was successful)

第十二步:运行仿真

  1. 点击 Processing → Start Simulation
  2. 等待仿真完成(分频常量改小后通常几秒就完成)
  3. 自动弹出 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

保存后重新编译,再进行引脚分配和下载。否则板子上的时钟分频极快,电梯行为会乱。


九、时序仿真

步骤 9.1:切换到时序仿真模式

  1. 确保已完成完整编译(不是仅 Analysis & Elaboration)
  2. 点击 Assignments → Settings...
  3. 左侧选择 Simulator Settings
  4. Simulation mode 改为 Timing(时序仿真)
  5. Simulation input 选择同一个 .vwf 文件
  6. 点击 OK

步骤 9.2:运行时序仿真

  1. 点击 Processing → Start Simulation
  2. 时序仿真比功能仿真慢,因为包含了实际延迟
  3. 等待完成

步骤 9.3:对比功能仿真和时序仿真

  • 功能仿真: 信号变化在时钟边沿瞬间完成,无延迟
  • 时序仿真: 信号变化有真实的传播延迟(纳秒级)
  • 对比两者的波形,确认时序仿真中没有功能错误(如竞争冒险导致的毛刺)

十、常见问题与解决

问题 1:"Error: Can't find design entity elevator_top"

原因: 顶层实体名与工程名不匹配,或文件未添加

解决:

  1. 确认 .vhd 文件已添加到工程(Project → Add/Remove Files)
  2. 确认 entity 名称拼写正确
  3. 确认 Project → Set as Top-Level Entity 设置正确

问题 2:"Error: Cannot generate functional simulation netlist"

原因: 需要先成功编译(至少 Analysis & Elaboration)

解决:

  1. 先执行 Processing → Start → Start Analysis & Elaboration
  2. 确保无编译错误
  3. 再执行 Processing → Generate Functional Simulation Netlist

问题 3:仿真结果全是 'X'(未知态)

原因: 复位信号未正确设置

解决:

  1. 确认 rst_n 在仿真开始时为 '0'(低电平有效复位)
  2. 确认 rst_n 在几个时钟周期后释放为 '1'
  3. 确认复位持续时间至少 2 个时钟周期

问题 4:仿真结果全是 'U'(未初始化)

原因: 信号未驱动或未连接

解决:

  1. 检查输入信号是否在 VWF 中设置了值
  2. 检查信号是否正确连接到端口

问题 5:Warning "inferred latch for signal"

原因: 组合逻辑中有信号在某些条件分支下未赋值

解决:

  1. 在组合进程的 case 之前为所有输出设置默认值
  2. 确保 case 有 when others 分支
  3. 检查所有 if-elsif-else 是否有完整的 else 分支

问题 6:VWF 文件中无法找到内部信号

原因: VWF 仿真只能观察端口信号,不能观察内部信号

解决:

  • 如需观察内部信号(如 current_state),将其通过端口引出
  • 本设计已将 state_out 端口引出用于调试
  • 或者使用 ModelSim 仿真(如果可用)

问题 7:编译时间过长

原因: 器件选择过大,或设计中有不必要的高位宽计数器

解决:

  1. 选择较小的目标器件(仿真阶段不影响结果)
  2. 仿真时使用缩短的分频常量

问题 8:Node Finder 中找不到信号

原因: 可能没有成功编译,或选择了错误的筛选条件

解决:

  1. 先确保 Analysis & Elaboration 成功
  2. Node Finder 中 Filter 选择 Pins: all
  3. 确认 Look in 选择的是正确的实体名
  4. 点击 List 刷新

十一、仿真技巧与注意事项

技巧 1:分模块仿真(推荐)

不要一上来就仿真顶层模块。按以下顺序逐个验证:

  1. clk_divider → 验证分频输出频率
  2. debounce → 验证消抖和边沿检测
  3. display_driver → 验证七段码输出
  4. door_controller → 验证开关门流程和 10 秒定时
  5. alarm_controller → 验证报警 toggle 和 LED 闪烁
  6. request_handler → 验证请求寄存和清除
  7. elevator_ctrl → 验证状态机逻辑(核心!)
  8. elevator_top → 最终集成验证

每次仿真某个子模块时:

  • Assignments → Settings... → General → 将 Top-level entity 改为该模块名 → OK
  • 重新 Analysis & Elaboration
  • 重新 Generate Functional Simulation Netlist
  • 新建/使用对应的 VWF 文件

技巧 2:快速修改仿真参数

VWF 文件可以直接编辑后重新仿真,不需要重新编译:

  1. 修改波形编辑器中的输入信号值
  2. 保存 VWF 文件
  3. 直接运行 Processing → Start Simulation

技巧 3:波形快捷键

快捷键 功能
Ctrl + 鼠标滚轮 缩放波形
Ctrl + Home 回到起始位置
Ctrl + End 跳到结束位置
Ctrl + F 查找信号

技巧 4:仿真加速

仿真 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 时钟。

技巧 5:Bus 信号设置任意值

设置多位总线信号的任意值:

  1. 选中总线信号的某个时间区间
  2. 右键 → Value → Arbitrary Value...
  3. 在弹出框中输入值(选择 Binary 格式)
  4. 例如设置 req_go = "100" 表示去3楼
  5. 点击 OK

技巧 6:创建分组信号

可以将相关信号分组显示以便观察:

  1. 右键信号列表 → Insert → Insert Group...
  2. 输入分组名(如 "Requests")
  3. 将相关信号拖入分组

注意事项

  1. 每次修改 VHDL 代码后,必须重新:编译 → 生成仿真网表 → 运行仿真
  2. 仿真完记得恢复分频常量!否则下载到板子上分频不正确
  3. VWF 文件与工程绑定,更换顶层实体后需要重新创建或修改 VWF
  4. 功能仿真不考虑延迟,通过功能仿真不代表时序一定正确
  5. 截图保存波形用于课程设计报告(使用系统截图工具或 Print Screen)

附录 A:仿真验证检查清单

在提交课程设计前,确认以下所有仿真场景已通过:

序号 仿真场景 模块 状态
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 ☐ 通过

附录 B:VWF 文件信号值快速参考

工具栏按钮说明

按钮 名称 功能
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

附录 C:完整仿真流程速查

[开始]
   │
   ├── 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 → 重新编译 → 重新生成网表 → 重新仿真