硬件流水灯
注意事项
本节作为选做内容, 不计入实验成绩, 有兴趣的同学可以参照教程实现硬件流水灯.
硬件流水灯
这一节将介绍硬件流水灯的实现方式, 使用硬件来延迟和控制输出, 处理器只需要控制延迟信息和流水灯端口的输出模式, 对应的硬件按照这两个控制信息来执行相应的输出.
硬件流水灯介绍
在上一节中, 我们提到了使用 GPIO 端口实现流水灯, 其有一个致命的缺点, 就是处理器需要全程参与控制的过程, 这使得处理器的执行效率非常低. 那我们可不可以将延迟和移位的功能交给硬件执行呢?

这就是我们本节设计的硬件流水灯, 如上图所示. 我们使用硬件来延迟和控制输出, 处理器只需要控制延迟信息和流水灯端口的输出模式, 对应的硬件按照这两个控制信息来执行相应的输出, 这就解放了处理器, 能提高处理器的代码执行效率. 硬件流水灯包括两个控制字, 一个为 WaterLight_speed 用于控制流水灯的流水速度, 另一个为 WaterLight_mode 用于控制硬件流水灯 8bits 的 4 种输出模式. 我们规定, 流水灯模式控制寄存器 WaterLight_mode 地址为 0x40000000 , 流水灯速度控制寄存器 WaterLight_speed 地址为 0x40000004 .
- 全灭模式: 所有灯都不亮;
- 左移模式: 同一时间点亮一个灯, 依次左移, WaterLight_mode 对应输入为 0x01 ;
- 右移模式: 同一时间点亮一个灯, 依次右移, WaterLight_mode 对应输入为 0x02 ;
- 闪烁模式: 所有灯同时闪烁, WaterLight_mode 对应输入为 0x03 .
硬件代码
本次的代码都在 "CortexM0_SoC/Task3/WaterLight" 目录下, 先打开 "./rtl/WaterLight.v", 按照之前所说的硬件流水灯的 4 种模式, 补全输出代码, 如下所示:
//------------------------------------------------------
// OUTPUT MUX
//------------------------------------------------------
改为
//------------------------------------------------------
// OUTPUT MUX
//------------------------------------------------------
always@(*) begin
case(WaterLight_mode)
8'h01 : begin LED = mode1; end
8'h02 : begin LED = mode2; end
8'h03 : begin LED = mode3; end
default : begin LED = 8'h00; end
endcase
end
上面的代码就是根据 WaterLight_mode 控制字, 将 LED 输出对应的 4 种模式.
接下来, 向 SoC 中添加 WaterLight , 在此之前, 我们需要参考完成之前添加 DATARAM 和实验二添加 RAMCODE 的步骤. 在顶层模块中已经添加好流水灯总线接口以及对应的硬件代码, 需要完成将流水灯总线接口接入总线扩展模块中预留的 P2 接口.
第一步, 完成 DATARAM 所需要的相关步骤.
第二步, 在 "AHBlite_Decoder.v" 中 WaterLight (Port 2) 端口参数将其使能.
第三步, 根据之前所述的 memory map , WaterLight 的总线编码为 0x40000000-0x4000000f , 因为对于一次总线操作, 只要地址总线的高 28 位为 0x4000000 , 则 Decoder 认为这是一次对流水灯的操作, 进而生成流水灯总线选择信号. 在译码部分插入流水灯的译码器代码.
//0x40000000 WaterLight MODE
//0x40000004 WaterLight SPEED
/*Insert WaterLight decoder code there*/
assign P2_HSEL = 1’b0;
/***********************************/
改为
//0x40000000 WaterLight MODE
//0x40000004 WaterLight SPEED
/*Insert WaterLight decoder code there*/
assign P2_HSEL = (HADDR[31:4] == 28'h4000000) ? Port2_en : 1'b0;
/***********************************/
接下来, 需要在顶层模块中将数据存储器总线接口与流水灯总线接口分别与总线扩展模块的 P1、P2 端口连接, 数据存储器和流水灯以及它们的总线接口都已经在顶层模块 "CortexM0_SoC.v" 中例化完成, 只需要将其总线接口的连线部分补充完毕, 实现正确的端口连接.
至此, 我们就完善好了所有 SoC 的硬件代码.
汇编代码
按照实验二所述, 新建 keil 工程, 编写汇编程序, 让流水灯流起来.
在 "/Task3/WaterLight/keil/startup_CMSDK_CM0.s" 文件中, 程序进入 WaterLight 段后, R0 存储流水灯模式, R1 存储模式转换计数器地址, R2 存储流水灯模式寄存器地址, R4 存储计数器时间. 当流水灯切换到当前模式后, 程序跳转至 delay 段开始计时, 流水灯模式保持不变 z 直到计数器计时完成, 程序返回, 流水灯模式改变, 循环往复.
需要注意的是, 为了方便仿真观察, 我们将流水灯模式转换间隔时间设置得非常小, 在接下来的上板调试时, 需要重新修改 R4 的值, 使流水灯模式转换时间保持在 10s 左右.
在汇编文件中补充 delay 段, 利用 R4 计数至 16 后返回实现 delay 功能.
; Finish function right model
;;;;;;;;;;;;;;;;;;;;;;
改为
; Finish function right model
STR R0, [R2] ;Set WaterLight mode
MOVS R0, #2 ;WaterLight mode initial
MOVS R1, #1 ;Interval cnt initial
BL delay
;;;;;;;;;;;;;;;;;;;;;;
汇编代码修改完成后, 点击编译, 自动生成目标文件.
系统仿真与调试
ModelSim 仿真
照实验二所述, 新建 modelsim 工程, 将之前编写的 verilog 文件添加进工程, 编译并开始仿真, 观察波形, 可以看到流水灯速度正确设置, 流水灯模式在周期性地改变.
硬件调试
由于调试是进行的单步调试, 因此暂时不必对 R4 的值进行调整, 当前 R4 的值决定了每 30 步左右流水灯模式改变一次.
将新编写的 verilog 文件添加进 TD 工程中, 将 led 灯添加进管脚约束, 综合布局布线后生成比特流文件并下载进 FPGA 中.
按照之前讲述的方法连接调试器与 PC , 打开 Keil 进行调试, 查看调试结果.
观察 FPGA 上的 LED 灯, 能看到其跟随程序的运行做出对应的反应, 在 Keil 的调试界面也能看到各个寄存器正确的赋值.
GPIO综述
GPIO 端口加上处理器的配合实现流水灯, 其特点在于其通用性, 通用性就来自于软件程序的灵活性, 对于用户来说, 当硬件流水灯外设没有应用场景需要的流水灯模式时, 程序员还可以通过软件编程配合 GPIO 的方式满足需求. 当然, 其缺点也很明显, 处理器的执行效率太低, 处理器只能做流水灯这一件事. 那么, 硬件流水灯的特点在于其专用性, 专用性就来自于流水灯硬件只干流水灯一件事情, 处理器只需要控制其该怎么去干流水灯这件事情就可以了, 因此对于实现流水灯功能, 用于硬件流水灯的 SoC 系统的效率就比软件实现流水灯的处理器系统高. 同样的, 其缺点就是硬件流水灯系统的输出模式是固定的, 在不满足应用需求时, 用户只能更新 SoC 平台.