第5章 CHAPTER 5 CPLD/FPGA应用实践 5.1常用组合逻辑电路的描述 常用的基本数字电路模块是数字系统中不可缺少的基本组成部分。数字逻辑电路可分为两类,一类逻辑电路的输出只与当时输入的逻辑值有关,而与输入的历史情况无关,这种逻辑电路称为组合逻辑电路(Combinational Logic Circuit); 另一类逻辑电路的输出不仅与电路当时输入的逻辑值有关,而且与电路以前输入过的逻辑值有关,这种逻辑电路称为时序逻辑电路(Sequential Logic Circuit)。 5.1.1非门电路的设计 1. 模型 根据VHDL的特点,对非门电路进行直接描述,其VHDI模型是非门的逻辑符号,如图5.1所示。 图5.1非门电路 其布尔代数模型为b=a-。 用VHDL描述为b<=NOT a。 2. 程序设计 按照VHDL的结构特点,首先要为其确定一个实体,然后确定一个结构体。 为了方便,取实体名为not_gate,取结构体名为behav。由于是门级的VHDL描述,直接给出VHDL文件。 【例5.1】非门的VHDL描述。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY not_gate IS PORT(a:IN STD_LOGIC; b:OUT STD_LOGIC); --定义输入端口a和输出端口b END not_gate; ARCHITECTURE behav OF not_gate IS BEGIN b<=NOT a;--逻辑"非"描述 END ARCHITECTURE behav; 3. 仿真验证 仿真验证的步骤如下: (1) 编译源文件的语法的正确性。 (2) 根据设计功能,编写测试文件。 (3) 进行功能仿真。根据上面的源程序和测试文件,调用Modelsim_Altera平台运行,得到功能仿真的验证结果如图5.2所示。 图5.2非门电路的功能仿真波形图 从图5.2可以看出非门的VHDL描述是正确的。 (4) 时间仿真。选择CPLD或FPGA作为承载器件,再次编译源文件时注意采用全编译方式,再次调用仿真结果如图5.3所示。从图中可以看出明显的延时信息,但逻辑关系是正确的。 图5.3非门电路的时间仿真结果 5.1.2其他基本门电路的设计 按照非门电路设计的方法和步骤,我们很容易用VHDL描述其他门的电路。在此只给出各种门电路的VHDL文件,其他步骤读者可以自己进行。 【例5.2】与门的VHDL描述。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY and_gate IS PORT(a,b:IN STD_LOGIC; c:OUT STD_LOGIC); END and_gate; ARCHITECTURE behav OF and_gate IS BEGIN c<=a AND b;--逻辑"与"描述 END ARCHITECTURE behav; 【例5.3】与非门的VHDL描述。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY nand_gate IS PORT(a,b:IN STD_LOGIC; c:OUT STD_LOGIC); END nand_gate; ARCHITECTURE behav OF nand_gate IS BEGIN c<=a NAND b; END ARCHITECTURE behav; 【例5.4】或非门的VHDL描述。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY nor_gate IS PORT(a,b:IN STD_LOGIC; c:OUT STD_LOGIC); END nor_gate; ARCHITECTURE behav OF nor_gate IS BEGIN c<=NOT(a OR b); END ARCHITECTURE behav; 【例5.5】异或非门的VHDL描述。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY nxor_gate IS PORT(a,b:IN STD_LOGIC; c:OUT STD_LOGIC); END nxor_gate; ARCHITECTURE behav OF nxor_gate IS BEGIN c<=NOT(a XOR b); END ARCHITECTURE behav; 【例5.6】三态门的VHDL描述。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY tri_gate IS PORT(a,ena:IN STD_LOGIC; b:OUT STD_LOGIC); END tri_gate; ARCHITECTURE behav OF tri_gate IS BEGIN b<=a WHEN ena='1' ELSE 'Z'; END ARCHITECTURE behav; 三态门仿真结果如图5.4所示,当使能信号ena为低电平时,输出b为高阻状态; 当使能信号ena为高电平时,输出b与输入a状态相同。 图5.4三态门仿真结果 5.2基本时序逻辑电路的VHDL描述 5.2.1D触发器的设计 1. 电路模型 D触发器的符号模型如图5.5所示。clr为异步复位信号,当clr为高电平时,输出Q为低电平与时钟边沿无关; 当复位信号为低电平且时钟的上 图5.5D触发器的 符号模型 升沿到来时,输出Q状态与输入d电平一致; 当时钟的上升沿未到来时,则等待它的到来状态保持不变,直到时钟信号上升沿到来为止。 2. 程序设计 【例5.7】异步复位的D触发器的VHDL描述。 library ieee; use ieee.std_logic_1164.all; entity dffa is port(D,clk,clr: in std_logic; Q: out std_logic); --定义输入/输出端口 end entity dffa; architecture behave of dffa is begin process(clk,D,clr)--进程敏感信号 begin ifclr='1'then Q<='0'; Elsif clk'event and clk='1'then--检测时钟上升沿 Q<=D; end if: end process; end architecture behave; 3. 仿真结果 功能仿真波形图如图5.6所示。 图5.6D触发器的功能仿真波形图 5.2.2T触发器的设计 T触发器逻辑符号如图5.7所示。图中clr_n为同步复位信号低电平有效,pr_n为异步置位信号。当pr_n为低电平时输出Q为高电平不需clk的上升沿触发,当pr_n为高电平时正常按照T触发器功能执行。每一个clk上升沿到来时首先判断clr_n状态,当clr_n为低电平时输出Q为低电平执行同步复位功能; 当clr_n为高电平时,输出Q为上一状态的Q与当前输入t信号电平做异或输出至当前状态。 图5.7T触发器逻辑符号 【例5.8】异步置位同步复位的T触发器的VHDL描述。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY t_ff IS PORT(clr_n,pr_n,t,clk:IN STD_LOGIC; Q:BUFFER STD_LOGIC); --输出端口Q类型定义为buffer,因为后续进程中需要读到该端口状态 END t_ff; ARCHITECTURE behav OF t_ff IS BEGIN PROCESS(clk,t,clr_n,pr_n)--进程敏感信号 BEGIN IFpr_n='0'THEN Q<='1'; ElSIF clk'EVENT AND clk='1'THEN--检测时钟上升沿 IFclr_n='0'THEN Q<='0'; ELSIF t='1' THEN Q<=t XOR Q; END IF; END IF; END PROCESS; END behav; 带有同步复位异步置位功能的T触发器的功能仿真波形图如图5.8所示。 图5.8T触发器的功能仿真波形图 5.2.3JK触发器的设计 【例5.9】基本JK触发器的VHDL描述(见图5.9)。 图5.9JK触发器的VHDL描述 library ieee; use ieee.std_logic_1164.all; entity jk is port(J,K,clk: in std_logic; Q: buffer std_logic); --定义输入/输出端口 end entity jk; architecture behave of jk is begin process(clk,J,K)--进程敏感信号 begin if clk'event and clk='1'then--检测时钟上升沿 Q<=((J and (not Q ))or ((not K)and Q)); end if: end process; end architecture behave; 基本JK触发器的功能仿真波形图如图5.10所示。 图5.10JK触发器的功能仿真波形图 5.2.4串行移位寄存器的设计 移位寄存器在微处理器的算术或逻辑运算中是一个常用的组件,移位方式有向左移位和向右移位。例5.10是一个双向串行移位寄存器,引入控制信号dir进行移位方向控制。 【例5.10】双向串行移位寄存器的VHDL描述。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY shift_reg IS PORT(clk,din,dir:IN STD_LOGIC; Q:OUT STD_LOGIC); END shift_reg; ARCHITECTURE behav OF shift_reg IS SIGNAL data:STD_LOGIC_VECTOR(7 DOWNTO 0):="00000000";--赋初值仿真使用 BEGIN PROCESS(clk,dir,din) BEGIN IF clk'EVENT AND clk='1' THEN IF dir='0' THEN--左移 data(0)<=din; FOR i IN 1 TO 7 LOOP data(i)<=data(i-1); END LOOP; ELSE--右移 data(7)<=din; FOR i IN 1 TO 7 LOOP data(i-1)<=data(i); END LOOP; END IF; END IF; END PROCESS; Q<=data(7) WHEN dir='0' ELSE --移位输出 data(0); END behav; 串行移位寄存器的仿真结果如图5.11所示。 图5.11串行移位寄存器的仿真波形 5.2.5分频电路的设计 计数器其实是一种分频电路,可以利用计数器实现偶数分频、奇数分频器、小数分频等,并且分频信号的占空比可以在一定范围内调整。 【例5.11】50%占空比的10分频器。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_UNSIGNED.ALL; ENTITY clk_div10 IS PORT(clk:IN STD_LOGIC; q:OUT STD_LOGIC); END clk_div10; ARCHITECTURE behave OF clk_div10 IS SIGNAL temp:INTEGER RANGE 0 TO 15:=0; BEGIN PROCESS(clk) BEGIN IF(clk'EVENT AND clk='1')THEN IF (temp=9) THEN--根据分频系数设计计数器范围N temp<=0; Else temp<=temp+1; END IF; END IF; END PROCESS; q<='1' WHEN temp>4 ELSE--根据占空比调整高电平出现的时刻 '0'; END behave; 仿真结果如图5.12所示。如果需要设计的是偶数分频N的分频器,须将计数器范围调整为N-1。 图5.1250%占空比的10分频器的仿真波形 【例5.12】50%占空比的9分频器。 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_UNSIGNED.ALL; ENTITY clk_div9 IS PORT(clk:IN STD_LOGIC; q:OUT STD_LOGIC); END clk_div9; ARCHITECTURE behav OF clk_div9 IS SIGNAL temp1,temp2:INTEGER RANGE 0 TO 15:=0; SIGNAL q1,q2:STD_LOGIC:='0'; BEGIN PROCESS(clk)--上升沿计数器,根据分频系数设计计数器范围N BEGIN IF(clk'EVENT AND clk='1')THEN IF (temp1=8) THEN temp1<=0; Else temp1<=temp1+1; END IF; END IF; END PROCESS; PROCESS(clk)--下升沿计数器,根据分频系数设计计数器范围N BEGIN IF(clk'EVENT AND clk='0')THEN IF (temp2=8) THEN temp2<=0; Else temp2<=temp2+1; END IF; END IF; END PROCESS; q1<='1' WHEN temp1>4 ELSE '0'; --根据占空比调整低电平出现的时刻 q2<='1' WHEN temp2>4 ELSE '0'; --根据占空比调整高电平出现的时刻 q<=q1 OR q2; END behav; 仿真结果如图5.13所示。如果需要设计的是奇数分频N的分频器,须将两个计数器范围调整为N-1。 图5.1350%占空比的9分频器的仿真波形 5.3常用算法VHDL实现 5.3.1流水线加法器的设计 流水线规则可以应用在FPGA设计中,只需要极少或者根本不需要额外的成本,因为每一个逻辑元件都包括一个触发器。采用流水线有可能将一个算术操作分解成一些小规模的基本操作,将进位和中间值存储在寄存器中,并在下一个时钟周期内继续计算。但是具体将加法器分成多少部分应视具体硬件结构而定。针对Cyclone器件,一个合理的选择就是采用一个带有16个LE的LAB组成一个流水线。例5.13给出了一个针对Cyclone器件的15位流水加法器的代码。 【例5.13】15位流水加法器的VHDL设计。 LIBRARY LPM; USE LPM.LPM_COMPONENTS.ALL; LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_ARITH.ALL; ENTITY pipeline_adder IS GENERIC(WIDTH:INTEGER:=15;--求位宽 width_l:INTEGER:=7; --低七位 width_h:INTEGER:=8; --高八位 co:INTEGER:=1--进位位 ); PORT(a,b:IN STD_LOGIC_VECTOR(WIDTH-1 DOWNTO 0); clk:IN STD_LOGIC; sum:OUT STD_LOGIC_VECTOR(WIDTH-1 DOWNTO 0) ); END pipeline_adder; ARCHITECTURE behav OF pipeline_adder IS SIGNAL a_l,b_l,resul_l,temp_l:STD_LOGIC_VECTOR(width_l-1 DOWNTO 0); SIGNAL a_h,b_h,resul_h,temp_h,temp_h1,temp_c:STD_LOGIC_VECTOR(width_h-1 DOWNTO 0); SIGNAL temp_sum:STD_LOGIC_VECTOR(WIDTH-1 DOWNTO 0);--输出寄存 SIGNAL temp_co1,temp_co11:STD_LOGIC_VECTOR(co-1 DOWNTO 0); --LSB进位寄存 BEGIN PROCESS(clk)--分解输入 BEGIN IF clk'EVENT AND clk='1' THEN FOR k IN width_l-1 DOWNTO 0 LOOP a_l(k)<=a(k); b_l(k)<=b(k); END LOOP; FOR k IN width_h-1 DOWNTO 0 LOOP a_h(k)<=a(k+width_l); b_h(k)<=b(k+width_l); END LOOP; END IF; END PROCESS; add_1:lpm_add_sub GENERIC MAP(lpm_width=>width_l, lpm_representation=>"UNSIGNED", lpm_direction=>"ADD") PORT MAP(dataa=>a_l, datab=>b_l, result=>resul_l, cout=>temp_co1(0)); add_2:lpm_add_sub--MSB求和 GENERIC MAP(lpm_width=>width_h, lpm_representation=>"UNSIGNED", lpm_direction=>"ADD") PORT MAP(dataa=>a_h,datab=>b_h,result=>resul_h); reg_1:lpm_ff GENERIC MAP(lpm_width=>width_l) PORT MAP(data=>resul_l,q=>temp_l,clock=>clk); reg_2:lpm_ff GENERIC MAP(lpm_width=>co) PORT MAP(data=>temp_co1,q=>temp_co11,clock=>clk); reg_3:lpm_ff GENERIC MAP(lpm_width=>width_h) PORT MAP(data=>resul_h,q=>temp_h,clock=>clk); temp_c<=(others=>'0'); add_3:lpm_add_sub--MSB求和结果与LSB进位相加 GENERIC MAP(lpm_width=>width_h, lpm_representation=>"UNSIGNED", lpm_direction=>"ADD") PORT MAP(cin=>temp_co11(0),dataa=>temp_h,datab=>temp_c,result=>temp_h1); PROCESS --输出寄存 BEGIN WAIT UNTIL clk='1'; FOR k IN width_l-1 DOWNTO 0 LOOP temp_sum(k)<=temp_l(k); END LOOP; FOR k IN width_h-1 DOWNTO 0 LOOP temp_sum(k+width_l)<=temp_h1(k); END LOOP; END PROCESS; sum<=temp_sum; end behav; 流水线加法器的仿真结果如图5.14所示。 图5.14流水线加法器的仿真结果 5.3.28位乘法器的设计 两个N位二进制数的乘积用X和A=∑N-1k=0ak2k表示,按“手工计算”的方法给出就是: P=A ×X=∑N-1k=0ak2kX,从中可以看出,乘法的完成实际上是一个移位相加的过程, 例5.14就采用该方法来实现两个8位整数相乘。乘法分为三个步骤: 首先下载8位操作数并且重置移位寄存器; 其次进行串并乘法运算; 最后乘积结果输出到寄存器。 【例5.14】8位乘法器的VHDL设计。 PACKAGE def_data_type IS --自定义程序包 SUBTYPE byte IS INTEGER RANGE 0 TO 127; SUBTYPE word IS INTEGER RANGE 0 TO 32767; TYPE state_type IS (s0,s1,s2); END def_data_type; LIBRARY WORK; --声明工作库 USE WORK.def_data_type.all; --声明自定义程序包 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_ARITH.ALL; ENTITY mul_8 IS PORT(clk:std_logic; x:in byte; y:in std_logic_vector(7 downto 0); z:out word); END mul_8; ARCHITECTURE behav OF mul_8 IS SIGNAL state:state_type; BEGIN PROCESS(clk) VARIABLE p,t:word:=0; VARIABLE c:INTEGER RANGE 0 TO 7; BEGIN IF clk'EVENT AND clk='1' THEN CASE state IS WHEN s0=>state<=s1;c:=0;p:=0;t:=x; --初始化 WHEN s1=>IF c=7 THEN state<=s2;--移位相加 ELSE IF y(c)='1' THEN p:=p+t; END IF; t:=t*2; c:=c+1; state<=s1; END IF; WHEN s2=>z<=p;state<=s0;--结果寄存 END CASE; END IF; END PROCESS; END behav; 8位乘法器的仿真结果如图5.15所示。 图5.158位乘法器的仿真结果 5.3.34抽头直接FIR滤波器的设计 FIR滤波器的结构主要是非递归结构,没有输出到输入的反馈,并且FIR滤波器很容易获得严格的线性相位特性,避免被处理信号产生相位失真。而线性相位体现在时域中仅仅是h(n)在时间上的延迟,这个特点在图像信号处理、数据传输等波形传递系统中是非常重要的。相位响应可以使线性系统绝对稳定,而且设计相对容易、高效。由于FIR滤波器没有反馈回路,因此它是无条件稳定系统,其单位冲激响应h(n)是一个有限长序列。由图5.16可见,FIR滤波器实际上是一种乘法累加运算,它不断 地输入样本x(n),经延时(Z-1),做乘法累加,再输出滤波结果y(n)。其差分表达式为: y(n)=∑N-1i=0aix(n-i)。图5.16给出N阶LTI型FIR滤波器的图解。 图5.16FIR滤波器的结构图 例5.15是对图5.16中直接FIR滤波器结构的文字解释,这种设计对对称和非对称滤波器都是适用的。抽头延迟线每个抽头的输出分别乘以相应的加权二进制值,再将结果相加。以系数为{-1,3.75,3.75,-1}为例,设计相应滤波器如下。 【例5.15】4抽头直接FIR滤波器的VHDL描述。 PACKAGE def_data_type IS SUBTYPE byte IS INTEGER RANGE -128 TO 127; TYPE array_byte IS ARRAY(0 TO 3) OF byte; END def_data_type; LIBRARY WORK; USE WORK.def_data_type.all; LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_ARITH.ALL; ENTITY FIR_4 IS PORT(clk:std_logic; x:in byte; y:out byte); END FIR_4; ARCHITECTURE behav OF FIR_4 IS SIGNAL tap:array_byte:=(0,0,0,0); BEGIN PROCESS(clk) BEGIN IF clk'EVENT AND clk='1' THEN y<=2*tap(1)+tap(1)+tap(1)/2+tap(1)/4 +2*tap(2)+tap(2)+tap(2)/2+tap(2)/4 -tap(3)-tap(0); FOR i IN 3 DOWNTO 1 LOOP tap(i)<=tap(i-1); END LOOP; tap(0)<=x; END IF; END PROCESS; END behav; FIR滤波器的仿真结果如图5.17所示。 图5.17FIR滤波器的仿真结果 5.3.4IIR数字滤波器的设计 IIR滤波器比FIR滤波器获得更高的性能,它具有工作速度快、耗用存储空间少的特点。IIR数字滤波器需要执行无限数量卷积,能得到较好的幅度特性,其相位特性是非线性,具有无限持续时间冲激响应。但是由于相位非线形的缺点,使其无法在图像处理以及数据传输中得到应用。 1. 有耗积分器Ⅰ 滤波器的一个基本功能就是使有干扰的信号平滑。假定信号x[n]是以含有宽频带零平均值随机噪声的形式接收到的。从数学的角度讲,可以采用积分器来消除噪声的影响。如果输入信号的平均值能够保持的时间间隔是有限长,就可以采用有耗积分器处理含有额外噪声的信号。图5.18显示了一个简单的一阶有耗积分器,它满足离散时间差分方程: y[n+1]=3/4y[n]+x[n] 图5.18有耗积分器的一阶IIR滤波器 【例5.16】IIR滤波器Ⅰ的VHDL设计。 package n_bit_int is subtype bits15 is integer range -2**14 to 2**14-1; end n_bit_int; library work; use work.n_bit_int.all; library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; entity iir_i is port(x_in:in bits15; clk:in std_logic; y_out:out bits15 ); end iir_i; architecture a of iir_i is signal x,y:bits15:=0; begin process(clk) begin if clk'event and clk='1' then x<=x_in; y<=x+y/4+y/2; end if; end process; y_out<=y; end a; 仿真结果如图5.19所示。仿真为滤波器对幅值为1000 “1000”为通过仿真软件数字化后的结果,无单位。全书同。的脉冲响应仿真结果。 图5.19有耗积分器Ⅰ脉冲的响应仿真结果 2. 有耗积分器Ⅱ 一阶IIR系统的差分方程为y[n+1]=ay[n]+bx[n],一阶系统的输出就是y[n+1]。可以采用预先考虑的方法计算,将y[n+1]代入y[n+2]的差分方程就是: y[n+2]=a y[n+1]+bx[n+1]=a2y[n]+abx[n]+bx[n+1] 其等价系统如图5.20所示。 图5.20采用预先算法的有耗积分器 通过采用预先考虑(S-1)步骤的转换,就可以生成这一概念,结果如下: y[n+S]=aSy[n]+∑S-1k=0akbx[n+S-1-k](η) 从中可以看出: (η)项定义了FIR滤波器的系数{b,ab,a2b,…,aS-1b},后者可以采用流水线技术。而递归部分也可以利用系数为a2的S阶流水线乘法器来实现。分析图5.20给出的有耗积分器,它由一个非递归部分(如FIR滤波器)和一个具有延迟为2s和系数为9/16的递归部分构成的。满足如下方程。 y[n+2]=3/4y[n+1]+x[n+1]=3/4(3/4y[n]+x[n])+x[n+1] =9/16y[n]+3/4x[n]+x[n+1] 实现这种IIR滤波器的VHDL代码如例5.17所示。 【例5.17】有耗积分器Ⅱ的VHDL设计。 package n_bit_int is subtype bits15 is integer range -2**14 to 2**14-1; end n_bit_int; library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use work.n_bit_int.all; entity iir2 is port(x_in:in bits15; y_out:out bits15; clk:in std_logic); end iir2; architecture a of iir2 is signal x,x3,sx,y,y9:bits15:=0; begin process begin wait until clk='1'; x<=x_in; x3<=x/2+x/4;--计算x*3/4 sx<=x+x3;--x部分求和 y9<=y/2+y/16;--计算y*9/16 y<=sx+y9;--计算输出 end process; y_out<=y; end a; 该例中先计算了9/16*y[n]与3/4*x[n]; 然后计算3/4*x[n]+x[n+1]+9/16*y[n]。仿真结果如图5.21所示。仿真为滤波器对幅值为1000的脉冲响应仿真结果。 图5.21有耗积分器Ⅱ脉冲的响应仿真结果 3. 有耗积分器Ⅲ 并行处理滤波器的实现是由P个并行IIR通路构成的,每个信道都以1/P个输入采样速率运行,它们在输出位置靠多路复用器合成在一起,一般情况下,由于多路复用器要比乘法器和/或加法器速度快,所以并行方法速度也就更快。进一步讲,每个信道P都有一个P因子来计算其指定的输出。以P=2的一阶系统为例,预先考虑与有耗积分器Ⅱ相同。现将其分成偶数n=2k和奇数n=2k-1输出序列,得到: y[n+2]= y[2k+2]=a2y[2k]+ax[2k]+x[2k+1] y[2k+1]=a2y[2k-1]+ax[2k-1]+x[2k] 其中n,k∈Z。这两个方程是IIR滤波器FPGA实现的基础。 例5.18给出了a=3/4的并行有耗积分器的VHDL实现。双信道并行有耗积分器是两个非递归部分(x的FIR滤波器)和两个延迟为2s、系数为9/16的递归部分的组合。其框图如图5.22所示。 图5.22双通路并行IIR原理框图 【例5.18】有耗积分器Ⅲ的VHDL设计。 package n_bit_int is subtype bits15 is integer range -2**14 to 2**14-1; end n_bit_int; library work; use work.n_bit_int.all; library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; entity iir3 is port(x_in:in bits15; y_out:out bits15; clk:in std_logic; clk2:out std_logic); end iir3; architecture a of iir3 is type state_type is (even,odd); signal state:state_type:=even; signal x_even,x_odd,xd_odd,x_wait:bits15:=0; signal y_even,y_odd,y_wait,y:bits15:=0; signal x_e,x_o,y_e,y_o:bits15:=0; signal sum_x_even,sum_x_odd:bits15:=0; signal clk_div2:std_logic; begin process --将输入x分为奇偶部分 begin--在时钟驱动下重新组合y wait until clk='1'; case state is when even=> x_even<=x_in; x_odd<=x_wait; clk_div2<='1'; y<=y_wait; state<=odd; when odd=> x_wait<=x_in; y<=y_odd; y_wait<=y_even; clk_div2<='0'; state<=even; end case; end process; y_out<=y; clk2<=clk_div2; process--滤波器算法的实现 begin wait until clk_div2='0'; --sum_x_even<=(x_even*2+x_even)/4+x_odd; --y_even<=(y_even*8+y_even)/16+sum_x_even; sum_x_even<=x_even/2+x_even/4+x_odd; y_even<=y_even/2+y_even/16+sum_x_even; xd_odd<=x_odd; sum_x_odd<=xd_odd/2+xd_odd/4+x_even; y_odd<=y_odd/2+y_odd/16+sum_x_odd; end process; end a; 并行IIR仿真结果如图5.23所示。 图5.23并行IIR滤波器对脉冲的响应仿真结果 5.4TestBench中随机数的设计 在VHDL写TestBench时有时会根据仿真需要产生一个随机数,如Verilog中的random函数。这个随机数的产生可以使用math_real函数包中的uniform函数得到一个real类型的归一随机数,然后可以对这个数进行其他处理来满足具体要求,例如扩大倍数、截掉小数等,此类操作需要利用一个类型转换函数实现。例5.19实现一个4位二进制数的平方功能,为了验证该功能,在例5.20中的TestBench中利用随机数函数uniform和转换函数实现一个4位二进制随机数提供给乘法器输入端口。仿真结果如图5.24所示。 图5.244位乘法器的仿真结果 【例5.19】4位二进制数平方。 library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; entity vhdl_random_test is port ( idata:in std_logic_vector(3 downto 0); odata :outstd_logic_vector(7 downto 0) ); end entity vhdl_random_test; architecture one of vhdl_random_test is begin odata <=idata * idata; end architecture one; 【例5.20】4位二进制数平方的TestBench设计。 library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; use ieee.math_real.all; use ieee.numeric_std.all; entity vhdl_random_test_tb is end entity vhdl_random_test_tb; architecture one of vhdl_random_test_tb is componentvhdl_random_test is port ( idata:in std_logic_vector(3 downto 0); odata :outstd_logic_vector(7 downto 0) ); end component; signal idata : std_logic_vector (3 downto 0); signal odata : std_logic_vector (7 downto 0); signal i: integer :=0; begin vhdl_random_test_inst : vhdl_random_test port map ( idata=> idata, odata=> odata ); process VARIABLE seed1, seed2: positive; VARIABLE rand: real; VARIABLE int_rand: integer; begin UNIFORM(seed1, seed2, rand); int_rand :=INTEGER(TRUNC(rand*16.0)); idata <=std_logic_vector(to_unigned(int_rand, 4)); i <=i+1; if i=30 then wait; else wait for 20 ns; end if; end process; end architecture one; 例子中uniform函数需要声明ieee.math_real程序包。uniform在第一次调用之前需要统一种子值(SEED1,SEED2),该种子值必须初始化为范围在12147483562~12147483398的值,每次调用后统一修改种子值。产生的随机数在0.0~1.0之间。 TRUNC函数主要防止数据溢出,当转换数据超出最大值后将保持最大值输出。本例中由于输入数据为4bit,所以扩大16倍。示例中用了两个转换函数,一个是将随机数扩大16倍后转换为整数。后续将整数转换为4bit的std_logic_vector数据类型并赋值给乘法器输入。该转换函数为std_logic_vector(to_unsigned(int_rand,4)),该函数使用前需声明ieee.numeric_std.all;程序包。 5.5二进制频移键控调制与解调的VHDL实现 频移键控(FSK)使用不同频率的载波来传送数字信号,并用数字基带信号控制载波信号的频率。二进制频移键控使用两个不同频率的载波来代表数字信号的两种电平。接收端接收到不同的载波信号再进行逆变换成数字信号,完成信息传输过程。 5.5.1FSK调制的VHDL实现 FSK调制的建模方框图如图5.25所示。FSK调制的核心部分包括分频器、二选一选通开关等。图5.25中的两个分频器分别产生两路数字载波信号。二选一选通开关的作用是: 以基带信号作为控制信号,当基带信号为“0”,选通载波f1; 当基带信号为“1”,选通载波f2。从选通开关输出的信号就是数字FSK信号。图中调制信号为数字信号。 图5.25FSK调制的建模方框图 【例5.21】FSK调制的VHDL描述。 library ieee; use ieee.std_logic_arith.all; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity fsk is port(clk:in std_logic;--系统时钟 start:in std_logic; --允许调制信号 x:in std_logic;--基带信号 y:out std_logic); --调制信号 end fsk; architecture a of fsk is signal q1:integer range 0 to 11;--载波信号f1的分频计数器 signal q2:integer range 0 to 3; --载波信号f2的分频计数器 signal f1,f2:std_logic;--载波信号f1、f2 begin process(clk)--此进程完成时钟分频得到载波 begin if clk'event and clk='1' then if start='0' then q1<=0; elsif q1=11 then q1<=0; elsif q1<=5 then f1<='1';q1<=q1+1; else f1<='0';q1<=q1+1; end if; end if; end process; process(clk) begin if clk'event and clk='1' then if start='0' then q2<=0; elsif q2=3 then f2<='0';q2<=0; elsif q2<=1 then f2<='1';q2<=q2+1; else f2<='0';q2<=q2+1; end if; end if; end process; process(clk,x)----此进程完成对基带信号的FSK调制 begin if clk'event and clk='1' then if x='0' then y<=f1; else y<=f2; end if; end if; end process; end a; FSK调制的仿真结果如图5.26所示。 图5.26FSK调制的仿真结果 5.5.2FSK信号解调的VHDL实现 FSK解调方框图如图5.27所示。其核心部分由分频器、寄存器、计数器和判决器组成。其中分频器的分频系数取值对应图5.25 中的分频器1和分频器2中较小的分频系数值,即FSK解调器的分频器输出为较高的那个载波信号。由于f1和f2的周期不同,若设f1=2f2,且基带信号电平“1”对应f1; 基带信号电平“0”对应载波f2,则图5.27中计数器以f1为时钟信号,上升沿计数,基带信号“1”码元对应的计数器个数为1/f1,基带信号“0”码元对应的计数器个数为1/f2。计数器根据两种不同的计数情况,对应输出“0”和“1”两种电平。判决器以f1为时钟信号,对计数器输出信号进行抽样判决,并输出基带信号。图中没有包含模拟电路部分,调制信号为数字信号形式。 图5.27FSK解调方框图 【例5.22】FSK信号解调的VHDL描述。 library ieee; use ieee.std_logic_arith.all; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity fskm is port(clk:in std_logic; --系统时钟 start:in std_logic;--同步信号 x:in std_logic; --调制信号 y:out std_logic);--基带信号 end fskm; architecture a of fskm is signal q:integer range 0 to 11;--分频计数器 signal m:integer range 0 to 5;--计数器 signal xx:std_logic;--寄存器 begin process(clk) --对系统时钟进行分频 begin if clk'event and clk='1' then xx<=x;--时钟上升沿存储调制信号值 if start='0' then q<=0; elsif q=11 then q<=0; else q<=q+1; end if; end if; end process; process(xx,q)--此进程完成FSK解调 begin if q=11 then m<=0; elsif q=10 then if m<=3 then y<='0';--通过m大小判决输出电平 else y<='1'; end if; if xx'event and xx='1' then m<=m+1;--计x信号脉冲个数 end if; end process; end a; 5.6基于DDS信号发生器的设计 本节介绍了一个频率和相位可调的正弦波信号发生器的设计。系统要求输出电压的最大值为5V。①输出频率范围为10~70Hz的三相交流电,要求分别输出电流和电压三相电信号; ②相位在0~360°范围内,步进0.1°可调; ③频率步进0.01Hz可调; ④输出电压波形应尽量接近正弦波,用示波器观察无明显失真。 此设计是以FPGA、单片机为主要控制核心,控制键盘的输入与LED显示,完成单片机与FPGA之间的数据传输,FPGA向存储器输出地址,使存储器将相应单元的数据传输到D/A转换器中,再经可调带通滤波器进行波形整形,最后输出完整的波形,如图5.28所示为设计的总体框图。 图5.28设计的总体框图 此系统利用DDS技术实现波形的发生与控制,单片机的控制电路主要完成将输入的数值转换成二进制以后将相应的频率控制字及相位数值输出到DDS模块。本节主要介绍FPGA部分的作用及实现。 5.6.1DDS设计及原理 直接数字频率合成(DDS)是从相位概念出发直接合成所需波形的一种新的频率合成技术。它在相对带宽、频率转换时间、相位连续性、正交输出、高分辨率以及集成化等一系列性能指标方面已远远超过了传统频率合成技术。 DDS的基本原理框图如图5.29所示,它主要是以数控振荡器的方式,产生频率、相位可控制的正弦波。它主要由标准参考频率源、相位累加器、波形存储器、数/模转换器、低通平滑滤波器等构成。其中,参考频率源一般是一个高稳定度的晶体振荡器,其输出信号用于DDS中各部件同步工作。DDS的实质是对相位进行可控等间隔的采样。 图5.29DDS的基本原理框图 相位累加器的结构如图5.30所示。 图5.30相位累加器的结构示意图 它是实现DDS的核心,由一个N位字长的加法器和一个由固定时钟脉冲取样的N位相位寄存器组成。将相位寄存器的输出和外部输入的频率控制字K作为加法器的输入,在时钟脉冲到达时,相位寄存器对上一个时钟周期内相位加法器的值与频率控制字K之和进行采样,作为相位累加器在此刻时钟的输出。相位累加器输出的高M位作为波形存储器查询表的地址,从波形存储器中读出相应的幅度值送到数/模转换器。 当DDS正常工作时,在标准参考频率源的控制下,相位累加器不断进行相位线性累加(每次累加值为频率控制字K),当相位累加器积满时,就会产生一次溢出,从而完成一个周期性的动作,这个周期就是DDS合成信号的频率周期。输出信号的频率为 fout=w2π=2π2N×K×fc2π=K×fc2N 显而易见,当K=1时输出最小频率,即频率分辨率为fmin=fc2N。式中,fout为输出信号的频率; K为频率控制字; N为相位累加器字长; fc作为标准参考频率源工作频率。 5.6.2FPGA内部的DDS模块的设计与实现 在此设计中,所要求的相位可调精度为0.1°,频率的可调精度要达到0.01Hz。由于一个周期为360°,所以在一个周期内进行3600点的采样。为了便于计算和提高精度,选用的时钟频率为3.6MHz。这样相位累加器的最大计数值为 Pacc=fcfmin=3.6×106Hz0.01Hz=3.6× 108 式中,Pacc为相位累加器的计数值,fc为标准参考频率源工作频率,fmin为输出频率可调精度。 则相位累加器的最少位数N为 N=lg(Pacc)lg2=lg(3.6×108)lg2=29 因为频率为3.6×106Hz,相位累加器一周期的累加数为3.6×108,取样3600次,则取样一次所需的计数值为 n=Pacc3600=3.6×1083600=105 输入的频率精确到0.01Hz,在计算频率控制字时,首先将输入的数值扩大100倍作为频率控制字K,例如输入的频率为10Hz, 图5.31FPGA内部的DDS模块 则频率控制字为1000,当累加器累加到105时,取高13位地址作为外部存储器的读取地址。图5.31为FPGA内部的DDS模块。 说明: (1) cs1=0 时,允许写数据到DDS; cs1=1时,不允许写数据到DDS。 (2) cs2=1时,DDS停止工作; cs2=0时,DDS正常工作。 (3) fqs=1时,由SEL0、SEL1、SEL2三位为低8位数据和高8位数据选择端。当sel2、sel1、sel0为“000”时选择频率的低8位,为“001”时选择频率的高8位; sel2、sel1、sel0为“010”时选择电流A路相位的低8位; 为“011”时选择电流A路相位的高8位; 为“100”时选择电流B路相位的低8位; 为“101”时选择电流B路相位的高8位; 为“110”时选择电流C路相位的低8位; 为“111”时选择电流C路相位的高8位。fqs=0,选择电压的三路相位,其数据的控制命令字与fqs=1相同。 (4) data为数据线(8位)。 (5) 例如选择输出10.00Hz,(分辨率为0.01Hz)需要输入频率字(1000)10(0000001111101000)2。 (6) 选择相位差为10.1°(分辨率为0.1°),需输入相位字(101)10(0000000001100101)2。 (7) 相位和频率可任意时刻设置,频率默认值为00000000001Hz,相位默认值为0。 (8) to da为12位地址线,直接接到外围EPROM的12位地址线。 【例5.23】产生频率、相位正弦信号的VHDL描述。 library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; … entity dds18m1 is port(clk:in std_logic; clkout:out std_logic; lod,fqs,cs1,cs2:in std_logic; sel: in std_logic_vector(2 downto 0); data: in std_logic_vector(7 downto 0); vaddera:out std_logic_vector(12 downto 0); vadderb:out std_logic_vector(12 downto 0); vadderc:out std_logic_vector(12 downto 0); iaddera:out std_logic_vector(11 downto 0); iadderb:out std_logic_vector(11 downto 0); iadderc:out std_logic_vector(11 downto 0) ); --vadder:out std_logic_vector(12 downto 0);--integer range 7200 downto 0; --iadder:out std_logic_vector(12 downto 0));--integer range 3600 downto 0; enddds18m1; -- architecture behave of dds18m1 is constant n:std_logic_vector(16 downto 0):="11000011010100000"; --100000=3.6M/3600clk=3.6Mconstantn std_logic_vector(18 downto 0):=x"7A120";--50000=18M/3600 clk=18M signaladd :std_logic_vector(11 downto 0);--7200个地址单元 signalm:std_logic_vector(32 downto 0); signalfset: std_logic_vector(15 downto 0); --频率分辨率位0.01Hz ,最高频率100Hz,100/0.01=10000,频率字要16位 signal pseta,psetb,psetc,psa,psb,psc: std_logic_vector(11 downto 0); --相位分辨率为0.1度 360*0.1=3600211<3600<212 signalcp,ld,clkk:std_logic; signalacc: std_logic_vector(3 downto 0); begin process(clk) begin if clk'event and clk='1'then if acc=4 then acc<="0000"; clkk<='0'; else acc<=acc+1; if acc<3 then clkk<='1'; else clkk<='0'; end if; end if; end if; end process; process(ld) begin if ld'event and ld='0' then if fqs='1' then--选择频率输入 if sel="000" then fset(7 downto 0)<=data;--SEL=00选择低8位 elsif sel="001"then fset(15 downto 8)<=data; --fset(31 downto 16)<="0000000000000000"; --elsif sel="10"then --fset(31 downto 16)<=data; elsif sel="100" then psb(7 downto 0)<=data; elsif sel="101" then psb(11 downto 8)<=data(3 downto 0); elsif sel="110" then psc(7 downto 0)<=data; elsif sel="111" then psc(11 downto 8)<=data(3 downto 0); elsif sel="010" then psa(7 downto 0)<=data; elsif sel="011" then psa(11 downto 8)<=data(3 downto 0); else NULL; end if; else--选择相位输入 if sel="100" then psetb(7 downto 0)<=data; elsif sel="101" then psetb(11 downto 8)<=data(3 downto 0); elsif sel="110" then psetc(7 downto 0)<=data; elsif sel="111" then psetc(11 downto 8)<=data(3 downto 0); elsif sel="010" then pseta(7 downto 0)<=data; elsif sel="011" then pseta(11 downto 8)<=data(3 downto 0); else NULL; end if; end if; --end if; end if; end process; process(cp) begin if (cp'event and cp='1')then if(add="111000010000")then add<="000000000000"; elsif m<n then m<=m+fset; else m<=m-n+fset; add<=add+1; end if; end if; end process; ld<=(not cs1) and lod; cp<=(not cs2) and clkk; clkout<=not clkk; vaddera<=add+pseta; vadderb<=add+psetb; vadderc<=add+psetc; iaddera(11 downto 0)<=add+psa; iadderb(11 downto 0)<=add+psb; iadderc(11 downto 0)<=add+psc; end behave; 5.6.3仿真结果及说明 这部分的仿真是对核心部分DDS的测试。如图5.32所示是信号频率为10Hz时输入频率控制字及各路相位的时序仿真图。其中电流a路相位为0°,电流b路相位为120°,电流c路相位为240°,电压a路相位为0°,电压b路相位为100°,电压c路相位为200°; 图5.33所示为DDS内部的实现过程,由相位仿真时序图可以看出在DDS内部累加3600个数以后,重新开始循环,且循环一个周期的时间为100ms,正好与设定值相对应,因此,此DDS程序设计是正确的。 图5.32频率控制字及各相位的时序仿真图 图5.33DDS时序仿真图 5.7SD卡驱动器设计 SD卡在现在的日常生活与工作中使用非常广泛,时下已经成为最为通用的数据存储卡。在诸如MP3、数码相机等设备上 大多采用SD卡作为其存储设备。SD卡之所以得到如此广泛的使用,是因为它价格低廉、存储容量大、使用方便、通用性与安全性强。 SD卡按容量(Capacity)分类,可以分为: (1) 标准容量卡: Standard Capacity SD Memory Card(SDSC): 容量小于等于2GB。 (2) 高容量卡: High Capacity SD Memory Card(SDHC): 容量大于2GB,小于等于32GB。 (3) 扩展容量卡: Extended Capacity SD Memory Card(SDXC): 容量大于32GB,小于等于2TB。 5.7.1SD卡电路结构 图5.34SD卡内部结构图 SD卡内部结构如图5.34所示。由图可知,SD卡上所有单元由内部时钟发生器提供时钟。接口驱动单元同步外部时钟的DAT和CMD信号到内部所用时钟。另外SD卡有6个寄存器OCR、CID、CSD、RCA、DSR、SCR。表5.1为各个寄存器功能含义。其中前4个保存卡的特定信息,后两个用来对卡进行配置。在DE2115平台中SD卡与FPGA_N电路图如图5.35所示。 表5.1SD卡寄存器说明 寄存器名称位宽功 能 描 述 OCR32支持的电压 CID128卡信息: 生产商、OEM、产品名称、版本、出产日期、CRC校验 RCA16卡地址: 在初始化时发布,用于与host通信,0x0000表示与所有卡通信 DSR16驱动相关、总线电流大小、上升沿时间、最大开启时间、最小开启时间 CSD128数据传输要求: 包括读写时间、读写电压最大最低值、写保护、块读写错误 SCR64特性支持,如CMD支持、总线数量支持 图5.35SD卡与FPGA连接电路原理图 SD卡支持两种总线方式: SD方式与SPI方式。各个模式下引脚功能如表5.2所示。其中SD方式采用6线制,使用CLK、CMD、DAT0~DAT3进行数据通信。而SPI方式采用4线制,使用CS、CLK、DataIn、DataOut进行数据通信。SD方式时的数据传输速度比SPI方式要快。采用不同的初始化方式可以使SD卡工作于SD方式或SPI方式。SD卡模式允许4线的高速数据传输,SPI模式允许简单通用的SPI通道接口,这种模式相对于SD模式的不足之处是丧失了速度。如果接到复位命令(CMD0)时,CS信号有效(低电平),SPI模式启用。本书只对其SPI方式进行介绍。CLK: 每个时钟周期传输一个命令或数据位。频率可在0~25MHz之间变化。在SPI模式下,CRC校验是被忽略的,但依然要求主从机发送CRC码,只是数值可以是任意值,一般主机的CRC码通常设为0x00或0xFF。 表5.2SD卡引脚功能 引脚 序号 SD模式SPI模式 名称类型描述名称类型描述 1CD/DAT3IO/PP卡检测/ 数据线3#CSI片选 2CMDPP命令/回应DII数据输入 3VSS1S电源地VSSS电源地 4VDDS电源VDDS电源 5CLKI时钟SCLKI时钟 6VSS2S电源地VSS2S电源地 7DAT0IO/PP数据线0DOO/PP数据输出 8DAT1IO/PP数据线1RSV 9DAT2IO/PP数据线2RSV 5.7.2SD卡命令 1. 命令类型 广播指令bc: 不需要响应。 广播指令bcr,每个卡都会独立接收指令并发送响应。 点对点指令ac: 发送完此类命令后,只有指定地址的SD卡会给予反馈(地址通过命令请求SD卡发布,是唯一的)。此时DAT线上无数据传输。 点对点数据传输指令adtc: 发送完此类命令后,只有指定地址的SD卡会给予反馈。此时DAT线上有数据传输。 2. 命令格式 所有命令均按照表5.3所示格式,总共48bit。首先是1bit起始位‘0’,然后是1bit方向位(主机发送‘1’),6bit命令位(0~63),32bit参数(部分命令需要),7bit CRC7校验,1bit停止位‘1’。 表5.3SD卡命令格式 位4746[45:40][39:8][7:1]0 宽度1163271 值‘0’‘1’XXX‘1’ 功能起始位传输位命令命令参数CRC7停止位 3. 命令分类 SD卡操作命令根据不同的类型分成不同的class。其中class0、2、4、5、8是每个卡都必须支持的命令,不同的卡所支持的命令保存在CSD中。 4. 应答格式 所有应答都是通过CMD发送,不同的应答长度可能不同,共有4种类型的应答。 (1) R1: 长度为48bit,格式如表5.4所示。 表5.4R1应答响应格式 位4746[45:40][39:8][7:1]0 宽度1163271 值‘0’‘0’XXX‘1’ 功能起始位传输位命令命令参数CRC7停止位 (2) R2(CID CSD寄存器): 长度为136bit,CID为CMD2和CMD10的应答,CSD为CMD9的应答。应答格式如表5.5所示。 表5.5R2应答响应格式 位135134[133:128][127:1]0 宽度1161271 值‘0’‘0’“111111”X‘1’ 功能起始位传输位保留CID或CSD寄存器包含了CRC7停止位 (3) R3(OCR寄存器): 长度为48bit,作为ACMD41的应答,格式如表5.6所示。 表5.6R3应答响应格式 位4746[45:40][39:8][7:1]0 宽度1163271 值‘0’‘0’“111111”X“111111”‘1’ 功能起始位传输位保留OCR寄存器保留停止位 (4) R6(RCA地址应答): 长度为48bit,格式如表5.7所示。 表5.7R6应答响应格式 位4746[45:40][39:8][7:1]0 宽度116161671 值‘0’‘0’XXXX‘1’ 功能起始位传输位命令序号 (“000011”)新版 RCA[31:16][15:0] 标准卡位CRC7停止位 5.7.3SD卡数据读取流程 本节主要讲解如何读取SD中数据流程。数据为bin格式的图像数据,图像大小为640×480 RGB656格式数据,该数据存储在SD卡起始地址为40092的位置,偏移地址为1200。注意例中为SD HC卡,该卡只能以块进行读取。偏移地址计算办法为图像数据字节数除以512(块字节)=640×480×2/512=1200。 读取SD卡数据步骤如下: (1) 延时至少74clock,等待SD卡内部操作完成,在MMC协议中有明确说明。 (2) CS低电平选中SD卡。 (3) 发送CMD0,需要返回0x01,进入Idle状态。 (4) 为了区别SD卡是2.0、1.0,还是MMC卡,根据协议向上兼容的原理,首先发送只有SD2.0才有的命令CMD8,如果CMD8返回无错误,则初步判断为2.0卡,进一步发送命令循环发送CMD55+ACMD41,直到返回0x00,确定SD2.0卡初始化成功,进入Ready状态,再发送CMD58命令来判断是HCSD还是SCSD,到此SD2.0卡初始化成功。如果CMD8返回错误则进一步判断为1.0卡还是MMC卡,循环发送CMD55+ACMD41,返回无错误,则为SD1.0卡,至此SD1.0卡初始成功,如果在一定的循环次数下,返回为错误,则进一步发送CMD1进行初始化,如果返回无错误,则确定为MMC卡,如果在一定的次数下,返回为错误,则不能识别该卡,初始化结束。 (5) 发送CMD17(单块)或CMD18(多块)读命令,返回0x00。 (6) 接收数据开始令牌0xfe(或0xfc)+正式数据512Byte+CRC校验2Byte,默认正式传输的数据长度是512Byte,可用CMD16设置块长度。 5.7.4SD卡数据读取代码说明 本节给出SD卡数据读取代码如例5.24所示。下面对代码中主要设计思想进行说明。SD卡中 大小为640×480bin格式图像文件由软件image2LCD制作生成。 类属声明中ADDR地址为SD卡中BIN文件所在的初始地址40992,读者可以利用winhex软件查看SD卡文件所在地址,偏移地址OFF_MAX为1200。 CMD0为X“400000000095”,该指令为SD卡复位指令,其中第一个字节X“40”参照表5.3得到,X“95”为CRC7校验+停止位‘1’得出。 CMD8为X“48000001aa87”,该指令为SD卡类型检测(SD V2.0),其中第一个字节X“48”参照表5.3得到,X“01”为支持电压,DE2115支持2.7~3.6V电压故该字节为X“01”,字节X“aa”为校验字节,主机发送后从机会原样返回,此处可任意修改。字节X“87”为CRC7校验+停止位‘1’得出。 SD卡初始化成功后,CMD55与ACMD41命令中CRC校验位可以忽略,以X“ff”填入即可。 SD初始化过程设计采用序列机方式进行设计。序列存储在信号state中,各个序列以十六进制表示。 序列X“00”~X“02”为上电延时等待SD内部初始化; 序列X“03”片选拉低,发送CMD0; 序列X“04”接收响应,如果完成第一个响应字节接收则跳转到X“05”,没有响应字节跳回至X“01”重新初始化; 序列X“05”判断响应字节是否为X“01”,是则跳转到下一序列X“06”,否则跳回至X“01”重新初始化; 序列X“06”~X“07”发送命令CMD8跳转到X“08”; 序列X“08”判断响应是否接到8个响应字节,如果接到跳转到序列X“09”,否则跳回至序列X“06”; 序列X“09”判断接收字节中第[19:16]位是否为X“01”,是则跳转到序列X“0A”,否则跳回至序列X“06”; 序列X“0A”~X“0B”发送命令CMD55跳转至X“0C”; 序列X“0C”接收响应,如果完成第1个响应字节接收则跳转到X“0D”,没有响应字节则跳回至X“0A”重新发送CMD55; 序列X“0D” 判断响应字节是否为X“01”,是则跳转到下一序列X“0E”,否则跳回至X“0A”重新发送CMD55; 序列X“0E”~X“0F”发送命令ACMD41跳转至序列X“10”; 序列X“10”判断响应是否接到第1个响应字节,如果接到则跳转到序列X“11”,否则跳回至序列X“0E”; 序列X“11” 判断响应字节是否为X“00”,是则跳转到下一序列X“12”,否则跳回至X“0A”重新发送CMD55; 序列X“12”~X“14”发送命令CMD17跳转至序列X“15”; 其中CMD17命令由字节X“51”+4字节地址(初始地址+偏移地址)+X“FF”构成,代码中地址按照块读取速度在增加; 序列X“15”接收响应,如果完成第1个响应字节接收则跳转到X“16”,没有响应字节跳回至X“12”重新发送CMD17; 序列X“16” 判断响应字节是否为X“00”,是则跳转到下一序列X“17”,否则跳回至X“12”重新发送CMD17; 序列X“17” 判断回传的512B数据的第一个起始位是否开始到达即响应字节是否为X“FE”,是则跳转到下一序列X“18”并置读使能,否则等待数据回传; 序列X“18”判断是否接收完512B数据,如果完成则跳转序列X“19”; 序列X“19”~X“1A”块偏移地址+1,如果偏移地址小于1200则跳转到序列序列X“12”继续接收下一块数据,否则跳转至序列X“1B”; 序列X“1B”数据接收完毕。 【例5.24】SD卡读取数据VHDL代码。 library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; use ieee.numeric_std.all; entity sd_init_read is generic( T_10ms:integer:=100000; CNT_FREE_CLK:integer:=100; ADDR:integer:=40992;--X"A020" OFF_MAX:integer:=1200; CMD0:std_logic_vector(47 downto 0):=X"400000000095"; CMD8:std_logic_vector(47 downto 0):=X"48000001aa87"; CMD55:std_logic_vector(47 downto 0):=X"7700000000ff"; ACMD41:std_logic_vector(47 downto 0):=X"6940000000ff" ); port ( clk:instd_logic; --25MHz rst_n:instd_logic; read_done:outstd_logic; rdata:out std_logic_vector(7 downto 0); rflag:out std_logic; spi_cs_n:out std_logic; spi_mosi:out std_logic; spi_miso:instd_logic ); end entity sd_init_read; architecture one of sd_init_read is signal CMD17:std_logic_vector(47 downto 0); signal cnt:integer;--接收块计数器、延时计数器 signal rec_en:std_logic; --接收字节使能 signal rec_clr:std_logic; --接收字节复位全1 signal rec_data:std_logic_vector(47 downto 0); --响应接收缓存 signal send_data:std_logic_vector(47 downto 0);--发送命令缓存 signal read_en:std_logic; --读字节使能 signal read_en_r:std_logic; --读字节使能寄存 signal rec_cnt:integer;--接收字节计数器 signal sd_addr:integer; signal offset_addr:integer; signal state:std_logic_vector(7 downto 0); begin process (clk) begin --读使能寄存 if clk'event and clk='1' then read_en_r <=read_en; end if; end process; process (clk) begin --接收缓冲区处理 if clk'event and clk='1' then if rst_n='0' then rec_data <=(others=> '1'); else if rec_clr='1' then rec_data <=(others=> '1'); else if rec_en='1' then rec_data <=rec_data(46 downto 0) & spi_miso; else rec_data <=rec_data; end if; end if; end if; end if; end process; process (clk) begin--接收有效数据满1B则输出至外部端口 if clk'event and clk='1' then if rst_n='0' then rdata <=X"00"; else if rec_cnt=0 and read_en_r='1' then--接收字节计数器为0与使能寄存 rdata <=rec_data(7 downto 0); end if; endif; end if; end process; process(clk) begin if clk'event and clk='1' then if rst_n='0'then rflag <='0'; else if rec_cnt=0 and read_en_r='1' then rflag <='1'; else rflag <='0'; end if; end if; end if; end process; process(clk) begin if clk'event and clk='1' then if rst_n='0'then rec_cnt <=0; else if read_en='1' and rec_cnt < 7 then rec_cnt <=rec_cnt+1; else rec_cnt <=0; end if; end if; end if; end process; process(clk) begin if clk'event and clk='0' then if rst_n='0' then read_done <='0'; spi_cs_n <='1'; spi_mosi <='1'; state <=(others=> '0'); cnt <=0; rec_clr <='1'; rec_en <='0'; send_data <=(others=> '0'); read_en <='0'; offset_addr <=0; else case state is when X"00"=> read_done <='0'; spi_cs_n <='1'; spi_mosi <='1'; state <=X"01"; cnt <=0; rec_clr <='1'; rec_en <='0'; send_data <=(others=> '0'); read_en <='0'; offset_addr <=0; when X"01"=> rec_clr <='0'; if cnt < T_10ms-1 then cnt <=cnt+1; else cnt <=0; state <=X"02"; end if; when X"02"=> spi_cs_n <='0'; if cnt < CNT_FREE_CLK-1 then cnt <=cnt+1; else cnt <=0; state <=X"03"; send_data <=CMD0; end if; when X"03"=> spi_cs_n <='0'; spi_mosi <=send_data(47); send_data <=send_data(46 downto 0) & '0'; if cnt < 47 then cnt <=cnt+1; else cnt <=0; state <=X"04"; end if; when X"04"=> spi_mosi <='1'; rec_en <='1'; if cnt < 100 then if rec_data(7)='0' then rec_en <='0'; state <=X"05"; spi_cs_n <='1'; cnt <=0; else cnt <=cnt+1; end if; else cnt <=0; state <=X"01"; spi_cs_n <='1'; end if; when X"05"=> rec_en <='0'; spi_cs_n <='1'; rec_clr <='1'; if rec_data(7 downto 0)=X"01" then state <=X"06"; else state <=X"01"; end if; when X"06"=> rec_clr <='0'; if cnt < 10 then cnt <=cnt+1; else cnt <=0; state <=X"07"; send_data <=CMD8; end if; when X"07"=> spi_cs_n <='0'; spi_mosi <=send_data(47); send_data <=send_data(46 downto 0) & '0'; if cnt < 47 then cnt <=cnt+1; else cnt <=0; state <=X"08"; end if; when X"08"=> spi_mosi <='1'; if cnt < 100 then if rec_data(47)='0' then rec_en <='0'; state <=X"09"; spi_cs_n <='1'; cnt <=0; else rec_en <='1'; cnt <=cnt+1; end if; else cnt <=0; state <=X"06"; rec_en <='0'; spi_cs_n <='1'; end if; when X"09"=> rec_en <='0'; spi_cs_n <='1'; rec_clr <='1'; if rec_data(19 downto 16)=X"1" then state <=X"0A"; else state <=X"06"; end if; when X"0A"=> rec_clr <='0'; if cnt < 20 then cnt <=cnt+1; else cnt <=0; state <=X"0B"; send_data <=CMD55; end if; when X"0B"=> spi_cs_n <='0'; spi_mosi <=send_data(47); send_data <=send_data(46 downto 0) & '0'; if cnt < 47 then cnt <=cnt+1; else cnt <=0; state <=X"0C"; end if; when X"0C"=> spi_mosi <='1'; if cnt < 100 then if rec_data(7)='0'then rec_en <='0'; state <=X"0D"; spi_cs_n <='1'; cnt <=0; else rec_en <='1'; cnt <=cnt+1; end if; else cnt <=0; state <=X"0A"; rec_en <='0'; spi_cs_n <='1'; end if; when X"0D"=> rec_en <='0'; spi_cs_n <='1'; rec_clr <='1'; if rec_data(7 downto 0)=X"01" then state <=X"0E"; else state <=X"0A"; end if; when X"0E"=> rec_clr <='0'; if cnt < 20 then cnt <=cnt+1; else cnt <=0; state <=X"0F"; send_data <=ACMD41; end if; when X"0F"=> spi_cs_n <='0'; spi_mosi <=send_data(47); send_data <=send_data(46 downto 0) & '0'; if cnt < 47 then cnt <=cnt+1; else cnt <=0; state <=X"10"; end if; when X"10"=> spi_mosi <='1'; if cnt < 100 then if rec_data(7)='0' then rec_en <='0'; state <=X"11"; spi_cs_n <='1'; cnt <=0; else rec_en <='1'; cnt <=cnt+1; end if; else cnt <=0; state <=X"0E"; rec_en <='0'; spi_cs_n <='1'; end if; when X"11"=> rec_en <='0'; spi_cs_n <='1'; rec_clr <='1'; if rec_data(7 downto 0)=X"00" then state <=X"12"; else state <=X"0A"; end if; when X"12"=> rec_clr <='0'; if cnt < 20 then cnt <=cnt+1; else cnt <=0; state <=X"13"; end if; when X"13"=> state <=X"14"; send_data <=CMD17; when X"14"=> spi_cs_n <='0'; spi_mosi <=send_data(47); send_data <=send_data(46 downto 0) & '0'; if cnt < 47 then cnt <=cnt+1; else cnt <=0; state <=X"15"; end if; when X"15"=> spi_mosi <='1'; if cnt < 100 then if rec_data(7)='0' then rec_en <='0'; state <=X"16"; spi_cs_n <='0'; cnt <=0; else rec_en <='1'; cnt <=cnt+1; end if; else cnt <=0; state <=X"12"; rec_en <='0'; spi_cs_n <='1'; end if; when X"16"=> rec_en <='0'; spi_cs_n <='0'; rec_clr <='1'; if rec_data(7 downto 0)=X"00" then state <=X"17"; else state <=X"12"; end if; when X"17"=> rec_clr <='0'; rec_en <='1'; if rec_data(7 downto 0)=X"FE" then state <=X"18"; read_en <='1'; else state <=X"17"; end if; when X"18"=> if cnt < 4095 then cnt <=cnt+1; else cnt <=0; read_en <='0'; rec_en <='0'; state <=X"19"; end if; when X"19"=> rec_clr <='1'; if cnt < 100 then cnt <=cnt+1; else cnt <=0; state <=X"1A"; end if; when X"1A"=> rec_clr <='0'; spi_cs_n <='1'; if offset_addr < OFF_MAX-1 then offset_addr <=offset_addr+1; state <=X"12"; else state <=X"1B"; end if; when X"1B"=> read_done <='1'; state <=X"1B"; when others=> state <=X"00"; end case; end if; end if; end process; process(offset_addr) begin sd_addr <=ADDR+offset_addr; end process; CMD17(47 downto 40) <=X"51"; CMD17(39 downto 8) <=std_logic_vector(to_unsigned(sd_addr, 32)); CMD17(7 downto 0) <=X"FF"; end architecture one; 5.8SDRAM控制器设计 SDRAM(Synchronous Dynamic Random Access Memory),中文名为同步动态随机存储器。同步是指 Memory工作需要同步时钟,内部命令的发送与数据的传输都以它为基准; 动态是指存储阵列需要不断地刷新来保证数据不丢失; 随机是指数据不是线性依次存储,而是自由指定地址进行数据读写。SDRAM在图像处理、大数据处理等领域有着广泛的应用。 DE2115平台所使用的SDRAM芯片型号为IS42S16320B7TL(8M×16bit×4Bank),其内部结构如图5.36所示。由图5.36可以知道该SDRAM有16bit双向数据总线接口,即读写都是通过这16bit的数据总线。还可以看出其中包含4个MBank(Memory Cell Array Bank,存储阵列块),每个MBank包含8M个存储单元,即整块SDRAM的存储大小为4×8M×16bit=512Mbit的容量。SDRAM的内部结构还包括许多其他模块,如自刷新定时器、内部行地址计数、行地址和列地址解码器、列地址累加器、模式寄存器、突发计数器、状态机和地址缓存器。 图5.36SDRAM(8M×16bit×4Bank)功能框图 5.8.1SDRAM引脚、命令和模式寄存器介绍 IS42S16320B引脚功能如表5.8所示。 表5.8SDRAM(IS42S16320B)引脚功能描述 引 脚 名 称功 能 描 述引 脚 名 称功 能 描 述 A0~A12行地址输入WE写使能 A0~A9列地址输入DQML×16 低字节掩码(高阻) BA0、BA1Bank选择地址DQMH×16 高字节掩码(高阻) DQ0~DQ15数据I/OVDD供电 CLK系统时钟输入VSS地 CKE时钟使能VDDQI/O供电 CS片选VSSQI/O供电地 RAS行地址选通命令NC不连接 CAS列地址选通命令 SDRAM的控制通常是使用厂家给定的一系列命令来实现的。因此SDRAM内部有一个命令解码器,SDRAM的初始化、读写等操作实际上就是将命令发送给解码器,通过解码器解码后告知SDRAM,表5.7中给出SDRAM的基本操作命令。 表5.9SDRAM命令集合表 命令缩写CSRASCASWEA10 器件未选DESLHXXXX 空操作NOPLHHHX 激活操作ACTLLHHX 读操作RDLHLHL 读操作带预充电RDPrLHLHH 写操作WRLHLLL 写操作带预充电WRPrLHLLH 突发终止BSTLHHLX 被选Bank预充电PRELLHLL 所有Bank预充电PALLLLHLH CBR自动刷新CKE=HREFLLLHX 自刷新 CKE=LSELFLLLHX 配置模式寄存器LMRLLLLL SDRAM内部有一个非常重要的模式寄存器(Mode Register,MR)。模式寄存器的地址总线定义如表5.10所示。 表5.10模式寄存器的地址总线定义 地址说明描述 BA0/1、A12~A10保留写入0 A9写突发模式(M9)0: 编程突发模式; 1: 单一入口地址 A8、A7操作模式(M8M7)00: 标准模式; 其他: 保留 A6、A5、A4潜伏模式(M6M5M4)010: 潜伏期2; 011: 潜伏期3; 其他: 保留 A3突发模式(M3)0: 顺序; 1: 间隔 A2、A1、A0突发长度BL000: 1; 001: 2; 010: 4; 011: 8; 111: 全页 突发长度(BL)的值,即连续读几列地址。所谓的全页操作是指对同一个Bank中同一个行地址进行操作,对于x16模式来说相当于连续操作512个字。 由于SDRAM的寻址具有独占性,所以在进行完读写操作后,如果要对同一LBank的另一行进行寻址,就要将原来有效(工作)的行关闭,重新发送行/列地址。LBank关闭现有工作行,准备打开新行的操作就是预充电(Precharge)。预充电可以通过命令控制,也可以通过辅助设定让芯片在每次读写操作之后自动进行预充电。实际上,预充电是对工作行中所有存储体进行数据重写,并对行地址进行复位,同时释放SAMP(重新加入比较电压,一般是电容电压的1/2,以帮助判断读取数据的逻辑电平,因为SAMP是通过一个参考电压与存储体的位线电压的比较来判断逻辑值的),以准备新行的工作。具体而言,就是将SAMP中的数据回写,即使是没有工作过的存储体也会因行选通而使存储电容受到干扰,所以也需要SAMP进行读后重写。此时,电容的电量(或者说其产生的电压)将是判断逻辑状态的依据(读取时也需要),为此要设定一个临界值,一般为电容电量的1/2,超过它的为逻辑1,进行重写,否则为逻辑0,不进行重写(等于放电)。为此,现在基本都将电容的另一端接入一个指定的电压(即1/2电容电压),而不是接地,以帮助重写时的比较与判断。从表5.9可以发现地址线A10控制着 读写之后当前Bank是否自动进行预充电。而在单独的预充电命令中,A10则控制着是对指定的Bank还是所有的Bank(当有多个Bank处于有效/活动状态时)进行预充电,前者需要提供Bank的地址,后者只需将A10信号置于高电平。 5.8.2SDRAM初始化 SDRAM要想正确地工作需要正确配置其模式寄存器以使其按照预期的方式进行工作。SDRAM的初始化就是在上电后对模式寄存器进行配置的过程。SDRAM的初始化有着严格的流程规定,如图5.37所示。 图5.37SDRAM初始化时序 从图5.37得出SDRAM上电序列,初始化代码如例5.25所示。代码中命令CS、RAS、CAS、WE组合为4位逻辑位矢量,代码以状态机方式设计,下面对状态加以说明。 (1) 状态X“0”: 命令为禁用(“1111”); (2) 状态X“1”: 根据图5.37上电后需等待至少200μs,此时命令采用空操作命令NOP(“0111”); (3) 状态X“2”: 给出所有Bank预充电命令,此时命令采用PALL命令(“0010”)给所有Bank预充电时要求A10为高电平,代码中采用sdr_addr(10) <='1'语句完成A10赋值; (4) 状态X“3”: 给出两个NOP后设置为CBR自动刷新(CKE端口始终设置为H),REF命令为(“0001”); (5) 状态X“4”: 给出两个NOP后设置为CBR自动刷新(CKE端口始终设置为H),REF命令为(“0001”); (6) 状态X“5”: 给出8个NOP后配置模式寄存器MR,命令LMR为(“0000”); Bank设置为“00”,MR设置为"0000000100111",含义为潜伏期2,全页顺序突发模式。 (7) 状态X“6”: 给出3个NOP后给出初始化完成标志init_done <='1'; 初始化任务完成。 【例5.25】SDRAM初始化。 library ieee; use ieee.std_logic_1164.all; entity sdr_init is port ( clk:in std_logic; rst_n:instd_logic; init_done:outstd_logic; init_bus:outstd_logic_vector(18 downto 0) ); end entity sdr_init; architecture one of sdr_init is signal sdr_cmd :std_logic_vector(3 downto 0); signal sdr_bank:std_logic_vector(1 downto 0); signal sdr_addr:std_logic_vector(12 downto 0); signal state:std_logic_vector(2 downto 0); signal cnt:integer; begin init_bus(18 downto 15) <=sdr_cmd; init_bus(14 downto 13) <=sdr_bank; init_bus(12 downto 0) <=sdr_addr; process(clk) begin if clk'event and clk='1' then if rst_n='0' then sdr_cmd <=X"F"; state <="000"; cnt <=0; sdr_addr <=(others=> '0'); sdr_bank <="00"; init_done <='0'; else case state is when "000"=> sdr_cmd <=X"F";-- inh state <="001"; cnt <=0; sdr_addr <=(others=> '0'); sdr_bank <="00"; init_done <='0'; when "001"=> if cnt < 10000 then cnt <=cnt+1; sdr_cmd <=X"7"; -- nop else cnt <=0; state <="010"; end if; when "010"=> sdr_cmd <=X"2"; -- PREC sdr_addr(10) <='1'; state <="011"; when "011"=> if cnt < 2 then cnt <=cnt+1; sdr_cmd <=X"7"; -- nop else cnt <=0; sdr_cmd <=X"1"; -- ref state <="100"; end if; when "100"=> if cnt < 7 then cnt <=cnt+1; sdr_cmd <=X"7"; -- nop else cnt <=0; sdr_cmd <=X"1"; -- ref state <="101"; end if; when "101"=> if cnt < 7 then cnt <=cnt+1; sdr_cmd <=X"7"; -- nop else cnt <=0; sdr_cmd <=X"0"; -- LMR sdr_addr <="0000000100111"; sdr_bank <="00"; state <="110"; end if; when "110"=> if cnt < 3 then cnt <=cnt+1; sdr_cmd <=X"7"; -- nop else init_done <='1'; end if; when others=> state <="000"; end case; end if; end if; end process; end architecture one; 5.8.3SDRAM读写操作 1. SDRAM写操作 SDRAM可以通过地址的检索实现单个数据的读写,也可以通过突发读写来实现数据的连续操作。为了实现更高的速率,一般采用突发读写的方式来实现海量数据的高速读写。这也是SDRAM控制器的读写实现方式。以突发长度BL=全页,读潜伏期CL=2,响应延时tRCD=2Clock为例的SDRAM的突发写时序图如图5.38所示。突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输的周期数就是突发长度,寻址与数据的读写将自动进行连续操作,由图可知,全页模式可连续操作512个地址(x16模式)。用户只需要控制好两段突发读写命令的间隔周期即可做到连续的突发传输。 图5.38SDRAM突发写时序图 在高速的图像数据缓存过程中,设置为顺序突发读写方式,同时以全页读写(数据长度最长为512×16bit,本例为320×16bit)的方式进行突发读写将能够得到更大的带宽,更高的效率。由于SDRAM的时钟为100MHz,进行突发读写的前提是每次进行读写时都必须要准备好320个数据的缓存器,以保证这些数据的读写能够快速而准确地连续进行,这样就需要为SDRAM读写配备异步FIFO。 从图5.38得出SDRAM写操作序列,写操作代码如例5.26所示。此例数据来源于存储在SD卡中640×480×16bit图像数据。每次从SD卡中读取320个字(半行图像数据)存储在SDRAM中,共读取960次完成全部图像数据的读取(320×2×480×16bit)。该模块主要结构也采用状态机结构完成写操作,状态“000”~“100”与图5.39序列一致,状态“101”~“111”为下一次突发写操作做准备,下面对各个操作状态加以详细说明。 (1) 状态X“0”: 命令为激活操作ACT(“0011”); 给出Bank为“00”,行地址为外部端口wr_addr赋值。 (2) 状态X“1”: 根据图5.38此时先给出空操作命令NOP(“0111”)后,给预充电写操作WRPr(“0100”),此时A10 由sdr_addr(10) <='1'赋值,列地址从0开始。 (3) 状态X“2”: 突发模式连续读取319个数据。 (4) 状态X“3”: 存储第320个数据。 (5) 状态X“4”: 给出突发终止命令BST(“0110”)。 (6) 状态X“5”: 给出空操作命令NOP(“0111”)。 (7) 状态X“6”: 给出所有Bank预充电命令,此时命令采用PALL命令(“0010”),给所有Bank预充电时要求A10为高电平,代码中采用sdr_addr(10) <='1'语句完成A10赋值。 (8) 状态X“7”: 给出两个NOP后给出完成写标志wr_done <='1'; 第一块写任务完成。 【例5.26】SDRAM突发写操作。 library ieee; use ieee.std_logic_1164.all; entity sdr_write is port( clk:in std_logic; rst_n:instd_logic; write_bus:outstd_logic_vector(18 downto 0); sdr_dq:inoutstd_logic_vector(15 downto 0); wr_fifo_rdreq:outstd_logic; wr_fifo_rdata:instd_logic_vector(15 downto 0); wr_addr:instd_logic_vector(12 downto 0); wr_done:outstd_logic ); end entity sdr_write; architecture one of sdr_write is signal flag:std_logic; signaldq_buf:std_logic_vector(15 downto 0); signalcmd:std_logic_vector(3 downto 0); signal sdr_bank:std_logic_vector(1 downto 0); signalsdr_addr:std_logic_vector(12 downto 0); signalstate:std_logic_vector(3 downto 0) :=X"0"; signal cnt:integer :=0; begin process(flag, dq_buf) begin if flag='1' then sdr_dq <=dq_buf; else sdr_dq <=(others=> 'Z'); end if; end process; write_bus(18 downto 15) <=cmd; write_bus(14 downto 13) <=sdr_bank; write_bus(12 downto 0) <=sdr_addr; process(clk) begin if clk'event and clk='1' then if rst_n='0' then state <=X"0"; cnt <=0; wr_done <='0'; wr_fifo_rdreq <='0'; cmd <=x"7"; -- nop sdr_bank <="00"; sdr_addr <=(others=> '0'); flag <='0'; dq_buf <=X"0000"; else case state is when X"0"=> cmd <=X"3";-- act sdr_addr <=wr_addr; sdr_bank <="00"; wr_fifo_rdreq <='1'; state <=X"1"; when X"1"=> if cnt < 1 then cmd <=X"7"; -- NOP cnt <=cnt+1; else cnt <=0; cmd <=X"4"; -- wr sdr_addr(10) <='1'; sdr_addr(12 downto 11) <="00"; sdr_addr(9 downto 0) <=(others=> '0'); flag <='1'; dq_buf <=wr_fifo_rdata; state <=X"2"; end if; when X"2"=> cmd <=X"7"; -- nop dq_buf <=wr_fifo_rdata; if cnt < 317 then cnt <=cnt+1; else cnt <=0; wr_fifo_rdreq <='0'; state <=X"3"; end if; when X"3"=> dq_buf <=wr_fifo_rdata; state <=X"4"; when X"4"=> cmd <=X"6"; -- bt state <=X"5"; when X"5"=> cmd <=X"7"; -- nop flag <='0'; state <=X"6"; when X"6"=> cmd <=X"2"; -- prec sdr_addr(10) <='1'; state <=X"7"; when X"7"=> if cnt < 2 then cmd <=X"7"; -- nop cnt <=cnt+1; else cmd <=X"7"; -- nop wr_done <='1'; state <=X"7"; end if; when others=> state <=X"0"; end case; end if; end if; end process; end architecture one; 2. SDRAM读操作 SDRAM突发读时序图如图5.39所示。从图5.39得出SDRAM读操作序列,读操作代码如例5.27所示。读操作与写操作代码结构相似。该模块也采用状态机结构完成写操作,状态“000”~“100”与图5.39序列一致,状态“101”~“111”为下一次突发写操作做准备。下面对各个操作状态加以详细说明。 (1) 状态X“0”: 命令为激活操作ACT(“0011”); 给出Bank为“00”,行地址为外部端口rd_addr赋值。 图5.39SDRAM突发读时序图 (2) 状态X“1”: 根据图5.39此时先给出一个空操作命令NOP(“0111”)后,给带预充电写操作RDPr(“0101”),此时A10 由sdr_addr(10) <='1'赋值,列地址从0开始。 (3) 状态X“2”: 给出两个空操作命令NOP(“0111”)后,置读FIFO写请求标志。 (4) 状态X“3”: 突发模式连续读取319个数据后给出突发终止命令BST(“0110”)。 (5) 状态X“4”: 存储第320个数据后给出空操作命令NOP(“0111”)。 (6) 状态X“5”: 清读FIFO写请求标志。 (7) 状态X“6”: 给出所有Bank预充电命令,此时命令采用PALL命令( “0010”),给所有Bank预充电时要求A10为高电平,代码中采用sdr_addr(10) <='1'语句完成A10赋值。 (8) 状态“111”给出两个空操作命令NOP(“0111”)后,给出完成读标志rd_done <='1'; 第一行读任务完成。 【例5.27】SDRAM突发读操作。 library ieee; use ieee.std_logic_1164.all; entity sdr_read is port ( clk:instd_logic; rst_n:instd_logic; rd_bus:outstd_logic_vector(18 downto 0); sdr_dq:inoutstd_logic_vector(15 downto 0); rd_addr:instd_logic_vector(12 downto 0); rd_fifo_wdata:outstd_logic_vector(15 downto 0); rd_fifo_wrreq:outstd_logic; rd_done:outstd_logic ); end entity sdr_read; architecture one of sdr_read is signal flag:std_logic; signaldq_buf:std_logic_vector(15 downto 0); signalcmd:std_logic_vector(3 downto 0); signal sdr_bank:std_logic_vector(1 downto 0); signalsdr_addr:std_logic_vector(12 downto 0); signalstate:std_logic_vector(3 downto 0) :=X"0"; signal cnt:integer :=0; begin process(flag, dq_buf) begin if flag='1' then sdr_dq <=dq_buf; else sdr_dq <=(others=> 'Z'); end if; end process; rd_bus(18 downto 15) <=cmd; rd_bus(14 downto 13) <=sdr_bank; rd_bus(12 downto 0) <=sdr_addr; process(clk) begin if clk'event and clk='1' then if rst_n='0' then cmd <=X"7"; -- nop sdr_bank <="00"; sdr_addr <=(others=> '0'); flag <='0'; dq_buf <=(others=> '0'); rd_fifo_wdata <=X"0000"; rd_fifo_wrreq <='0'; rd_done <='0'; state <=X"0"; cnt <=0; else case state is when X"0"=> cmd <=X"3"; --act sdr_bank <="00"; sdr_addr <=rd_addr; state <=X"1"; flag <='0'; when X"1"=> if cnt < 1 then cmd <=X"7"; -- nop cnt <=cnt+1; else cmd <=X"5"; --rd sdr_addr(10) <='1'; sdr_addr(12 downto 11) <="00"; sdr_addr(9 downto 0) <=(others=> '0'); cnt <=0; state <=X"2"; end if; when X"2"=> if cnt < 2 then cmd <=X"7"; -- nop cnt <=cnt+1; else rd_fifo_wdata <=sdr_dq; rd_fifo_wrreq <='1'; cnt <=0; state <=X"3"; end if; when X"3"=> rd_fifo_wdata <=sdr_dq; rd_fifo_wrreq <='1'; if cnt < 317 then cnt <=cnt+1; else cnt <=0; cmd <=X"6"; state <=X"4"; end if; when X"4"=> rd_fifo_wdata <=sdr_dq; rd_fifo_wrreq <='1'; cmd <=X"7"; -- nop state <=X"5"; when X"5"=> rd_fifo_wrreq <='0'; state <=X"6"; when X"6"=> cmd <=X"2"; -- prec sdr_addr(10) <='1'; state <=X"7"; when X"7"=> if cnt < 2 then cnt <=cnt+1; cmd <=X"7"; -- nop else cmd <=X"7"; rd_done <='1'; state <=X"7"; end if; when others=> state <=X"0"; end case; end if; end if; end process; end architecture one; 5.8.4SDRAM自动刷新时序 SDRAM的Bank逻辑单元是电容型结构,电容易掉电,因此要及时充电。若一段时间不充电,电会放完,数据也就丢失了。因此SDRAM的这一物理特性决定了必须不停地刷新或者预刷新,手册要求必须每64ms对所有的行、列进行一次刷新以确保数据的完整。也就是说每行刷新的循环周期是64ms,这样刷新速度就是: 行数量/64ms。手册中4096 Refresh Cycles/64ms或8192RefreshCycles/64ms的标识,4096行刷新间隔为15.625μs,8192行时就为7.8125μs。 刷新操作分为两种: 自动刷新(Auto Refresh,REF)和自刷新(self Refresh,SELF R)。不论是何种刷新方式,都不需要外部提供行地址信息, 对于REF,SDRAM内部有一个行地址生成器(也称刷新计数器),用来自动地依次生成行地址,由于刷新是针对一行中的所有存储体进行,所以无须列寻址,或者说CAS在RAS之前有效。所以REF又称CBR(CAS Before RAS,列提前于行定位)式刷新。由于刷新涉及所有Bank,因此在刷新过程中,所有Bank都停止工作,所有工作指令只能等待而无法执行,刷新之后就可进入正常的工作状态。64ms之后需再次对同一行进行刷新,因此循环刷新操作会对SDRAM的性能造成影响,这也是DRAM相对于SRAM牺牲性能获取了成本优势。刷新时序如图5.40所示。刷新范例如例5.28所示,例中采用自动刷新模式设计。 图5.40SDRAM自动刷新时序图 例5.28 SDRAM自动刷新模式也是根据图5.40中各个命令顺序关系利用状态机完成设计。下面对各个操作状态加以详细说明。 (1) 状态X“0”: 给出所有Bank预充电命令,此时命令采用PALL命令(“0010”),给所有Bank预充电时要求A10为高电平,代码中采用sdr_addr(10) <='1'语句完成A10赋值; (2) 状态X“1”: 根据图5.40,此时先给出两个空操作命令NOP(“0111”)后,设置为CBR自动刷新(CKE端口始终设置为H),REF命令为(“0001”); (3) 状态X“2”: 先给出8个空操作命令NOP(“0111”)后,设置为CBR自动刷新(CKE端口始终设置为H),REF命令为(“0001”); 此处设置8个空操作命令是根据初始化时序要求设置tRC; (4) 状态X“3”: 先给出8个空操作命令NOP(“0111”)后,设置刷新结束标志位refresh_done <='1'。 【例5.28】SDRAM自动刷新。 library ieee; use ieee.std_logic_1164.all; entity sdr_refresh is port( clk:instd_logic; rst_n:instd_logic; refresh_bus:outstd_logic_vector(18 downto 0); refresh_done:outstd_logic ); end entity sdr_refresh; architecture one of sdr_refresh is signal sdr_cmd:std_logic_vector(3 downto 0); signal sdr_bank:std_logic_vector(1 downto 0); signal sdr_addr:std_logic_vector(12 downto 0); signal state:std_logic_vector(3 downto 0); signal cnt: integer; begin refresh_bus(18 downto 15) <=sdr_cmd; refresh_bus(14 downto 13) <=sdr_bank; refresh_bus(12 downto 0) <=sdr_addr; process(clk) begin if clk'event and clk='1' then if rst_n='0'then state <=X"0"; sdr_cmd <=X"7"; -- nop cnt <=0; sdr_addr <=(others=> '0'); sdr_bank <="00"; refresh_done <='0'; else case state is when X"0"=> sdr_cmd <=X"2"; -- prec sdr_addr(10) <='1'; state <=X"1"; when X"1"=> if cnt < 2 then sdr_cmd <=X"7"; -- nop cnt <=cnt+1; else sdr_cmd <=X"1"; -- ref cnt <=0; state <=X"2"; end if; when X"2"=> if cnt < 7 then cnt <=cnt+1; sdr_cmd <=X"7"; -- nop else cnt <=0; sdr_cmd <=X"1"; --ref state <=X"3"; end if; when X"3"=> if cnt < 7 then cnt <=cnt+1; sdr_cmd <=X"7";-- nop else state <=X"3"; refresh_done <='1'; end if; when others=> state <=X"0"; end case; end if; end if; end process; end architecture one; 5.8.5SDRAM控制器 前面章节介绍了SDRAM主要的操作设计方法和代码案例,本节将结合前面章节内容继续利用状态机完成SDRAM控制器的整体设计。控制器RTL视图如图5.41所示。图中timer模块主要是产生自动刷新触发信号,每4μs产生一次刷新触发信号。实际上就是 图5.41SDRAM自动刷新时序图 一个计数器计数时钟由锁相环产生的100MHz作为计数时钟源。Mux_bus为总线选择器,实际上就是一个4选1数据选择器,通过Sdram_fsm控制器状态机产生的总线选择端选择不同的总线命令传输到SDRAM端口。总线命令来自于前面章节介绍的SDRAM初始化操作总线、写操作总线、读操作总线和刷新操作总线。例5.29为总线状态机模块,总线状态机主要用来调度各个模块协同完成SDRAM读写、初始化和刷新操作。下面对例5.29中各个状态设计进行说明。 (1) 状态X“0”: 选择初始化模块总线等待初始化完成标志init_done,初始化完成后复位初始化模块同时跳转到状态X“1”并使能刷新计数器; (2) 状态X“1”: 选择刷新总线等待刷新结束,结束后跳转到状态X“2”; (3) 状态X“2”: 判断是否到刷新时间,刷新时间到跳转到状态X“1”,否则判断写FIFO数据是否少于150个,如果少则跳转到读操作状态X“3”、X“4”进行读取SDRAM到写FIFO中,如果读FIFO中多于340个数据,则跳转到状态X“5”、X“6” 将FIFO模块数据读取到SDRAM中。状态机的状态转移图如图5.42所示。 图5.42SDRAM控制器状态转移图 【例5.29】SDRAM控制器。 library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; use ieee.std_logic_arith.all; entity sdram_fsm is port ( clk:instd_logic; rst_n:instd_logic; init_rst_n:outstd_logic; timer_rst_n:outstd_logic; refresh_rst_n:outstd_logic; wr_rst_n:outstd_logic; rd_rst_n:outstd_logic; bus_sel:outstd_logic_vector(1 downto 0); wrdat_num:instd_logic_vector(10 downto 0); rddat_num:instd_logic_vector(10 downto 0); init_done:instd_logic; refresh_done:instd_logic; wr_done:instd_logic; rd_done:instd_logic; sdr_init_done:outstd_logic; wr_addr:outstd_logic_vector(12 downto 0); rd_addr:outstd_logic_vector(12 downto 0); times:ininteger ); end entity sdram_fsm; architecture one of sdram_fsm is signal state:std_logic_vector(3 downto 0); signalcnt:integer; signal rdaddr:integer :=0; signal wraddr:integer :=0; begin process(clk) begin if clk'event and clk='1' then if rst_n='0' then state <=X"0"; init_rst_n <='0'; timer_rst_n <='0'; refresh_rst_n <='0'; wr_rst_n <='0'; rd_rst_n <='0'; bus_sel <="00"; wraddr <=0; rdaddr <=0; sdr_init_done <='0'; else case state is when X"0"=> if init_done='0' then init_rst_n <='1'; bus_sel <="00";--init_bus else init_rst_n <='0'; bus_sel <="01"; timer_rst_n <='1'; state <=X"1"; end if; when X"1"=> if refresh_done='0' then timer_rst_n <='1'; refresh_rst_n <='1'; else refresh_rst_n <='0'; state <=X"2"; sdr_init_done <='1'; end if; when X"2"=> if times=400 then timer_rst_n <='0'; bus_sel <="01"; --refresh_bus state <=X"1"; else if conv_integer(rddat_num) < 150 then bus_sel <="11";--rd_bus state <=X"3"; else if conv_integer(wrdat_num) > 340 then bus_sel <="10";--wr_bus state <=X"5"; else state <=X"2"; end if; end if; end if; when X"3"=> state <=X"4"; if rdaddr < 960 then rdaddr <=rdaddr+1; else rdaddr <=1; end if; when X"4"=> if rd_done='0' then rd_rst_n <='1'; else rd_rst_n <='0'; state <=X"2"; end if; when X"5"=> state <=X"6"; if wraddr < 960 then wraddr <=wraddr+1; else wraddr <=1; end if; when X"6"=> if wr_done='0' then wr_rst_n <='1'; else wr_rst_n <='0'; state <=X"2"; end if; when others=> state <=X"0"; end case; end if; end if; end process; wr_addr <=conv_std_logic_vector(wraddr, 13); rd_addr <=conv_std_logic_vector(rdaddr, 13); end architecture one; 5.9利用VGA接口显示SD卡图像数据 该工程主要原理框图如图5.43所示。FPGA通过SPI接口读取存储在SD卡内的图像并先存入内部的SDR_WrFIFO里,通过SDRAM控制器将SDR_WrFIFO数据读取写入外部SDRAM中,直到完整图像数据存入SDRAM中。VGA显示是利用SDRAM控制器将SDRAM的图像数据读到内部的SDR_RdFIFO中,再驱动VGA显示器把图像传输给VGA液晶屏上显示。由于 无法将SD卡读取到的内容直接放到VGA驱动器中显示,需要经过SDRAM及FIFO做显示缓存。SD卡中图像数据是特指 bin格式的图像数据,图像格式为640×480,每个像素宽度为16bit,即RGB565。用户将bin格式文件存储在SD卡中时需要利用Winhex软件查看文件所在扇区地址。用户在操作SD卡时需要用到该地址。 图5.43SD卡图像数据VGA显示框图 整体工程RTL视图如图5.44所示,图中包含4个主要功能模块。 图5.44整体工程RTL视图 (1) Clock_reset_processor: 专门用来处理系统时钟和复位信号。该模块利用锁相环IP完成,锁相环产生3个时钟输出。c0输出时钟100MHz,用于sys_clk_100m输出提供给FPGA内部SDRAM控制器时钟使用; c1输出时钟100MHz相移270°,用于片外SDRAM时钟使用; c2输出时钟25MHz,用于SD卡操作、VGA时钟、FIFO读写时钟。另外利用锁相环锁定信号产生了用于100MHz和25MHz工作模块的复位信号。 (2) Vga_ctrl: 是采用640×480视频标准的VGA驱动器。数字信号转换为模拟信号(R、G、B),采用高速视频DAC芯片ADV7123实现。 (3) Sd_init_read: 用来初始化SD卡,并且从SD卡中读取数据。本例程的设计采用SDHC的SD卡(8GB),SD卡存储是按照扇区(512B)存储的。本模块利用SPI协议将数据读出,按照字节的方式进行输出。 (4) Sdram_controller: 用来初始化SDRAM,将SD卡的数据存入SDRAM中,并将SDRAM中的数据输出给VGA。该模块内部集成了两个用于数据缓存的FIFO,内部RTL视图如图5.45所示。由图5.45可知,sdr_wrfifo用于缓存从SD卡读出的数据,通过SDRAM控制器将其数据缓存至SDRAM中,该FIFO配置如图5.46所示。sdr_rdfifo用于缓存从SDRAM读出的数据,用于VGA显示使用,该FIFO配置如图5.47所示。 图5.45Sdram_controller模块RTL视图 图5.46sdr_wrfifo IP配置 图5.47sdr_rdfifo IP配置 代码 完整工程软件代码扫码可见。 5.10本章小结 本章主要介绍了VHDL在各个数字模块或系统中的应用实例。组合逻辑主要介绍了基本门电路的建模,本时序电路建模讲解了从基本触发器到移位寄存器和分频器的设计。介绍了常用算法的VHDL设计,包括流水线加法器、乘法器及数字滤波器的设计。介绍了在通信领域中最常用的FSK调制与解调设计方法。本章最后给出了综合应用实例,包括DDS、SD卡驱动及SDRAM控制器的具体设计思想及设计代码,可以让读者了解复杂设计问题的设计思路及方法。