与组合逻辑(给定输入,输出是确定的,与时间无关)相比较,时序逻辑不仅仅与输入信号相关,还与时钟信号相关。
D触发器:在上升沿时(CLK)才将输出(Q)修改为当前的输入值(D),具有存储的性质。
注意根据时钟频率计算计数量。
时序逻辑使用 posedge 表示时钟信号。
module led_flash (
clk,
reset_n,
led
);
input clk, reset_n;
output reg led;
reg [24:0] counter;
// ÍÆŒö·ÖΪÁœžö always£¬ÓÐÖúÓÚ·ÖÎö×ÛºÏ
always@(posedge clk or negedge reset_n) // œöœöʱÖÓÉÏÉýÑصœÀŽ»òÕßžŽÎ»ÐźŲúÉú²Å»áŒ€»îÏÂÃæµÄŽúÂë¿é
if(!reset_n)
counter <= 0; // ·Ç×èÈûž³Öµ
else if(counter == 25000000) // ×¢ÒâÕâÀïÓŠžÃ 25000000 - 1
counter <= 0; // ºÍÉÏÃæÒ»ÐÐÒ»Æð×ö£¬ÐèҪʹÓÃbegin¿éʵÏÖ
else
counter <= counter + 1'd1;
always@(posedge clk or negedge reset_n) // œöœöʱÖÓÉÏÉýÑصœÀŽ»òÕßžŽÎ»ÐźŲúÉú²Å»áŒ€»îÏÂÃæµÄŽúÂë¿é
if(!reset_n)
led <= 0;
else if(counter == 25000000)
led <= !led;
else
counter <= counter + 1'd1;
endmodule
二者差值并非绝对的 500ms,问题出在计数变量的初始化上。
时序逻辑的 test_bench 怎么写?
`timescale 1ns/1ns
module led_flush_tb();
reg clk;
reg reset_n;
wire led;
led_flash led_flash_example (
.clk(clk),
.reset_n(reset_n),
.led(led)
);
initial clk = 1;
always #10 clk = ~clk; // ÊŒÖÕ10ns·×ªÒ»ŽÎ
initial begin
reset_n = 0;
#201
reset_n = 1;
#2000000000;
$stop;
end
endmodule
LED 灯控制实验,每秒钟控制开发板上的 LED 灯翻转一次。
Zynq 相比较其它 ARM 平台,其可以使用 PL 端资源定制很多 ARM 端的外设。
开发板的 PL 端连接了一个红色的 LED 灯,完全由 PL 端控制。如果 PL_LED1 为高电平,三极管导通,灯会亮,否则熄灭。
输入工程名和工程存放目录,工程路径不能有中文空格。
工程类型中选择 RTL Project。
目标语言选择 Verilog,VHDL 也可以使用,支持多语言混合编程,并可以在这里创建文件。
选择器件。
点击 Finish 完成工程创建。
在弹出的模块定义中可以指定端口,这里暂不指定。
双击 led2.v 文件编写,这里定义了一个 32 位寄存器 timer,用于循环计数 0~199999999(1秒钟),计数到 199999999 的时候,寄存器 timer 变为 0,并翻转四个 LED,原来 LED 是灭的话就会点亮,如果原来 LED 为亮的话,就会熄灭。由于输入时钟为 200MHz 的差分时钟,因此需要添加 IBUFDS 原语连接差分信号。
`timescale 1ns / 1ps
module led(
//Differential system clock
input sys_clk_p,
input sys_clk_n,
input rst_n,
(* MARK_DEBUG="true" *) output reg led
);
(* MARK_DEBUG="true" *)reg[31:0] timer_cnt;
wire sys_clk ;
IBUFDS IBUFDS_inst (
.O(sys_clk), // 1-bit output: Buffer output
.I(sys_clk_p), // 1-bit input: Diff_p buffer input (connect directly to top-level port)
.IB(sys_clk_n) // 1-bit input: Diff_n buffer input (connect directly to top-level port)
);
always@(posedge sys_clk)
begin
if (!rst_n)
begin
led <= 1'b0 ;
timer_cnt <= 32'd0 ;
end
else if(timer_cnt >= 32'd199_999_999) //1 second counter, 200M-1=199999999
begin
led <= ~led;
timer_cnt <= 32'd0;
end
else
begin
led <= led;
timer_cnt <= timer_cnt + 32'd1;
end
end
//Instantiate ila in source file
//ila ila_inst(
// .clk(sys_clk),
// .probe0(timer_cnt),
// .probe1(led)
// );
endmodule
Vivado 使用的约束文件格式为 xdc 文件。xdc 文件里主要是完成管脚的约束、时钟的约束、以及组的约束。这里我们需要对 led.v 程序中的输入输出端口分配到 FPGA 的真实管脚上。
Open Elaborated Design
:将RTL源代码翻译转换成对应的电路。Run Synthesis
:同样提供了显示详细电路的功能,相比较前者,更偏向于显示 Xilinx 中已封装好的库。在 I/O Ports 中可以看到管脚分配情况。
将复位信号 rst_n 绑定到 PL 端的按键,给 LED 和时钟分配管脚、电平标准,完成后 Ctrl + S
,弹出窗口,保存约束文件,文件类型默认为 XDC。
打开生成的 led2.xdc 文件,最基本的 XDC 编写语法:
普通 IO 口只需要约束引脚号和电压,管脚约束:
set_property PACKAGE_PIN "引脚编号" [get_ports "端口名称"]
电平信号约束:
set_property IOSTANDARD "电平标准" [get_ports "端口名称"]
电平标准中 ”LVCMOSS33“ 后面的数字指 FPGA 的 BANK 电压,LED 所在 BANK 电压为 3.3 伏,Vivado 默认要求为所有 IO 分配正确的电平标准和管脚编号。
先分析综合,然后点击 Constraints Wizard:
时序约束向导分析出设计中的时钟,这里把 sys_clk_p 频率设置为 200MHz,然后点击 Skip to Finish,结束时序约束向导。
这个时候 led.xdc 文件已经更新,点击 Reload 重新加载文件,并保存文件。
利用 Vivado 自带的仿真工具输出波形验证流水灯程序设计结果:
2. 在 Simulation Settings 窗口中进行如下配置,这里设置成 50ms,其它默认。
3. 添加激励测试文件,点击 Project Manager 下的 Add Sources 图标:
`timescale 1ns / 1ps
//
// Module Name: vtf_led_test
//
module vtf_led_test;
// Inputs
reg sys_clk_p;
reg rst_n ;
wire sys_clk_n;
// Outputs
wire led;
// Instantiate the Unit Under Test (UUT)
led uut (
.sys_clk_p(sys_clk_p),
.sys_clk_n(sys_clk_n),
.rst_n(rst_n),
.led(led)
);
initial
begin
// Initialize Inputs
sys_clk_p = 0;
rst_n = 0;
// Wait for global reset to finish
#1000;
rst_n = 1;
end
//Create clock
always #2.5 sys_clk_p = ~ sys_clk_p;
assign sys_clk_n = ~sys_clk_p ;
endmodule
连接 JTAG 线缆,调整到 JTAG 模式启动,开发板上电、
在 HARDWARE MANAGER 界面,点击 Auto Connect 自动连接设备。
选择 FPGA 芯片,右键选择 Program Device,点击 Program。
下载完成后,可以看到 PL LED 每秒钟变化一次。
只有 PL 工程不能直接烧写 Flash。