哈Ha! 我想为FPGA的发展做出自己的贡献。 在本文中,我将尝试解释如何在VHDL中描述控制七段显示器的设备。 但是,在开始之前,我想简单地谈谈我如何进入FPGA以及为什么选择VHDL语言。
大约半年前,我决定尝试进行FPGA编程。 在那之前,我从未遇到过电路。 使用微控制器(Atmega328p,STM32)的经验很少。 在决定适应FPGA之后,立即出现了选择我要使用的语言的问题。 之所以选择VHDL,是因为其严格的键入。 作为一个初学者,我想在合成阶段而不是在工作设备上捕获尽可能多的问题。
为什么要精确显示七段显示器? LED闪烁已经很累,并且闪烁逻辑并不代表任何有趣的事情。 一方面,控制显示器的逻辑要比使LED闪烁更复杂(即,编写LED更为有趣),另一方面,实现起来也很简单。
我在创建设备的过程中使用了什么:
- FPGA Altera Cyclone II(我知道它已经过时了,但是中国人可以花一分钱买到它)
- Quartus II 13.0.0(据我所知,这是支持Cyclone II的最新版本)
- 仿真器模型
- 带移位寄存器的七段显示
挑战赛
创建一个设备,该设备将在一个周期内显示数字0-9,每秒钟一次,显示屏上显示的值应增加1。
有许多方法可以实现此逻辑。 我将把该设备分为多个模块,每个模块将执行一些操作,该操作的结果将传输到下一个模块。
模组
- 该设备应该能够计算时间。 为了计算时间,我创建了一个“延迟”模块。 该模块有1个输入信号和1个输出信号。 该模块接收FPGA频率信号,并在指定数量的输入信号周期后,将输出信号的值更改为相反的值。
- 该设备应从0到9进行读取。bcd_counter模块将用于此目的。
- 为了在显示器上点亮一个分段,您需要在显示器的移位寄存器中将与该分段相对应的位设置为0,并且为了将该分段清除为位,请写入1(我的显示器具有反向逻辑)。 bcd_2_7seg解码器将处理所需位的安装和重置。
- 发送器模块将负责数据传输。
主机设备将控制模块之间信号的正确传输,并在完成数据传输后生成rclk信号。
从图中可以看到,该设备具有1个输入信号(clk)和3个输出信号(sclk,dio,rclk)。 clk信号进入2个信号分配器(sec_delay和transfer_delay)。 周期为1s的传出信号离开sec_delay设备。 在此信号的上升沿,计数器(bcd_counter1)开始生成下一个要显示的数字。 生成数字后,解码器(bcd_2_7seg1)将数字的二进制表示形式转换为显示屏上的点亮和不点亮段。 使用发送器(发送器1)将其发送到显示器。 发送器使用transfer_delay设备计时。
代号
为了在VHDL中创建设备,使用了实体和体系结构两个组件的构造。 实体声明用于与设备一起使用的接口。 体系结构描述了设备的逻辑。
通过通用字段,我们可以将设备设置为所需的延迟。 在端口字段中,我们描述了设备的传入和传出信号。
处理部分内部的代码是顺序执行的,任何其他代码是并行执行的。 在方括号中,在process关键字之后,通过更改给定过程将开始的位置(灵敏度列表)来指示信号。
就执行逻辑而言,bcd_counter设备与延迟设备相同。 因此,我将不对其进行详细介绍。
这是解码器的实体和架构 entity bcd_to_7seg is port(bcd: in std_logic_vector(3 downto 0) := X"0"; disp_out: out std_logic_vector(7 downto 0) := X"00"); end entity bcd_to_7seg; architecture bcd_to_7seg_arch of bcd_to_7seg is signal not_bcd_s: std_logic_vector(3 downto 0) := X"0"; begin not_bcd_s <= not bcd; disp_out(7) <= (bcd(2) and not_bcd_s(1) and not_bcd_s(0)) or (not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1) and bcd(0)); disp_out(6) <= (bcd(2) and not_bcd_s(1) and bcd(0)) or (bcd(2) and bcd(1) and not_bcd_s(0)); disp_out(5) <= not_bcd_s(2) and bcd(1) and not_bcd_s(0); disp_out(4) <= (not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1) and bcd(0)) or (bcd(2) and not_bcd_s(1) and not_bcd_s(0)) or (bcd(2) and bcd(1) and bcd(0)); disp_out(3) <= (bcd(2) and not_bcd_s(1)) or bcd(0); disp_out(2) <= (not_bcd_s(3) and not_bcd_s(2) and bcd(0)) or (not_bcd_s(3) and not_bcd_s(2) and bcd(1)) or (bcd(1) and bcd(0)); disp_out(1) <= (not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1)) or (bcd(2) and bcd(1) and bcd(0)); disp_out(0) <= '1'; end bcd_to_7seg_arch;
该设备的所有逻辑都是并行执行的。 我在自己频道的其中一部视频中谈到了如何获取此设备的公式。 谁在乎,这是
视频的链接。
在发送器设备中,我结合了串行和并行逻辑 entity transmitter is port(enable: in boolean; clk: in std_logic; digit_pos: in std_logic_vector(7 downto 0) := X"00"; digit: in std_logic_vector(7 downto 0) := X"00"; sclk, dio: out std_logic := '0'; ready: buffer boolean := true); end entity transmitter; architecture transmitter_arch of transmitter is constant max_int: integer := 16; begin sclk <= clk when not ready else '0'; send_proc: process(clk, enable, ready) variable dio_cnt_v: integer range 0 to max_int := 0; variable data_v: std_logic_vector((max_int - 1) downto 0); begin
对于sclk信号,我将进入发送器的clk信号的值重定向,但仅当设备当前正在发送数据时(信号就绪=假)。 否则,sclk信号值将为0。在数据传输开始时(启用=真信号),我将来自进入设备的两个8位向量(digit_pos和digit)的数据合并为一个16位向量(data_v)并发送该向量的数据每个时钟为一位,从而设置了传出信号dio中发送位的值。 关于该设备的有趣之处,我想指出的是,dio中的数据被设置为clk信号的后沿,并且当sclk信号的前沿到达时,来自dio引脚的数据将被写入显示器的移位寄存器。 传输完成后,通过设置ready <= true信号,我向其他设备发信号通知传输已完成。
这是显示设备的实体和体系结构的外观 entity display is port(clk: in std_logic; sclk, rclk, dio: out std_logic := '0'); end entity display; architecture display_arch of display is component delay is generic (delay_cnt: integer); port(clk: in std_logic; out_s: out std_logic := '0'); end component; component bcd_counter is port(clk: in std_logic; bcd: out std_logic_vector(3 downto 0)); end component; component bcd_to_7seg is port(bcd: in std_logic_vector(3 downto 0); disp_out: out std_logic_vector(7 downto 0)); end component; component transmitter is port(enable: in boolean; clk: in std_logic; digit_pos: in std_logic_vector(7 downto 0); digit: in std_logic_vector(7 downto 0); sclk, dio: out std_logic; ready: buffer boolean); end component; signal sec_s: std_logic := '0'; signal bcd_counter_s: std_logic_vector(3 downto 0) := X"0"; signal disp_out_s: std_logic_vector(7 downto 0) := X"00"; signal tr_enable_s: boolean; signal tr_ready_s: boolean; signal tr_data_s: std_logic_vector(7 downto 0) := X"00";
该设备控制其他设备。 在这里,在声明辅助信号之前,我先声明将要使用的组件。 在架构本身中(在begin关键字之后),我创建设备实例:
- sec_delay-延迟组件的实例。 传出信号被路由到sec_s。
- transfer_delay-延迟组件的实例。 传出信号被发送到信号transfer_clk。
- bcd_counter1-bcd_counter组件的实例。 传出信号被路由到bcd_counter_s。
- bcd_to_7seg1-bcd_to_7seg组件的实例。 传出信号被路由到disp_out_s。
- 发送器1是发送器组件的一个实例。 传出信号被发送到sclk,dio,tr_ready_s信号。
在组件实例之后,将声明一个进程。 此过程解决了几个问题:
如果发送器不忙,则该过程将初始化数据传输的开始。 if(tr_ready_s) then if(not (prev_disp = disp_out_s)) then prev_disp := disp_out_s;
- 如果发送器忙(tr_ready_s = false),则该过程将信号disp_refresh_s <= true的值设置为true(此信号表示传输完成后,必须更新显示屏上的数据)。 还将设置信号值tr_enable_s <= false,如果在传输完成之前未完成此操作,则将下载下载到发送器的数据
数据传输完成后设置并重置rclk信号 if(rclk_v = '1') then disp_refresh_s <= false; end if; if(tr_ready_s and disp_refresh_s) then rclk_v := '1'; else rclk_v := '0'; end if; rclk <= rclk_v;
时序图
首先,发送数据“ 10011111”。 然后,数字在显示屏上的位置为“ 00010000”(此参数以常量X“ 10”的形式到达变送器)。 在这两种情况下,最右边的位(lsb)首先被发送。
所有代码都可以在
github上查看。 带下标* _tb.vhd的文件是相应组件的调试文件(例如,generator_tb.vhd是发送器的调试文件)。 以防万一,我也将它们上传到了github。 此代码已下载并在实际板上工作。 谁在乎,您可以在
此处看到代码说明(从15:30开始)。 谢谢您的关注。