01 简介
关于Verilog
verilog
- 以文本形式来描述数字系统硬件的结构和行为的语言。
- 表示逻辑电路图、逻辑表达式、数字逻辑系统所完成的逻辑功能
- 五个层次:系统级、算法级、寄存器传输级、门级、开关级
Verilog的设计方法
自上向下(top-down)
先定义顶层模块的功能 再分成子模块
02 基础语法
注意事项
- 区分大小写
- 每个语句必须以分号为结束符
- // 单行注释 /**/ 多行注释
- 标识符 任意一组字母、数字、$和_的组合,第一个字符必须是字母或_
- 关键字 verilog中预留的用于定义语言结构的特殊标识符,全小写
数值表示
- 电平逻辑:0 - 逻辑0或假 1 - 逻辑1或真 z或Z-高阻 x或X-未知
- 十进制d 十六进制h 八进制o 二进制b
- _可以增强数值可读性
- 直接写数字时默认十进制
- 字符串 不能包含回车符
数据类型
常用:wire和reg
- wire: 硬件单元之间的物理连线,由其连接的期间输出端连续驱动,缺省值一般为Z
wire in_1;
- reg: 存储单元,保持数据原有值,直到被更改
reg clk;
- 向量:位宽大于1
支持指定bit位后固定位宽的向量域选择访问
- `[bit+:width]`
- `[bit-:width]`
- 对信号进行重新组合时需要借助大括号
```verilog
wire [31:0] temp1,temp2;
assign temp1 = {byte[0][7:0],data1[31:8]};
- 整型 integer
辅助信号,综合后实际电路里没有j这个信号 - 实数 real
- 时间 time
- 数组
<数组名>[<下标>]
比如integer tmp[7:0];
8个整数组成的数组 - 参数 parameter
只能赋值1次,但是可以通过实例化的方式更改值
局部参数用localparam表示 - 字符串
表达式
操作数有一位为x或z时,结果为x
归约操作符
只有一个操作数,对这个向量操作数逐位进行操作,最终产生1bit的结果
A = 4'b1010;
&A; //result: 1 & 0 & 1 & 0 可用来判断变量A是否全为1
~|A; //re:~(1|0|1|0) 可用来判断变量A是否全为0
移位操作符
算术左移和逻辑左移时,右边低位会补 0。
逻辑右移时,左边高位会补 0;而算术右移时,左边高位会补充符号位,以保证数据缩小后值的正确性。
编译指令
以反引号开始的某些标识符时verilog的系统编译指令
\\`define 用于文本替换
`define DATA_DW 32
\\`undef 用于取消之前的宏定义
`undef DATA_DW
\\`ifdef `elsif `else `endif
\\`include 编译时将一个verilog文件内嵌到另一个verilog文件中
\\`timescale 将时间单位与实际时间相关联,格式为
`timescale time_unit/time_precision
\\时间精度必须小于等于时间单位,影响仿真时占用的内存
\\`default_nettype 用于为隐式的线网变量指定为线网类型
\\`resetall 将所有的编译指令重新设置成缺省值
\\`celldefine `endcelldefine 用于将模块标记为单元模块
\\`unconnected_drive `nounconnected_drive 出现在这两个编译指令之间的任何未连接的输入端口,为正偏电路状态或者为反偏电路状态
连续赋值
assign
wire cout,a,b;
assign cout = a & b;
实例:全加器
module full_adder(
input Ai,Bi,Ci,
output So,Co
);
assign So = Ai ^ Bi ^ Ci;
assign Co = (Ai & Bi) | (Ci & (Ai | Bi));
endmodule
\*
可以写成
assign {Co,So} = Ai + Bi + Ci;
*\
仿真在vivado
中可以不锁引脚,其仿真如下
`timescale 1ns/1ns //设置时间单位和精度
module test;
reg Ai,Bi,Ci;
wire So,Co; //输出值一般都是wire类型
initial begin //初始化
{Ai,Bi,Ci} = 3'b0;
forever begin //每10ns加一
#10;
{Ai,Bi,Ci} = {Ai,Bi,Ci} + 1'b1;
end
end
//实例化
full_adder u_adder(
.Ai (Ai),
.Bi (Bi),
.Ci (Ci),
.So (So),
.Co (Co)
);
//暂时未知 待补
initial begin
forever begin
#100;
if($time >= 1000) begin
$finish;
end
end
end
endmodule
过程结构
行为级建模的两种基本语句:initial
和always
一个模块中可以包含多个initial
和always
语句,但2种语句不能嵌套使用
initial
语句从0时刻开始执行,只执行一次,多用于初始化、信号检测
always
重复执行,多用于仿真时钟的产生、信号行为的检测等
//产生100MHz时钟
`timescale 1ns/1ns
module test;
parameter CLK_FREQ = 100; //100MHz
parameter CLK_CYCLE = 1e9 / (CLK_FREQ * 1e6); //转换成ns
reg clk;
initial
clk = 1'b0;
always
# (CLK_CYCLE/2) clk = ~clk;
always begin
#10
if($time >= 1000) begin
$finish
end
end
endmodule
过程赋值
- 过程赋值:只有在语句执行时才起作用
- 连续性赋值:任何操作数的改变都会影响表达式的结果
过程赋值包括两种语句:阻塞赋值
、非阻塞赋值
阻塞赋值
顺序执行,常在initial
里使用
语法:=
非阻塞赋值
并行执行
语法:<=
\\ 阻塞赋值和非阻塞赋值的区别
`timescale 1ns/1ns
module test;
reg[3:0] ai,bi;
reg[3:0] ai2,bi2;
reg[3:0] val_blk;
reg[3:0] val_non;
reg[3:0] val_non_2;
initial begin
ai = 4'd1;
bi = 4'd2;
ai2 = 4'd7;
bi2 = 4'd8;
#20;
ai = 4'd3;
bi = 4'd4;
val_blk = ai + bi;
val_non <= ai + bi;
ai2 <= 4'd5;
bi2 <= 4'd6;
val_non_2 = ai2 + bi2;
end
always begin
#10;
if($time >= 1000) $finish;
end
endmodule
常常在时序逻辑块中用非阻塞,组合逻辑中用阻塞,使用非阻塞赋值避免竞争冒险
过程连续赋值
能够替换其他所有wire
和reg
的赋值,发生作用时,其右端表达式中任何操作数变化都会引起过程连续赋值语句重新执行
assign
和deassign
赋值对象只能是寄存器、寄存器组
带复位端D触发器
module dff_normal(
input rstn,
input clk,
input D,
output reg Q
);
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
Q <= 1'b0;
end
else begin
Q <= D;
end
end
endmodule
用assign
和deassign
改写
module dff_assgin(
input rstn,
input clk,
input D,
output reg Q
);
always @(posedge clk) begin
Q <= D;
end
always @(negedge rstn) begin
if(!rstn) begin
assign Q = 1'b0;
end
else begin
deassign Q;
end
end
endmodule
force
和release
赋值对象可以是reg也可以是wire
无条件强制赋值,多用于交互式调试
当 force 作用在寄存器上时,寄存器当前值被覆盖;release 时该寄存器值将继续保留强制赋值时的值。之后,该寄存器的值可以被原有的过程赋值语句改变。
当 force 作用在线网上时,线网值也会被强制赋值。但是,一旦 release 该线网型变量,其值马上变为原有的驱动值。
时序赋值
两大时序控制方法:时延控制 事件控制
时延控制
指定了语句从开始执行到执行完毕的时间间隔,时延可以是数字、标识符或者表达式
- 常规时延
reg val_test; reg val_general; #10 val_general = val_test; //或直接写为 #10;
- 内嵌时延
```verilog
\\遇到内嵌时延时,需要先将计算结果保存
reg val_test;
reg val_embed;
val_embed = #10 val_test;
边沿触发事件控制
在Verilog中,事件指某一reg或wire变量发生值的变化
- 一般事件控制
用符号@表示
posedge
正跳变
negedge
负跳变 - 命名事件控制
声明event类型 并触发该变量来识别事件是否发生event start_receiving; always @(posedge clk_samp) begin -> start_receiving; end always @(start_receiving) begin data_buf = {data_if[0],data_if[1]}; end
- 敏感列表
当多个信号或事件中任意一个发生变化时都能触发语句执行,这些事件组成的列表叫敏感列表。
```verilog
always @(*)
/*
always @(posedge clk or negedge rstn)
*/
电平敏感事件控制
电平是敏感信号,常用while来表示电平敏感情况
initial begin
wait(start_enable);
forever begin
@(posedge clk_samp);
data_buf = {data_if[0],data_if[1]};
end
end
语句块
顺序块和并行块:提供了将两条或更多条语句组成语法结构上相当于一条一句的机制。
顺序块: begin
和 end
并行块: fork
和 join
`timescale 1ns/1ns
module test;
reg[3:0] ai_sequen,bi_sequen;
reg[3:0] ai_paral,bi_paral;
reg[3:0] ai_nonblk,bi_nonblk;
initial begin
#5 ai_sequen = 4'd5;
#5 bi_sequen = 4'd8;
end
initial fork
#5 ai_paral = 4'd5;
#5 bi_paral = 4'd8;
join
initial fork
#5 ai_nonblk <= 4'd5;
#5 bi_nonblk <= 4'd8;
join
endmodule
顺序块和并行块可以嵌套着用
命名块 可以给块语句结构命名,声明局部变量,通过层次名引用的方法对变量进行访问
`timescale 1ns/1ns
module test;
initial begin: module_name //命名模块必须要加分号
integer i;
i = 0;
forever begin
#10 i = i + 10;
end
end
reg stop_flag;
initial stop_flag = 1'b0;
always begin: detect_stop
if(test.module_name.i == 100) begin
stop_flag = 1'b1;
end
#10;
end
命名的块也可以被禁用,用disable
表示,可以用来从循环中退出或处理错误。
`timescale 1ns/1ns
module test;
initial begin: module1
integer i;
i = 0;
if(i >= 50) begin
disable module2.clk_gen;
disable module1;
end
i = i + 10;
end
reg clk;
initial begin: module2
while(1) begin: clk_gen
clk = 1; #10;
clk = 0; #10;
end
end
当disable
在always
和forever
中使用时仅跳出当前回合,下一回合仍继续执行。
条件语句和多路分支语句
if 条件表达式必须在圆括号里
if(condition) // do while the condition is true
else if(condition_2) //TO-DO
else //do while all conditions are fault
case 多路条件分支
case(case_express)
condition_1: //to-do
condition_2: //to-do
default: //to-do
endcase
/* 表示条件选项中的无关项
casex x表示无关值
casez ?表示无关值
*/
循环语句
while
while(condition) begin
//to-do
end
for
for(initial_assignment;condition;step_assignment) begin
//to-do tips:i++ and i-- is not admitted in verilog
end
repeat
repeat(times) begin
end
forever
forever begin //equal to while(1)
// use $finish to exit it
end
模块与端口
![[Pasted image 20221218231709.png]]
- 模块必须以
module
开始,endmodule
结束 - 端口内部不可见,对模块的调用只通过端口连接
- 端口列表:不带类型、位宽的信号罗列在模块声明里
- 端口声明:在端口列表罗列出后可以在模块实体声明,有
input
、output
和inout
三种类型。
端口的仿真
input用reg定义,output用wire定义
模块例化
在一个模块中引用另一个模块,对其端口进行相关连接
命名端口连接
将需要例化的模块端口与外部信号按照其名字进行连接
full_adder1 u_adder0(
.Ai (a[0]),
.Bi (b[0]),
.Ci (c == 1'b1 ? 1'b0 : 1'b1),
.So (so_bit0),
.Co (co_temp[0]) //.Co () 悬空,output一般可以删除
);
顺序端口连接
将需要例化的模块端口按照模块声明时端口的顺序与外部信号进行匹配连接,位置要严格保持一致
full_adder1 u_adder0(
a[1],b[1],co_temp[0],so_bit0,co_temp[1]
);
端口例化注意事项
- input端口可以连接wire或reg,在内部必须是wire
- output端口必须连接wire,内部可以是wire或reg
- inout必须连接wire
- output可悬空或删除
- input悬空表示高阻态,逻辑值为z,不能删除
generate模块例化
可进行多个模块的重复例化
genvar i;
generate
//TO-DO
endgenerate
带参数例化
当一个模块被另一个模块引用例化时,高层模块可以对低层模块的参数值进行改写。这样就允许在编译时将不同的参数传递给多个相同名字的模块,而不用单独为只有参数不同的多个模块再新建文件。
- 使用
defparam
- 带参数值模块例化
层次访问
通过使用一连串的.
对各模块进行层次分隔连接,多用于仿真中。
a = top.u_m2.u_n3.c;//访问子模块u_m2中叶单元u_n3中的变量c
函数与任务
可以利用task
或function
将重复性的行为级设计进行封装
task
描述共同代码段,并在模块内任意位置被调用
函数功能 + 时序控制逻辑
特点
- 没有或有多个输出
- 无返回值
- 可以在非零时刻执行
- 不能出现always语句
- 可以调用函数和任务
- 可以作为单独语句出现在语句块
/*
声明格式:
task task_id:
// Port Declaration
// Procedural Statement
endtask
*/
function
- 只能在模块中定义,作用范围只局限于此模块
- 不含任何延迟、时序控制逻辑
- 至少有一个输入,只有一个返回值,无输出
- 不含有非阻塞赋值语句
- 可以调用其他函数,但不能调用任务
/*
声明格式:
function [range-1:0] function_id;
input_declaration;
other_declaration;
procedural_statement;
endfunction
调用格式:
function_id(input1,input2,...);
*/
- 常数函数
编译期间就计算出结果为常数的函数
不允许访问全局变量
不允许调用系统函数 - automatic函数
调用时自动分配新的内存空间
数码管译码器 4位十进制
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,
output reg[6:0] abcdefg
);
reg[1:0] scan_r;
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; //选择个位
abcdefg <= dt_translate(single_digit);
end
end
end
/*--------translate function--------------*/
function [6:0] dt_translate;
input[3:0] data;
begin
case(data)
4'd0:dt_translate = 7'b1111_110; //0
4'd1:dt_translate = 7'b0110_000; //1
4'd2:dt_translate = 7'b1101_101; //2
4'd3:dt_translate = 7'b1111_001; //3
4'd4:dt_translate = 7'b0110_011; //4
4'd5:dt_translate = 7'b1011_011; //5
4'd6:dt_translate = 7'b1011_111; //6
4'd7:dt_translate = 7'b1111_000; //7
4'd8:dt_translate = 7'b1111_111; //8
4'd9:dt_translate = 7'b1111_011; //9
end
endfunction
endmodule
状态机
有限状态机
Moore型:输出只与当前状态有关,和当前输入无关。输入、输出是隔离开的。
Mealy型:输出在输入发生变化后立刻改变
自动售货机