Verilog简单基础

Verilog是一种用于描述数字系统硬件结构和行为的语言,支持自顶向下的设计方法。它包括基础语法如区分大小写、数值表示、数据类型(如wire和reg)、表达式以及移位操作符。Verilog还涉及编译指令、连续赋值、过程结构(如initial和always语句)、阻塞与非阻塞赋值,以及模块和端口的声明与实例化。此外,文章还提到了函数和任务的使用,以及状态机的概念。
摘要由CSDN通过智能技术生成

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

过程结构

行为级建模的两种基本语句:initialalways
一个模块中可以包含多个initialalways语句,但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

常常在时序逻辑块中用非阻塞,组合逻辑中用阻塞,使用非阻塞赋值避免竞争冒险

过程连续赋值
能够替换其他所有wirereg的赋值,发生作用时,其右端表达式中任何操作数变化都会引起过程连续赋值语句重新执行

  • assigndeassign
    赋值对象只能是寄存器、寄存器组
    带复位端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

assigndeassign改写

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
  • forcerelease
    赋值对象可以是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

语句块

顺序块并行块:提供了将两条或更多条语句组成语法结构上相当于一条一句的机制。

顺序块: beginend
并行块: forkjoin

`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

disablealwaysforever中使用时仅跳出当前回合,下一回合仍继续执行。

条件语句和多路分支语句

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结束
  • 端口内部不可见,对模块的调用只通过端口连接
  • 端口列表:不带类型、位宽的信号罗列在模块声明里
  • 端口声明:在端口列表罗列出后可以在模块实体声明,有inputoutputinout三种类型。
    端口的仿真
    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

函数与任务

可以利用taskfunction将重复性的行为级设计进行封装
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型:输出在输入发生变化后立刻改变
自动售货机

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值