1. 触发器的设计
1.1 基本D触发器的设计
基本的D触发器分为三个端口:信号输入端D、信号输出端Q、以及时钟信号输入CLK。
其工作特性:在时钟上升沿到来的瞬间,从输入端Q获取信号来更新输出端口D的信号,其余时刻输出信号D保持不变。
以下是实现代码:
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY DFF1 IS
PORT(
CLK,D : IN STD_LOGIC;
Q : OUT STD_LOGIC
);
END DFF1;
ARCHITECTURE BHV OF DFF1 IS
SIGNAL Q1 : STD_LOGIC;
BEGIN
PROCESS(CLK,D) BEGIN
IF CLK'EVENT AND CLK='1'
THEN Q1<=D;
END IF;
END PROCESS;
Q<=Q1;
END BHV;
最终仿真效果如下图所示
1.1.1 几种不同的上升沿触发检测方式
- 使用event函数与逻辑与组合形式
CLK'EVENT AND CLK='1'
- 更保险的event+逻辑与组合形式
CLK'EVENT AND (CLK='1') AND (CLK'LAST_VALUE='0')
- lastvalue函数形式
CLK='1' AND CLK'LAST_VALUE='0'
- rising_edge()函数形式
IF RISING_EDGE(CLK)
1.2 异步复位与时钟使能的D触发器
顾名思义,就是比基本的D触发器多了两个输入端:异步复位端RST和时钟使能端EN。对于异步复位端RST,只要其输入为“1”,D触发器输出端立即清零;对于时钟使能端EN,只有其为“1”时,D触发器才能接受时钟上升沿的信号,对输出端口产生信号更新。实现代码如下:
library ieee;
use ieee.STD_logic_1164.all;
entity dff2 is
port (
D,CLK,EN,RST : IN std_logic;-- EN时钟使能 RST异步清零
Q : OUT std_logic
) ;
end dff2;
architecture behav of dff2 is
signal Q1 : std_logic;
begin
reg : process( RST,EN,CLK,D )
begin
if RST='1' then --触发器被清零
Q1<='0';
elsif EN='1' then
if (CLK'EVENT AND CLK='1') then
Q1 <= D;
end if ;
end if ;
end process ; -- reg
Q <= Q1;
end behav ; -- behav
这部分存在有两个疑问,留待后续解决:
- process的敏感变量表中的敏感变量该如何选取呢?
将进程内部判断语句中出现的变量、赋值符号右侧的变量都纳入敏感变量表中。 - 仿真时存在一个10ns左右的延时是合理的吗?
合理的!
1.3 同步复位控制的D触发器
顾名思义,该触发器含有四个输入端口,信号输入端D、时钟信号CLK,时钟使能信号EN以及同步复位端RST,“RST=1”只有在等到信号上升沿到达时才能起到清零作用。
这里需要注意两个重点:
- 同步信号放置在时钟边沿检测以内(以下),异步信号放置在时钟边沿检测以外(以上)。
- 在if判断语句中出现else语句说明该判断语句肯定是完整的,实例化后是一个组合逻辑器件。
以下是实现代码:
library IEEE;
use IEEE.std_logic_1164.all;
entity dff3 is
port(
RST,EN,CLK,D : in std_logic;
Q : out std_logic
);
end dff3;
architecture behav of dff3 is
signal Q1 : std_logic;
begin ---千万不要忘记architecture里的begin!
reg:process (CLK,EN) begin
if (CLK'event and CLK='1' and EN='1') then --外部的if语句是不完整的,构成了时序逻辑器件
if RST = '1' then --内部的if是完整的,构成了一个多路选择器
Q1<='0';
else --加上了else肯定是完整的判断语句
Q1 <=D;
end if;
end if;
end process;
Q <= Q1;
end behav;
仿真效果图如下:
2. 锁存器的设计
2.1 基本的锁存器
基本锁存器端口组成:
- 输入端口:时钟信号CLK以及信号输入D
- 输出端口:信号输出Q
基本锁存器工作特性:当CLK=‘1’时,输出信号Q时刻等于输入信号D,当CLK=‘0’时,输出信号Q保持。
实现代码如下:
library IEEE;
use IEEE.std_logic_1164.all;
entity LTCH1 is
port(
CLK,D : in std_logic;
Q : out std_logic
);
end LTCH1;
architecture bhv of LTCH1 is
signal Q1 : std_logic;
begin
reg:process(CLK,D) begin
if CLK='1' then
Q1 <= D;
end if;
end process;
Q <= Q1;
end; --bhv
仿真波形如下:
2.2 含有清零控制的锁存器
3. 计数器的设计
3.1 简单的四位二进制加法计数器
3.2 带有异步复位和同步加载功能的十进制加法计数器
4. 移位寄存器的设计
5. 实验题目
1)设计一个带计数使能、进位输出、预置数及同步清0的增1二十进制计数器
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity CNT20 is
port (
CLK,RST,EN,LOAD : in std_logic;-- 时钟信号、同步清零信号、计数使能信号、预置数信号
DATA : in std_logic_vector(4 downto 0);--预置数信号
DOUT : out std_logic_vector(4 downto 0);--计数输出
COUT : out std_logic--进位输出
) ;
end CNT20;
architecture bhv of CNT20 is
signal Q : std_logic_vector(4 downto 0);
begin
REG : process( CLK,RST,EN,LOAD )
begin
if CLK'EVENT and CLK='1' then
if RST='0' then Q <= (others=>'0');-- 同步清零
elsif EN='1' then
if LOAD='0' then Q <= DATA;--采用低电平进行预置数
elsif Q<19 then
Q <= Q + 1;--未达到进位条件则进位
else
Q <= (others=>'0');--手动清零
end if ;
end if ;
end if ;
end process ; -- REG
COM : process( Q )
begin
if Q="10011" then --20进制计数到19
COUT <= '1';
else
COUT <= '0';
end if ;
end process ; -- COM
DOUT <= Q;
end bhv ; -- bhv
2)设计并实现一个带计数使能及异步清0的增1、 8位二进制计数器
3)设计并实现一个带计数使能、进位输出、预置数及异步清0的增1/减1的8位二进制计数器
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity CNT_8 is
port (
CLK,RST,EN,LOAD : in std_logic;-- 时钟信号、异步清零信号、计数使能信号、预置数信号
DATA : in std_logic_vector(7 downto 0);--预置数信号
DOUT : out std_logic_vector(7 downto 0);--计数输出
COUT : out std_logic--进位输出
) ;
end CNT_8;
architecture bhv of CNT_8 is
signal Q : std_logic_vector(7 downto 0);
begin
REG : process( CLK,RST,EN,LOAD )
begin
if RST='0' then Q <= (others=>'0');
elsif CLK'EVENT and CLK='1' then
if EN='1' then
if LOAD='0' then Q <= DATA;
else
Q <= Q + 1;
end if ;
end if ;
end if ;
end process ; -- REG
COM : process( Q )
begin
if Q="11111111" then --
COUT <= '1';
else
COUT <= '0';
end if ;
end process ; -- COM
DOUT <= Q;
end bhv ; -- bhv
注意,由于这个8位计数器是计满全部位,所以不需手动设置计数最高位的清零跳转。
4)设计6位串入并出左移移位寄存器(使用状态位实现数据自动加载)
--串行输入并行输出
library IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
entity SHIFT6_LEFT is
port (
CR,CLK,SHIFT : IN STD_LOGIC; --清零信号、时钟信号、串行输入信号
Y : OUT STD_LOGIC_VECTOR(5 DOwNTO 0) --并行四位输出
) ;
end SHIFT6_LEFT;
architecture A of SHIFT6_LEFT is
signal TEMP_DATA : STD_LOGIC_VECTOR(6 DOWNTO 0); --共计7位,增加一位作为状态位
begin
REG1:process( CLK ) --状态位"011111"工作特性的设定
begin
if CLK'EVENT AND CLK='1' then
if CR='0' then--信号清零
TEMP_DATA<="0000000";
elsif TEMP_DATA(6)='0' then--状态位重载 --此句更改了TEMP_DATA
--默认信号初始值都为0 (一般会先存在CR信号对TEMP_DATA进行清零)
TEMP_DATA<="111110" & SHIFT ;
else
TEMP_DATA <= TEMP_DATA(5 DOWNTO 0) & SHIFT ;--信号左移:串行输入一位 & 高四位右移
end if ;
end if ;
end process ;
REG2:process( TEMP_DATA ) --并行输出时刻的设定
--每 移动一位\状态位重载 检测一次
begin
if CLK'event and CLK='1' then
if TEMP_DATA(6)='0' then --输出状态位到达
Y <= TEMP_DATA(5 DOWNTO 0);
--else
--Y<="0000";
end if ;
end if ;
end process ;
end A ;
5)设计6位并入串出右移移位寄存器
library IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
entity SHIFT6_RIGHT is
port (
CR,CLK : IN STD_LOGIC;
SHIFT : IN STD_LOGIC_VECTOR(5 DOwNTO 0);
Y : OUT STD_LOGIC
) ;
end SHIFT6_RIGHT;
architecture A of SHIFT6_RIGHT is
signal TEMP_DATA : STD_LOGIC_VECTOR(11 DOWNTO 0);
begin
REG1:process( CLK )
begin
if CLK'EVENT AND CLK='1' then
if CR='0' then
TEMP_DATA<=(others=>'0');
elsif TEMP_DATA(0)='0' then
TEMP_DATA<=SHIFT & "011111";
else
TEMP_DATA(10 DOWNTO 0) <= TEMP_DATA(11 DOWNTO 1);
end if ;
end if ;
end process ;
Y <= TEMP_DATA(6);
end A ;
6)设计6位串入串出右移移位寄存器
7)设计6位并入并出左移移位寄存器
6. VHDL语言书写规范
摘录了几个我目前需要改进的、可以理解的VHDL书写习惯:
(完整版参考:VHDL书写规范)
- 实体、结构名、端口信号、常量用大写标识;同样的,变量与一般信号使用首字母大写表示。
- VHDL保留字使用小写
- 多比特信号都采用 downto描述
- 调用IEEE库时,IEEE使用大写,其余使用小写
- 每个信号、变量、常量和端口的定义都要有注释
- 进程之间使用“--------”隔开,若多个进程实现一个功能模块则,功能模块之间使用“–********”分隔。
- 端口名要对齐。冒号要对齐,in或out类属要对齐,矢量定义要对齐。
- 几个常见的信号缩写