我在学习Verilog HDL的时候并没有系统的去学,只是大致的了解了下,然后就用一些常用的语法去设计简单常见的硬件电路,这样做的好处是节省时间,也不会感觉重新学习一门语言很累,但是也会遇到一些问题,例如,《基于PLL分频计数的LED灯闪烁实例》实验记录这篇博文中的一个小问题。
建议先看下上面提到的那篇博文,如果实在不想看,又想了解Parameter的作用,那就直接跳过下面的案例。
下面是顶层模块中的Verilog HDL设计代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 21:49:05 08/16/2018
// Design Name:
// Module Name: sp6_pll
// Project Name:
// Target Devices:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module sp6_pll(input ext_clk_25m, //外部输入25MHz时钟信号input ext_rst_n, //外部输入复位信号,低电平有效output[7:0] led //8个LED指示灯接口 ); wire clk_12m5; //PLL输出12.5MHz时钟
wire clk_25m; //PLL输出25MHz时钟
wire clk_50m; //PLL输出50MHz时钟
wire clk_100m; //PLL输出100MHz时钟
wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作//-------------------------------------
//PLL例化pll_clk u1(// Clock in ports.CLK_IN1(ext_clk_25m), // IN// Clock out ports.CLK_OUT1(clk_12m5), // OUT.CLK_OUT2(clk_25m), // OUT.CLK_OUT3(clk_50m), // OUT.CLK_OUT4(clk_100m), // OUT// Status and control signals.RESET(!ext_rst_n),// IN.LOCKED(sys_rst_n)); // OUT //-------------------------------------
//12.5MHz时钟进行分频闪烁,计数器为23位
led_control #(23) uut_led_controller_clk12m5(.clk(clk_12m5), //时钟信号.rst_n(sys_rst_n), //复位信号,低电平有效.sled(led[0]) //LED指示灯接口 );//-------------------------------------
//25MHz时钟进行分频闪烁,计数器为24位
led_control #(24) uut_led_controller_clk25m(.clk(clk_25m), //时钟信号.rst_n(sys_rst_n), //复位信号,低电平有效.sled(led[1]) //LED指示灯接口 );//-------------------------------------
//25MHz时钟进行分频闪烁,计数器为25位
led_control #(25) uut_led_controller_clk50m(.clk(clk_50m), //时钟信号.rst_n(sys_rst_n), //复位信号,低电平有效.sled(led[2]) //LED指示灯接口 );//-------------------------------------
//25MHz时钟进行分频闪烁,计数器为26位
led_control #(26) uut_led_controller_clk100m(.clk(clk_100m), //时钟信号.rst_n(sys_rst_n), //复位信号,低电平有效.sled(led[3]) //LED指示灯接口 ); //-------------------------------------
//高4位LED指示灯关闭
assign led[7:4] = 4'b1111; endmodule
上面代码中调用了下面的控制led灯闪烁的模块:
`timescale 1ns / 1ps//单个LED闪烁
module led_control(input clk, //时钟信号input rst_n, //复位信号,低电平有效output sled //LED指示灯接口 ); parameter CNT_HIGH = 24; //计数器最高位
//-------------------------------------
reg[(CNT_HIGH-1):0] cnt; //24位计数器 //cnt计数器进行循环计数
always @ (posedge clk or negedge rst_n) if(!rst_n) cnt <= 0; else cnt <= cnt+1'b1; assign sled = cnt[CNT_HIGH-1]; //cnt从0开始计数时,led灯一直是亮的,然后计数达到最大值,led灯就灭了 endmodule
由于计数器使用不同的时钟计数,计同样的次数使用的时间不同,具体而言,12.5MHz的时钟计数到M花费的时间是25MHz的时钟的2倍,因此,若要二者计数时间相同,则25MHz的时钟要多计数一倍,也就是25MHz的时钟计数器的位数要多1位。这就是一个位数问题, 顶层模块调用同一个模块,可是被调用模块中的计数器位数不一致,如何解决这个调用问题呢?
这就引出了这篇博文的主题:parameter型常量的作用。
在被调用模块中声明了一个parameter型的常量,然后在顶层模块中调用该模块时,使用如下格式指定该参数的具体位数。
//12.5MHz时钟进行分频闪烁,计数器为23位
led_control #(23) uut_led_controller_clk12m5(
.clk(clk_12m5), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.sled(led[0]) //LED指示灯接口
);
Verilog HDL中用parameter来定义常量,即用parameter来定义一个标识符来代表一个常量,称为符号常量,即标识符形式的常量,采用标识符代表一个常量可以提高程序的可读性和可维护性。
parameter型常量的声明格式如下:
parameter 参数名1 = 表达式, 参数名2 = 表达式,...,参数名n = 表达式。
或
parameter 参数名1 = 表达式;
parameter 参数名2 = 表达式;
...
parameter 参数名n = 表达式;
上面的表达式是常数表达式,也就是说只能包含数字或先前已经定义过的参数。
具体而言如下:
parameter msb = 7;
parameter byte_size = 8, byte_msb = byte_size - 1;
等等。
参数型常量经常用于定义延迟时间和变量宽度。
定义延迟时间太简单了,不在话下,本博文主要强调参数型常量用于定义变量宽度的作用。
本文开头举的一个例子就是定义变量宽度的实实在在的案例。
下面再举一个简单的例子说明:
module Top(...);//这是一个顶层模块wire [3:0] A4;
wire [4:0] A5;
wire [15:0] F16;
wire [31:0] F32;Decode #(4,0) U_D1(A4,F16); //使得Width = 4, Polarity = 0;
Decode #(5) U_D2(A5,F32); //使得 Width = 5,Polarity 不变,即为1;endmodulemodule Decode(A,F); //这是一个子模块parameter Width = 1, Polarity = 1;
......endmodule
上面的顶层模块在对Decode模块进行实例化时,U_D1和U_D2的Width分别采用不同的值4和5,且U_D1的Polarity将为0。可以用例子中的方式来改变参数,即用#(4,0)向U_D1中传递Width = 4,Polarity = 0;用#(5)向U_D2中传递Width = 5,Polarity仍为1。
当然还有另外一种方式,即使用命令defparam命令来改变参数。这种方式我们下篇博文再说。