赞
踩
Verilog 的设计多采用自上而下的设计方法(top-down)。即先定义顶层模块功能,进而分析要构成顶层模块的必要子模块;然后进一步对各个模块进行分解、设计,直到到达无法进一步分解的底层功能块。这样,可以把一个较大的系统,细化成多个小系统,从时间、工作量上分配给更多的人员去设计,从而提高了设计速度,缩短了开发周期。
Verilog 是区分大小写的。
格式自由,可以在一行内编写,也可跨多行编写。
每个语句必须以分号为结束符。空白符(换行、制表、空格)都没有实际的意义,在编译阶段可忽略。例如下面两中编程方式都是等效的。
0 1 x z
合法的基数格式:4'b1011,32'h3022_c0de,'d100,'O332
负数——-6d15
,-15
。
实数——20.123,1.2e4【12000】
reg [0: 14*8-1] str ;
initial begin
str = “www.runoob.com”;
end
wire 类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动。如果没有驱动元件连接到 wire 型变量,缺省值一般为 “Z”。
寄存器(reg)用来表示存储单元,它会保持数据原有的值,直到被改写。
整数,实数,时间寄存器变量等数据类型实际也属于寄存器类型。
integer flag [7:0] ; //8个整数组成的数组
reg [3:0] counter [3:0] ; //由4个4bit计数器组成的数组
wire [7:0] addr_bus [3:0] ; //由4个8bit wire型变量组成的数组
wire data_bit[7:0][5:0] ; //声明1bit wire型变量的二维数组
reg [31:0] data_4d[11:0][3:0][3:0][255:0] ; //声明4维的32bit数据变量数组
存储器变量就是一种寄存器数组,可用来描述 RAM 或 ROM 的行为。
参数用来表示常量,用关键字 parameter 声明,只能赋值一次。
操作符——
以反引号 ` 开始的某些标识符是 Verilog 系统编译指令。
编译指令 | 含义 |
---|---|
`define | 宏定义,替换文本 |
`undef | 取消宏定义 |
`ifdef, `ifndef, `elsif, `else, `endif | 条件编译指令 |
`ifdef MCU51
parameter DATA_DW = 8 ;
`elsif WINDOW
parameter DATA_DW = 64 ;
`else
parameter DATA_DW = 32 ;
`endif
例子中,如果定义了 MCU51,则使用第一种参数说明;如果没有定义 MCU、定义了 WINDOW,则使用第二种参数说明;如果 2 个都没有定义,则使用第三种参数说明。
编译指令 | 含义 |
---|---|
`include | 使用 `include 可以在编译时将一个 Verilog 文件内嵌到另一个 Verilog 文件中,作用类似于 C 语言中的 #include 结构。该指令通常用于将全局或公用的头文件包含在设计文件里。文件路径既可以使用相对路径,也可以使用绝对路径。 |
`timescale | 在 Verilog 模型中,时延有具体的单位时间表述,并用 `timescale 编译指令将时间单位与实际时间相关联。`timescale 的时间精度设置是会影响仿真时间的。时间精度越小,仿真时占用内存越多,实际使用的仿真时间就越长。所以如果没有必要,应尽量将时间精度设置的大一些。 |
`timescale time_unit / time_precision
time_unit 表示时间单位,time_precision 表示时间精度,它们均是由数字以及单位 s(秒),ms(毫秒),us(微妙),ns(纳秒),ps(皮秒)和 fs(飞秒)组成。时间精度可以和时间单位一样,但是时间精度大小不能超过时间单位大小,例如下面例子中,输出端 Z 会延迟 5.21ns 输出 A&B 的结果。
在编译过程中,`timescale 指令会影响后面所有模块中的时延值,直至遇到另一个 `timescale 指令或 `resetall 指令。
编译指令 | 含义 |
---|---|
`default_nettype | 该指令用于为隐式的线网变量指定为线网类型,即将没有被声明的连线定义为线网类型。 |
`resetall | 该编译器指令将所有的编译指令重新设置为缺省值。`resetall 可以使得缺省连线类型为线网类型。当 `resetall 加到模块最后时,可以将当前的 `timescale 取消防止进一步传递,只保证当前的 `timescale 在局部有效,避免 `timescale 的错误继承。 |
`celldefine, `endcelldefine | 这两个程序指令用于将模块标记为单元模块,他们包含模块的定义。例如一些与、或、非门,一些 PLL 单元,PAD 模型,以及一些 Analog IP 等。 |
`unconnected_drive, `nounconnected_drive | 在模块实例化中,出现在这两个编译指令间的任何未连接的输入端口,为正偏电路状态或者为反偏电路状态。 |
`celldefine
module (
input clk,
input rst,
output clk_pll,
output flag);
……
endmodule
`endcelldefine
assign LHS_target = RHS_expression ;
wire A, B ;
wire Cout = A & B ;
一个模块中可以包含多个 initial 和 always 语句,但 2 种语句不能嵌套使用。
这些语句在模块间并行执行,与其在模块的前后顺序没有关系。
但是 initial 语句或 always 语句内部可以理解为是顺序执行的(非阻塞赋值除外)。
每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始。
initial 语句从 0 时刻开始执行,只执行一次,多个 initial 块之间是相互独立的。
initial 理论上来讲是不可综合的,多用于初始化、信号检测等。
与 initial 语句相反,always 语句是重复执行的。always 语句块从 0 时刻开始执行其中的行为语句;当执行完最后一条语句后,便再次执行语句块中的第一条语句,如此循环反复。
由于循环执行的特点,always 语句多用于仿真时钟的产生,信号行为的检测等。
基于时延的时序控制出现在表达式中,它指定了语句从开始执行到执行完毕之间的时间间隔。
时延类型 | 解释 |
---|---|
常规时延 | 遇到常规延时时,该语句需要等待一定时间,然后将计算结果赋值给目标信号。 |
内嵌时延 | 遇到内嵌延时时,该语句先将计算结果保存,然后等待一定的时间后赋值给目标信号。 |
当延时语句的赋值符号右端是常量时,2 种时延控制都能达到相同的延时赋值效果。
当延时语句的赋值符号右端是变量时,2 种时延控制可能会产生不同的延时赋值效果。
一般时延赋值方式:遇到延迟语句后先延迟一定的时间,然后将当前操作数赋值给目标信号,并没有"惯性延迟"的特点,不会漏掉相对较窄的脉冲。
内嵌时延赋值方式:遇到延迟语句后,先计算出表达式右端的结果,然后再延迟一定的时间,赋值给目标信号。
类型 | 内容 |
---|---|
顺序块 | 顺序块用关键字 begin 和 end 来表示。顺序块中的语句是一条条执行的。当然,非阻塞赋值除外。顺序块中每条语句的时延总是与其前面语句执行的时间相关。 |
并行块 | 并行块有关键字 fork 和 join 来表示。并行块中的语句是并行执行的,即便是阻塞形式的赋值。并行块中每条语句的时延都是与块语句开始执行的时间相关。 |
嵌套块 | 顺序块和并行块还可以嵌套使用。 |
命名块 | 我们可以给块语句结构命名。命名的块中可以声明局部变量,通过层次名引用的方法对变量进行访问。 |
while (condition) begin
…
end
for(initial_assignment; condition ; step_assignment) begin
…
end
repeat (loop_times) begin
…
end
repeat 的功能是执行固定次数的循环,它不能像 while 循环那样用一个逻辑表达式来确定循环是否继续执行。repeat 循环的次数必须是一个常量、变量或信号。如果循环次数是变量信号,则循环次数是开始执行 repeat 循环时变量信号的值。即便执行期间,循环次数代表的变量信号值发生了变化,repeat 执行次数也不会改变。
forever begin
…
end
forever 语句表示永久循环,不包含任何条件表达式,一旦执行便无限的执行下去,系统函数 $finish 可退出 forever。
forever 相当于 while(1) 。
通常,forever 循环是和时序控制结构配合使用的。
// 产生一个时钟
reg clk ;
initial begin
clk = 0 ;
forever begin
clk = ~clk ;
#5 ;
end
end
函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:
1)不含有任何延迟、时序或时序控制逻辑
2)至少有一个输入变量
3)只有一个返回值,且没有输出
4)不含有非阻塞赋值语句
5)函数可以调用其他函数,但是不能调用任务
function [range-1:0] function_id ;
input_declaration ;
other_declaration ;
procedural_statement ;
endfunction
函数在声明时,会隐式的声明一个宽度为 range、 名字为 function_id 的寄存器变量,函数的返回值通过这个变量进行传递。当该寄存器变量没有指定位宽时,默认位宽为 1。
函数通过指明函数名与输入变量进行调用。函数结束时,返回值被传递到调用处。
function_id(input1, input2, …);
下面用函数实现一个数据大小端转换的功能。
module endian_rvs #(parameter N = 4) ( input en, //enable control input [N-1:0] a , output [N-1:0] b ); reg [N-1:0] b_temp ; always @(*) begin if (en) begin b_temp = data_rvs(a); end else begin b_temp = 0 ; end end assign b = b_temp ; //function entity function [N-1:0] data_rvs ; input [N-1:0] data_in ; parameter MASK = 32'h3 ; integer k ; begin for(k=0; k<N; k=k+1) begin data_rvs[N-k-1] = data_in[k] ; end end endfunction endmodule
常数函数是指在仿真开始之前,在编译期间就计算出结果为常数的函数。常数函数不允许访问全局变量或者调用系统函数,但是可以调用另一个常数函数。
这种函数能够用来引用复杂的值,因此可用来代替常量。
在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。
Verilog 用关键字 automatic 来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的。因此,automatic 函数中声明的局部变量不能通过层次命名进行访问,但是 automatic 函数本身可以通过层次名进行调用。
module digital_tube ( input clk , input rstn , input en , input [3:0] single_digit , input [3:0] ten_digit , input [3:0] hundred_digit , input [3:0] kilo_digit , output reg [3:0] csn , //chip select, low-available output reg [6:0] abcdefg //light control ); reg [1:0] scan_r ; //scan_ctrl always @ (posedge clk or negedge rstn) begin if(!rstn)begin csn <= 4'b1111; abcdefg <= 'd0; scan_r <= 3'd0; end else if (en) begin case(scan_r) 2'd0:begin scan_r <= 3'd1; csn <= 4'b0111; //select single digit abcdefg <= dt_translate(single_digit); end 2'd1:begin scan_r <= 3'd2; csn <= 4'b1011; //select ten digit abcdefg <= dt_translate(ten_digit); end 2'd2:begin scan_r <= 3'd3; csn <= 4'b1101; //select hundred digit abcdefg <= dt_translate(hundred_digit); end 2'd3:begin scan_r <= 3'd0; csn <= 4'b1110; //select kilo digit abcdefg <= dt_translate(kilo_digit); end endcase end end /*------------ translate function -------*/ function [6:0] dt_translate; input [3:0] data; begin case(data) 4'd0: dt_translate = 7'b1111110; //number 0 -> 0x7e 4'd1: dt_translate = 7'b0110000; //number 1 -> 0x30 4'd2: dt_translate = 7'b1101101; //number 2 -> 0x6d 4'd3: dt_translate = 7'b1111001; //number 3 -> 0x79 4'd4: dt_translate = 7'b0110011; //number 4 -> 0x33 4'd5: dt_translate = 7'b1011011; //number 5 -> 0x5b 4'd6: dt_translate = 7'b1011111; //number 6 -> 0x5f 4'd7: dt_translate = 7'b1110000; //number 7 -> 0x70 4'd8: dt_translate = 7'b1111111; //number 8 -> 0x7f 4'd9: dt_translate = 7'b1111011; //number 9 -> 0x7b endcase end endfunction endmodule
有限状态机(Finite-State Machine,FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。状态机不仅是一种电路的描述工具,而且也是一种思想方法,在电路设计的系统级和 RTL 级有着广泛的应用。
Verilog 中状态机主要用于同步时序逻辑的设计,能够在有限个状态之间按一定要求和规律切换时序电路的状态。状态的切换方向不但取决于各个输入值,还取决于当前所在状态。 状态机可分为 2 类:Moore 状态机和 Mealy 状态机。
输出会在一个完整的时钟周期内保持稳定,即使此时输入信号有变化,输出也不会变化。输入对输出的影响要到下一个时钟周期才能反映出来。这也是 Moore 型状态机的一个重要特点:输入与输出是隔离开来的。
Mealy 型状态机的输出是在输入信号变化以后立刻发生变化,且输入变化可能出现在任何状态的时钟周期内。因此,同种逻辑下,Mealy 型状态机输出对输入的响应会比 Moore 型状态机早一个时钟周期。
题意描述:饮料单价 2 元,该售卖机只能接受 0.5 元、1 元的硬币。考虑找零和出货。投币和出货过程都是一次一次的进行,不会出现一次性投入多币或一次性出货多瓶饮料的现象。每一轮售卖机接受投币、出货、找零完成后,才能进入到新的自动售卖状态。
该售卖机的工作状态转移图如下所示,包含了输入、输出信号状态。
其中,coin = 1 代表投入了 0.5 元硬币,coin = 2 代表投入了 1 元硬币。
状态机设计:3 段式(推荐)
状态机设计如下:
(0) 首先,根据状态机的个数确定状态机编码。利用编码给状态寄存器赋值,代码可读性更好。
(1) 状态机第一段,时序逻辑,非阻塞赋值,传递寄存器的状态。
(2) 状态机第二段,组合逻辑,阻塞赋值,根据当前状态和当前输入,确定下一个状态机的状态。
(3) 状态机第三代,时序逻辑,非阻塞赋值,因为是 Mealy 型状态机,根据当前状态和当前输入,确定输出信号。
module vending_machine_p3 ( input clk, input rstn, input [1:0] coin, output [1:0] change, output shell ); parameter IDLE = 3'd0; parameter GET05 = 3'd1; parameter GET10 = 3'd2; parameter GET15 = 3'd3; reg [2:0] st_next; reg [2:0] st_cur; always @(posedge clk or negedge rstn) begin if (!rstn) begin st_cur <= 1'b0; end else begin st_cur <= st_next; end end always @(*) begin case(st_cur) IDLE: case(coin) 2'b01: st_next = GET05; 2'b10: st_next = GET10; default:st_next = IDLE; endcase GET05: case(coin) 2'b01: st_next = GET10; 2'b10: st_next = GET15; default:st_next = GET05; endcase GET10: case(coin) 2'b01: st_next = GET15; 2'b10: st_next = IDLE; default:st_next = GET10; endcase GET15: case(coin) 2'b01,2'b10: st_next = IDLE; // 2'b10: st_next = GET15; default:st_next = GET15; endcase default: st_next = IDLE; endcase end reg [1:0] change_r; reg sell_r; always @(posedge clk or negedge rstn) begin if (!rstn) begin change_r <= 2'b0; sell_r <= 1'b0; end else if ((st_cur == GET15 && coin == 2'h1) || (st_cur == GET10 || coin == 2'd2)) begin change_r <= 2'b0; sell_r <= 1'b1; end else if (st_cur == GET15 && coin == 2'h2) begin change_r <= 2'b1; sell_r <= 1'b1; end else begin change_r <= 2'b0; sell_r <= 1'b0; end end assign sell = sell_r; assign change = change_r; endmodule
testbench 设计如下。仿真中模拟了 4 种情景,分别是:
case1 对应连续输入 4 个 5 角硬币;case2 对应 1 元 - 5 角 - 1 元的投币顺序;case3 对应 5 角 - 1 元 - 5 角的投币顺序;case4 对应连续 3 个 5 角然后一个 1 元的投币顺序。
`timescale 1ns/1ps module test; reg clk; reg rstn; reg [1:0] coin; wire [1:0] change; wire sell; parameter CYCLE_200MHZ = 10; always begin clk = 0; #(CYCLE_200MHZ/2); clk = 1; #(CYCLE_200MHZ/2); end reg [9:0] buy_oper; initial begin buy_oper = 'h0; coin = 2'h0; rstn = 1'b0; #8 rstn = 1'b1; @(negedge clk); #16; buy_oper = 10'b00_0101_0101; repeat(5) begin @(negedge clk); coin = buy_oper[1:0]; buy_oper = buy_oper >> 2; end #16 ; buy_oper = 10'b00_0010_0110 ; repeat(5) begin @(negedge clk) ; coin = buy_oper[1:0] ; buy_oper = buy_oper >> 2 ; end //case(3) 0.5 -> 1 -> 0.5 #16 ; buy_oper = 10'b00_0001_1001 ; repeat(5) begin @(negedge clk) ; coin = buy_oper[1:0] ; buy_oper = buy_oper >> 2 ; end //case(4) 0.5 -> 0.5 -> 0.5 -> 1, taking change #16 ; buy_oper = 10'b00_1001_0101 ; repeat(5) begin @(negedge clk) ; coin = buy_oper[1:0] ; buy_oper = buy_oper >> 2 ; end end vending_machine_p3 u_mealy_p3 ( .clk (clk), .rstn (rstn), .coin (coin), .change (change), .sell (sell) ); always begin #100 if ($time >= 10000) $finish; end endmodule
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。