FPGA_学习_04_Verilog基础语法和Modelsem仿真

news/2024/11/8 22:52:44/

前言:对于以前学过C/C++/C#的作者来讲,Verilog的基础语法算是特别简单的。本文主要介绍Verilog的基础语法和Modelsem仿真。

Verilog的基础语法

1 模块声明

FPGA开发是以模块为基础的,每个可综合的.v文件都是一个模块,模块由moduleendmodule来声明。在这两个关键字的内部,完成模块功能的实现。

在Vivado的一个空项目中,新建一个.v源文件,会自动生成以下代码(我把多余的注释删除了)

`timescale 1ns / 1ps	// 这行以后代码经常会见,表示时间单位是1ns,精度是1ps    module verilog_base(	// module 模块名(...		            // 定义模块的输入输出接口
);			            // );
endmodule		        // endmodule

 下面以一个与门为例,进一步演示如何声明一个模块。

`timescale 1ns / 1ps	        // 这行以后代码经常会见,表示时间单位是1ns,精度是1ps    module verilog_base(input		wire		a,	// input表示这是模块的输入接口  wire是变量类型 a是变量名,除最后一个变量定义外,都以‘,’结束input		wire		b,output		wire		c	// output表示这是模块的输出接口 wire是变量类型 c是变量名,最后一个变量定义,不以‘,’结束
);assign c = a & b;				// 完成与门的组合逻辑幅值endmodule

 

2 变量类型

在Verilog开发中有两种数据类型,一种是wire (线),一种是reg (寄存器)。在数字电路中信号只有两种形态,一种是传输,一种是存储。传输是通过连接线,存储是用寄存器,因此也就清楚了在Verilog中常用了wire和reg变量了。wire和reg变量模型如下图所示:

 

wire型变量在物理结构上只是一根线,在Verilog描述时,对线型变量赋值用assign即可,相对简单。

C语言赋值我直接 a = b;   Verilog赋值得在前面加关键字。  assign a = b;

reg型变量左端有一个输入端口D,右端有一个输出端口Q,并且reg型存储数据需要在clk(时钟)沿的控制下完成。 clk也即是我们常说的方波,它由晶振产生,是我们描述数字电路是最基本的时间单元,它周期固定,占空比一般为50%(高电平占整个周期的比例)。clk的低电平用数字0表示,高电平用1表示,从低电平转变到高电平的过程叫做上升沿,从高电平转变到低电平的过程叫做下降沿。

 

 在对reg型变量赋值时,必须在always块内完成,可以选择用时钟上升沿,也可以选择时钟下降沿,具体用上升沿还是下降沿可以根据需要定。下面尝试把第一节给出的与门例子的输出定义成reg型,实现两个输入相与之后将结果传输给寄存器的功能。即

 Verilog代码如下:

`timescale 1ns / 1ps	// 这行以后代码经常会见,表示时间单位是1ns,精度是1ps    module verilog_base(input		wire		clk,	// 系统时钟输入,由晶振提供input		wire		a,input		wire		b,output		reg		    c	    // 将输出的类型改成reg
);// reg型变量的赋值必须要在always块里面,注意:赋值符号不是'=',而是 '<='。
always @(posedge clk) beginc <= a & b;
endendmodule

在always块中,@(posedge clk)表示的是,每当遇到clk的上升沿时,执行always块中的语句。

c <= a & b;中的'<='表示的是非阻塞赋值。Verilog中有两种赋值方式:

        1  '<=' 非阻塞赋值 适用于给reg变量赋值,always块 - 时序逻辑

        2  ‘=’ 阻塞赋值 适用于给wire变量赋值,assign块 – 组合逻辑

阻塞赋值时,输入改变输出是同时改变,在非阻塞赋值中,只有在时钟变化的时候,输出才会发生变化。

begin-end其作用与C C++ C#中的{}是一样的,在Verilog中,就可以视begin-end为{}

3 多位宽数据表示

在数字电路中,所有数据最终都是以二进制形式呈现的,二进制和十进制都只是一种数据的表达方式,同一个数据无论用二进制还是十进制来表达,本质上代表的值是一样的。十进制数据是0到9组成,二进制是0,1组成。 比如5,它的二进制是3’b101,十进制是’d5。二进制的每一个0/1叫做1位,若想知道数据是多少位。可以用计算器看。

 

如果我要表示53698745这个数,就需要26个位。 那么在定义的时候就要像下面这样定义

wire [25:0] data;
reg [25:0] data;
wire [26:1] data;
reg [26:1] data;

一般推荐最低位都是从0开始,也就是[25:0]这种写法。

4 赋值语句

前面第二小节已讲了一些相关内容。assign 和 always分别用于组合逻辑的赋值 和 时序逻辑的赋值。 还有一种initial,一般仅用于仿真文件中。

initial 语句是初始化语句,会在上电(电路刚刚运行时)执行一次,不会循环执行。在电路中只有可以存储数据的寄存器才有初始化的必要,所以initial 语句中被赋值的变量也必须为reg。 某些第三方综合软件认为initial 是不可以被综合的,也就是说不可以被写到功能文件中的,所以为了代码的兼容性,我们尽量只在测试文件中写initial 语句。示例代码如下所示。

reg	clk;
initial beginclk = 0;forever #(5) clk = ~clk;    // forever和always的区别是 forever仅用于仿真
end

示例代码产生的是周期为10ns的时钟信号。

5 运算符

5.1 关系运算符(< 、<= 、== 、>= 、> 、!=)

C语言中可以用if(2<a<6),但Verilog只能用if(a>2 && a<6),其余一致

5.2 逻辑运算符(&& 、||、 !)

逻辑运算符与C 语言中一致:对于&& 只有参与运算的两个数据都为真时,结果才为真,对于|| 只要参与运算的两个数据由一个为真,结果就为真。(!)进行的时逻辑取反。

5.3 位运算符(& 、|、 ~)

位运算是对二进制的运算,运算时,需要将数据都转换位二进制后才能进行计算。当位宽不一样时,位宽少的在高位补0。

5.4 条件运算符

如:assign a = (b>6)?1’b1:1’b0;

当b 大于6 时,a 等于1,否则a 等于0;

5.5 赋值运算符

前面已经介绍过Verilog 中的赋值运算符,<= 和=,其中<= 是非阻塞赋值,用于时序逻辑,=是阻塞赋值,用于组合逻辑。

5.6 移位运算符(<< >>)

>>为向右移位,每次右移一位,数据高位补零,向下溢出的数据丢弃。<<与之相反。

5.7 位拼接运算符({}

位拼接运算符,可以将不同数据的位拼接成一个新的数据。

reg[3:0] a = 4'b0110;
reg[4:0] b = 5'b10110;
reg[4:0] c;always@(posedge clk) beginc <= {b[1],a[1:0],b[3],b[1]};
end

拼接完成后,c = 5’b11001;

6 条件判断

同一个变量可以在不同的情况下获得不同的值,不同的情况需要判断语句来描述。Verilog HDL 中经常使用的判断语句有if else 语句和case endcase 语句,两种判断语句必须写在always 语句中,不能写在assign 中。

6.1 If-else

if-else 语句与C 语言当中的使用方法类似。

always@(posedge clk)beginif(a==1) beginb <= 1;endelse if(a==2) beginb <= 2;end		else if(a==3) beginb <= 3;endelse if(a==4) beginb <= 4;endelse if(a==5) beginb <= 5;endend

if else 叠加不易过多,不然可能造成线路的延时过多。每一个if else 语句都会生成一选择器,当if else 过多时,选择器链路就会很长,而每两级选择器之间都会有线路的延时,当链路过多时,造成的延时就会很多,这样对于描述的电路的时序影响会很大,时序出问题时,就算是功能仿真正确,下板后电路也是不正确的。

使用if-else 语句时需要考虑优先级的顺序,优先执行的条件放在上方,优先级较低的条件放在后方。最好还是用case吧。

6.2 Case 语句

Verilog的case语句和C语言的switch case类似,在if-else 级数过多的情况下,也可以使用case 语句,case 语句生成的是多路器

 

always@(posedge clk) begincase(a)0: b <= 0;1: b <= 1;2: b <= 2;default:b <= 0;endcase
end

Case 语句以case 开始,以endcase 结尾,在其之间,列出要判断的条件,根据条件的值,执行对应的代码。

Modelsem仿真

1 编写仿真程序

新建一个空白的项目的流程在上一篇博客中已经讲过了,这里不在赘述。 按照下面的操作流程,新建一个仿真文件。

 

 

 

 Sublime Text (编辑软件)可以利用 源文件 自动生成 仿真文件。

 

 

 

仿真程序输入要用reg型输出要用wire型
 

 再次强调initial语句也常用在仿真文件,请尽量不要在实际的功能模块中使用。

 

`timescale 1ns/1ps
module tb_verilog_base (); /* this is automatically generated */reg  a;reg  b;wire  c;verilog_base inst_verilog_base (.a(a), .b(b), .c(c));initial begina = 0;b = 0;#100; // 延时100nsa = 1;b = 0;#100; a = 0;b = 1;#100;a = 1;b = 1;end
endmodule

2 Vivado仿真流程

 

 

 

 

 从仿真中我们可以看到,assign赋值是立即生效的。

3 Modelsem仿真流程

Modelsem软件安装及编译相关的库文件等操作,请参考Vivado:【1】Vivado 2018.3 配置ModelSim仿真_Alex-YiWang的博客-CSDN博客

 

 

 

 

 

正常人都能看出来Modelsem的原始界面是比vivado丑的,但Modelsem的界面是可以配置的,可自行查找相关资料。

4 时序逻辑仿真

源文件

`timescale 1ns / 1ps	// 这行以后代码经常会见,表示时间单位是1ns,精度是1ps    module verilog_base(input		wire		clk,	// 系统时钟输入input		wire		a,input		wire		b,output		reg		    c	    // 将输出的类型改成reg
);// reg型变量的赋值必须要在always块里面,注意:赋值符号不是'=',而是 '<='。
always @(posedge clk) beginc <= a & b;
endendmodule

仿真文件

`timescale 1ns/1ps
module tb_verilog_base (); /* this is automatically generated */reg  clk;reg  a;reg  b;wire  c;verilog_base inst_verilog_base (.clk(clk), .a(a), .b(b), .c(c));initial beginclk = 0;forever #(5) clk = ~clk;endinitial begina = 0;b = 0;#100; // 延时100nsa = 1;b = 0;#100; a = 0;b = 1;#100;a = 1;b = 1;endendmodule

仿真结果

时序逻辑赋值,并不是立即生效,而是在相应的“沿”信号处执行。

把源文件的always块修改成下降沿触发  always @(negedge clk) begin

把仿真文件的最后一个#100改成#33。

 把仿真文件的 #33 改回 #100。

 

5 Modelsem小技巧

打开以下这个文件

 

 

你修改源文件或者修改仿真文件保存后,可以不用重新在vivado点击仿真。 可以在Modelsem的命令行执行以下三个命令。

        1 do tb_verilog_base_compile.do 重新编译一遍

        2 restart 有弹窗没关系,直接回车键默认Ok

        3 run 运行仿真

这样可以快速修改代码,高效率仿真。

最后愿我们共同进步! 感谢您的阅读,欢迎留言讨论、收藏、点赞、分享。


http://www.ppmy.cn/news/82179.html

相关文章

【C生万物】 字符串内存函数篇 (下)

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; &#x1f449; 专栏&#xff1a;《C生万物 | 先来学C》&#x1f448; 前言&#xff1a; 承接上篇&#xff0c;在认识了常用的字符串和内存函数后&#xff0c;带大家实现几个函…

【腾讯云Finops Crane集训营】降本增效之 Crane 初体验

1. Crane 初识2. Crane 如何进行成本优化&#xff1f;3. Crane 快速上手体验3.1 安装 Prometheus 和 Grafana3.2 安装 Crane 和 Fadvisor3.3 验证安装是否成功3.4 访问 Dashboard 4. Crane 初体验 - 总结&建议5. 关于腾讯云 Finops Crane 集训营 最近有幸参加了腾讯云 Fino…

基于广义Benders分解法的综合能源系统优化规划(matlab程序)

目录 1 主要内容 广义benders分解法流程图&#xff1a; 优化目标&#xff1a; 约束条件&#xff1a; 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序复现文章《综合能源系统协同运行策略与规划研究》第四章内容基于广义Benders分解法的综合能源系统优化规划&…

【利刃出鞘】链式思维利用ChatGPT,让其成为工作中的利剑?附带初学者扫盲SpringBoot

【利刃出鞘】链式思维利用ChatGPT&#xff0c;让其成为工作中的利剑 一、一点思考二、技术学习——链式思维2.1 springboot注册bean的几种方式&#xff1f;2.2 springboot Component 注册的原理&#xff1f;2.3 springboot引用注册的Bean原理&#xff1f;2.4 private final MyB…

专为Windows电脑和服务器设计的磁盘管理软件

关于Windows磁盘管理 磁盘管理是Windows自带工具&#xff0c;允许你对磁盘进行一些基本操作&#xff0c;Windows个人用户和Windows Server用户可以使用它来&#xff1a; 1. 创建一个新驱动器&#xff0c;如“新建简单卷”功能。 2. 将一个卷扩展到当前未被同一磁盘…

通过Python的filestools库给图片添加全图水印

文章目录 前言一、filestools库简介二、安装filestools三、查看filestools版本四、图片添加全图水印1.引入库2.添加水印3.效果 五、参数调整对比1.水印颜色1.1通过名称设置颜色1.2通过RGB值设置颜色1.3通过十六进制设置颜色 2.水印字体的大小3.水印的透明度4.水印直接的间隔5.水…

如何对图片进行卷积计算

1 问题 如何对图片进行卷积计算&#xff1f; 2 方法 先导入torch和torch里的nn类&#xff0c;然后设置一个指定尺寸的随机像素值的图片&#xff0c;然后使用nn.conv2d函数进行卷积计算&#xff0c;然后建立全连接层&#xff0c;最后得到新的图片的尺寸 步骤: (1) 导入实验所需要…

快递业的最新发展趋势:2023年市场预测

快递业是随着电子商务崛起而迅速发展的行业之一。自从互联网取代了线下商业模式&#xff0c;电子商务的发展成为了现代零售业的主要趋势&#xff0c;而快递业则变得越来越重要和不可或缺。未来的快递业需要应对许多挑战和机遇。 在2023年&#xff0c;快递业将进一步走向数字化、…