实验前请点击获取配套资料
关于LAB2:
- 在本实验中, 你将动手实现一个基于 CortexM0 的 SoC 基础软硬件系统, 然后通过 Modelsim 对系统功能进行仿真, 仿真通过后, 使用 Vivado 搭建系统并将其下载配置到 FPGA 板卡中, 然后使用 Keil 进行实际上板调试.
- 本实验旨在加深大家对基于 CortexM0 的 SoC 的理解, 熟悉各个软件的基本使用方法, 所以本实验的软件部分和硬件部分都非常简单.
- 在进行本实验之前, 你应该已经理解了硬件设计的涵义, 掌握了四种 verilog 关键字语法: always, assign, if, case, 而且掌握了模块例化的基本方式. 另外, 你还应当对 AHB 总线协议有一定的了解.
搭建Keil工程
注意, 本实验没有实现数据存储器, 所以我们暂时不能使用 Load/Store 指令. 在本实验中, 我们仍然使用汇编语言实现一个简单的循环计数器.
首先, 按照在 LAB1 中的方式, 在 "/Task2/keil" 文件夹中创建名为 code 的 Keil 工程, 由于涉及到实际上板调试, 这里的 Target 设置方式与之前有些不同:
仿真/调试设置
其中 "code.ini" 文件位于 "Task2/keil/code.ini", 直接将其添加到图中位置即可.
接着, 我们点击进入 Debugger 的 settings 窗口, 选择上方的 Flash Download. 设置方式如下:
Flash 下载设置
点击 "OK" 后, 进入 Utilities 设置界面, 取消勾选 Update Target before Debugging.
Utilities 设置
注意
- 上述几步设置与上板调试有关, 与接下来的 modelsim 仿真无关, 我们之后再回来讨论这几步的作用.
- 除上述对 Debug 和 Utilities 的设置, 不要忘了其他部分 (Device, Target, Output 等) 的设置哦, 这些部分的设置方法与 LAB1 "搭建 Keil 环境" 一节中相同.
- 将 "/Task2/keil/startup_CMSDK_CM0.s" 文件添加至工程中, 进行编译. 这里的启动文件中的程序与 LAB1 相同, 仍然是一个循环计数器.
注意得到的 "/Task2/keil/code.hex" 文件, 它代表了程序的可执行文件, 是系统的软件组成部分, 为了将它与我们的硬件结合起来, 实现整个系统的 modelsim 仿真, 我们需要修改 "/Task2/rtl/Block_RAM.v" 文件, 将 RAM 初始化文件路径设为上述 "code.hex" 的路径. 注意这里必须使用绝对路径:
initial begin
$readmemh("Your/absolute/path/to/code.hex",mem);
end
这样, 我们就在仿真的层面上, 将程序的软件代码 "下载" 到了硬件的 "内存" 中.
Modelsim 仿真
打开 Modelsim 软件, 点击左上角 File 菜单, 再点击 New -> Project 创建一个 Modelsim 工程, 命名为 "code", 工程地址选为 "/Task2/modelsim/" 文件夹.
新建 Modelsim 工程
点击 OK 后, 选择 Add Existing File, 添加源文件.
添加源文件
由于我们的软件程序已经被 "下载" 到了 RAM 中, 这里只需要把 "/Task2/rtl/" 下的所有的 RTL 文件添加进来, 另外, 还需要把 Testbench 文件 "/Task2/modelsim/CortexM0_SoC_vlg_tst.v" 中的添加进来.
什么是 Testbench ?
Testbench 是数字系统仿真的测试用例, 以 verilog module 的形式存在, Testbench 用于测试激励的产生和响应信号的收集处理, 虽然不属于设计的组成部分, 但必须是仿真时的顶层文件.
关于本实验中的 Testbench
打开 "/Task2/modelsim/CortexM0_SoC_vlg_tst.v" 可以看到:
timescale 1 ps/ 1 ps
module CortexM0_SoC_vlg_tst();
reg clk;
reg RSTn;
reg TXD;
CortexM0_SoC i1 (
.clk(clk),
.RSTn(RSTn)
);
initial begin
clk = 0; RSTn=0;
#100 RSTn=1;
end
always begin #10 clk = ~clk; end
endmodule
可见, Testbench 中只是对整个 SoC 做了简单的例化, 并提供了时钟信号和初始复位.
文件添加完毕后, 点击编译按钮, 对所有源文件进行编译查错. 通过编译的文件后的 "?" 都变成了 "√":
编译之前
编译通过
正如之前所说, Testbench 是仿真时的顶层文件, 所以仿真的对象应该是 Testbench 文件. 我们选择左侧导航栏下方的 Library, 展开 work, 右键单击 CortexM0_SoC_vlg_tst, 选择 Simulate without Optimization, 开始仿真.
选择 Library -> work -> CortexM0_SoC_vlg_tst
右键 CortexM0_SoC_vlg_tst 选择 Simulate without Optimization
Instance, Object 和 Wave
仿真时三个非常重要的窗口:
- Instance: 以 Testbench 为顶层模块, 层次化地展示所有被例化的模块. 注意, 展示的是实例名而不是模块名.
- Object: 可以理解为展示了某个模块中的所有信号.
- Wave: 波形窗口, 展示被添加的信号及其波形.
在 Modelsim 中, 如果不小心关闭了这些窗口, 可以在 View 菜单栏中重新打开.
下一步, 我们要把待观察的信号添加到 Wave 窗口. 那么我们应该添加哪些信号呢? 为了观察程序的运行情况, 我们需要观察处理器中寄存器的值, 而 Cortex-M0 中的调试恰好有捕捉寄存器值的功能, 所以我们只需要把 Cortex-M0 中的对应信号添加进来即可.
注意, 在 CortexM0_SoC_vlg_tst.v 中, SoC 的实例名为 i1, 而在 SoC 中, Cortex-M0 核的实例名为 u_logic. 我们在 Instance 窗口中展开 CortexM0_SoC_vlg_tst, 依次选择 i1 -> u_logic.
双击 u_logic, 该模块中的所有信号就呈现在了 Object 窗口中. 在 Object 窗口中选中调试信号 vis_r0_o, vis_r1_o, vis_pc_o; 另外, 为了观察 Cortex-M0 数据接口的工作过程, 我们继续选中 HADDR, HSIZE, HTRANS, HRDATA 这几个 AHB 信号, 右键点击 Add Wave, 这些信号就被添加到了 Wave 窗口中.
选择待观察的信号
在 Wave 窗口中选中所有信号, 右键点击 Radix -> Hexadecimal, 将其改为 16 进制显示, 以便观察.
Wave 窗口中的信号
保存波形格式
在 Modelsim 中, 所有对波形格式 (如显示格式, 颜色, 信号分组) 的修改可以被保存为一个 do 脚本文件, 以便下次进入仿真后加载执行.
保存方式为: 点击 Wave 窗口左上角 File 菜单, 选择 Save Format:
选择波形格式文件保存路径
加载方式为: 点击 Wave 窗口左上角 File 菜单, 选择 Load -> Macro File.
在运行仿真前, 首先要设置仿真时间, 这里设置仿真时间为 100ns, 然后点击旁边按钮正式运行仿真.
设置仿真时间并运行仿真
仿真运行停止后, 点击 Wave 窗口菜单栏中的17将波形缩放到满屏, 然后通过滚动条和 Ctrl + 鼠标滚轮将波形缩放到合适比例.
仿真波形
下面根据程序功能, 分析仿真波形:
- 寄存器
vis_r1_o 在 0-4 范围内循环计数, 与程序功能相符.
vis_pc_o 在 0x25-0x29 之间循环, 为什么? 这时我们就需要进入之前生成的 "/Task2/keil/code.txt" 文件一探究竟了, 在 "code.txt" 的 .text 中有这样一段:
start
0x00000048: 2104 .! MOVS r1,#4
0x0000004a: 2000 . MOVS r0,#0
0x0000004c: 1c40 @. ADDS r0,r0,#1
0x0000004e: 4288 .B CMP r0,r1
0x00000050: d0fb .. BEQ 0x4a ; start + 2
0x00000052: d1fb .. BNE 0x4c ; start + 4
这表明, 程序运行时, 取指地址在 0x0000004a-0x00000052 之间循环, 不难发现, vis_pc_o 恰好是取指地址去除 LSB 后的结果.
- AHB 信号
Cortex-M0 属于冯诺依曼架构, 指令和数据都要通过同一个 AHB 接口访问. 而本实验中未涉及对数据的读写, 所以这里的 AHB 接口仅用于指令的读取. 另外, 由于 Cortex-M0 内部未设有 Cache 来缓存指令和数据, 所以对于循环程序而言, 也需要不断去主存中读取同一段指令. 所以我们会看到 HADDR 不断在 0x0000004a-0x00000052 之间循环, 对应的 HTRANS 在传输时变为 1, HSIZE 在传输时变为 2, 表示 32 位数据传输, 相应的指令数据在下一拍表现在 HRDATA 总线上.
若要退出仿真, 点击主窗口菜单中的 Simulate -> End Simulation.
退出仿真
命令行操作 - 从小白到工程师
在工程中, 使用命令行或脚本操作软件可以为我们节省大量的操作时间, 提升我们的工作效率. 熟练的命令行技能是一个优秀工程师必备的专业素质.
如果你厌倦了繁琐的图形界面操作, 请跟我一起学习使用命令行操作 Modelsim 吧.
Modelsim 采用 TCL 命令, 你可以在命令行窗口 (Transcript) 中执行单条命令, 也可以使用 do 脚本来执行多条命令. 对于本仿真实验, 这里给出了一种等效的 do 脚本的编写方法, 有兴趣的同学可以自行尝试(注意你的文件路径):
编写 "run.do" 脚本文件:
vlib work
vlog "./CortexM0_SoC_vlg_tst.v"
vlog "./rtl/*"
vsim -voptargs=+acc -c work.CortexM0_SoC_vlg_tst
add wave "CortexM0_SoC_vlg_tst/i1/u_logic/*"
run 100ns
在 Modelsim Transcript 窗口中执行命令:
do run.do
END
文章来源:
- https://uestcwq.gitee.io/wq-gitbook/lab2/sec_2.html
- https://uestcwq.gitee.io/wq-gitbook/lab2/sec_3.html
推荐内容:
- 【课程实验 LAB0】FPGA简介
- 【课程实验 LAB0】为什么是 FPGA?
- 【课程实验 LAB0】设计方法
- 【课程实验 LAB0】平台介绍
- 【课程实验 LAB0】软硬件关系及软件Modelsim, Vivado, Keil的介绍
- 【课程实验 LAB1】“施法”让CPU动起来:搭建 Keil 环境
- 【课程实验 LAB1】“施法”让CPU动起来:运行吧,第一个汇编程序
- 【课程实验 LAB1】实现你的首个SoC:函数调用
- 【课程实验 LAB2】实现你的首个SoC:硬件部分说明
更多内容请关注微处理器系统结构与嵌入式系统设计专栏