FPGA/verilog 学习笔记—— verilog程序框架

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FPGA/verilog 学习笔记—— verilog程序框架相关的知识,希望对你有一定的参考价值。


文章目录

  • ​​一、注释​​
  • ​​二、关键字​​
  • ​​三、 Verilog程序框架​​
  • ​​1. 模块​​
  • ​​(1)基础概念​​
  • ​​(2)定义一个模块​​
  • ​​(3)功能定义的三种方法​​
  • ​​(4)模块的调用​​
  • ​​2. 结构语句​​
  • ​​(1)initial语句​​
  • ​​(2)always语句​​
  • ​​(3)组合逻辑电路和时序逻辑电路​​
  • ​​3. 赋值语句​​
  • ​​(1)阻塞赋值​​
  • ​​(2)非阻塞赋值​​
  • ​​(3)使用原则​​
  • ​​(4)assign​​
  • ​​4. 条件语句​​
  • ​​(1)if_else语句​​
  • ​​(2)case/casez/casex语句​​

一、注释

  • verilog中有两种注释方式,同C语法

二、关键字

  • 全部关键字[
  • FPGA/verilog

  • 常用关键字
  • FPGA/verilog

三、 Verilog程序框架

1. 模块

(1)基础概念

  1. Verilog程序的基本单元是 “模块(block)” (类似C的函数)
  2. 一个模块由两部分组成
  • 接口描述
  • 端口定义
  • I/O说明
  • 逻辑功能描述
  • 内部信号声明
  • 功能定义
  1. 可综合和不可综合
  • 可综合的模块:可以用综合工具生成实际电路结构的模块
  • 不可综合的模块:不能生成实际电路结构的模块,常用于testbench

(2)定义一个模块

  • 示例代码
module block(a,b,c,d);  //端口定义
input a,c; //I/O说明
output c,d;

assign c = a | b; //功能定义
assign d = a & b;

endmodule
  1. 模块用 ​​moudule​​ 和 ​​endmodule​​ 包起来
  2. moudle后接模块名
  3. 模块名后是参数列表,每个参数是一个端口,别忘了​​;​
  4. I/O说明
  • ​input​​​ 指定端口为输入端口,默认是​​wire​​类型。写成​​input reg​​则为​​reg​​类型
  • ​ouput​​​ 指定端口为输出端口,默认是​​wire​​类型。写成​​output reg​​则为​​reg​​类型
  • I/O说明语句也可以和端口定义写在一起
module block(
input a,
input b,
output c,
output d
);
  1. 这段程序对应的接口描述和逻辑功能
  • 接口描述
  • FPGA/verilog

  • 逻辑功能
  • FPGA/verilog

  1. 模块基本结构
  2. FPGA/verilog

(3)功能定义的三种方法

  • 示例代码
//端口定义 + I/O说明---------------------------------------------
module flow_led(
input sys_clk , //系统时钟
input sys_rst_n, //系统复位,低电平有效
output reg [3:0] led //4个LED灯,这是一个1位位宽reg的数组,数组长4
);

//内部信号声明--------------------------------------------------
reg [23:0] counter; //reg define,这是一个24位位宽reg类型变量
//功能定义-----------------------------------------------------
//计数器对系统时钟计数,计时0.2秒
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
counter <= 24d0;
else if (counter < 24d10)
counter <= counter + 1b1;
else
counter <= 24d0;
end

//通过移位寄存器控制IO口的高低电平,从而改变LED的显示状态
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
led <= 4b0001;
else if(counter == 24d10)
led[3:0] <= led[2:0],led[3];
else
led <= led;
end

endmodule
  • 功能定义的三种方法(以下三种逻辑功能是并行的)
  • assign语句:描述组合逻辑
  • always语句:描述组合/时序逻辑
  • always块中,逻辑是顺序执行
  • always块之间,逻辑是并行的
  • 实例化元件:如​​and #2 u1(q,a,b); //and例化一个与门模块​

(4)模块的调用

  1. 模块调用类似C中函数调用,信号通过模块端口在模块之间传递
  2. 示例
  • 被调模块(子模块)
  • 主调模块(主模块)
  • 这里最下面就是例化time_count模块
  • 在​​#​​​后接的是:传入的​​parameter​​ 类型数据
  • 在​​()​​​中的是:传递的​​wire​​​ 和 ​​reg​​ 类型数据
  • 注意
  • 子模块的输入端口(input端口),连接的信号可以是**​​wire​​ 或者 ​​reg​​ 类型**
  • 子模块的输出端口(output端口),连接的信号必须是wire 类型
  • 传递的信号位宽必须保持一致
  • 另外一种连接方式:直接把主模块的信号放到子模块参数列表中,这要求信号排列顺序一致

2. 结构语句

(1)initial语句

  1. initial语句在模块中只在最开始执行一次
  2. 常用于测试文件的编写,用来产生仿真测试信号(激励信号),或者用来对存储器变量赋值
  3. 如果initial语句有多行,需要用​​begin​​​ 和 ​​end​​ 把它们括起来(类似C的大括号)
  4. 示例
//给输入信号的初始值
initial begin
//这三条同时执行
sys_clk <= 1b0;
sys_rst_n <= 1b0;
touch_key <= 1b0;

//#是延时
#20 sys_rst_n <= 1b1; //过20ns,拉高sys_rst_n
#10 touch_key <= 1b1; //再过10ns,touch_key
#30 touch_key <= 1b0; //再过30ns....
#110 touch_key <= 1b1;
end

(2)always语句

  1. always语句一直在不断地重复活动
  2. 只有和一定的时间控制结合在一起才有作用(比如下面那个​​#10​​)
  3. 如果always语句有多行,需要用​​begin​​​ 和 ​​end​​ 把它们括起来(类似C的大括号)
  4. 示例
//产生50Mhz时钟,周期20us
always #10 sys_clk <= ~sys_clk;
  1. 波形(最上面一行是clk信号)
  2. always的时间控制(触发控制)
  • 触发方式
  • 简单延时: ​​always #10 sys_clk <= ~sys_clk;​
  • 沿触发:常用于描述时序逻辑行为
  • 电平触发:常用来描述组合逻辑行为

    如果组合逻辑的输入变量很多,编写敏感列表会很麻烦,这时可以@(*)表示对后面语句块中所有输入变量的变化都是敏感的
  • 触发信号
  • 单个信号
  • 多个信号,中间需要​​or​​连接
  • 引起触发的多个事件名/信号名组成的列表称为 “敏感列表

(3)组合逻辑电路和时序逻辑电路

  • 数字电路分为组合逻辑电路和时序逻辑电路两类
  • 组合逻辑电路
  • 任意时刻的输出仅仅决定于此时刻的输入信号,与电路原来的状态无关
  • 时序逻辑电路
  • 任意时刻的输出决定于此时刻的输入信号以及电路原来的状态,因此时序逻辑电路必须具有记忆功能

FPGA/verilog

3. 赋值语句

  • reg只能在initial和always块中赋值
  • wire只能通过assign或通过模块输入输出端口赋值

(1)阻塞赋值

  1. 格式:​​b = [delay] a;​​ (这里的delay是语句内延时)
  2. 操作:计算右值,内部延时,更新左值。
  3. 所谓 “阻塞” :指同一个always块中,后面的赋值语句是在前一句赋值语句结束之后才开始赋值的
  4. 示例
  • 示例(1)
  • 由于采用了阻塞赋值,执行过程如下
  • ​a = 0;​​ 这时a=0,b=2,c=3
  • ​b = a;​​ 这时a=0,b=0,c=3
  • ​c = b;​​ 这时a=0,b=0,c=0
  • 从波形图来看,就好像三条语句在同一时刻执行完一样
  • 示例(2)
initial begin 
t = #5 0; // 时间为(0,5]时,t=x
t = #4 1; // 时间为(5,9]时,t=0
t = #10 0; // 时间为(9,19]时,t=1
end

(2)非阻塞赋值

  1. 格式:​​b <= [delay] a;​​(这里的delay是语句内延时)
  2. 操作:赋值开始的时候,计算右值;进行内部延时;赋值结束时,更新左值
  3. 所谓 “非阻塞”:指在计算右值和更新左值期间,允许其他的非阻塞赋值语句同时计算右值和左值
  4. 非阻塞赋值只能用于对reg类型的变量进行赋值,因此只能用在 initial块​always块​
  5. 示例
  • 示例(1)
  • 由于采用了非阻塞赋值,执行过程如下
  • ​a = 0;​​​ ​​b = a;​​​ ​​c = b;​​ 三行同时开始计算右值,然后同时更新左值,导致a的值变为0,b的值变为一开始a的值(1),c变为一开始b的值(2)
  • 示例(2)
initial begin 
t = #5 1;
t = #4 0;
t = #10 0;
end
// 时间为(0,4]时,t=x,时间为(4,5]时,t=0,时间为(5,10]时,t=1,时间为(10,正无穷]时,t=0,
  • 示例(3)
  • 这里我需要在一个时钟周期内先读再写一个reg类型变量,通过非阻塞赋值来实现它
  • 分析以下代码,当clk的下降沿来到时,两个always语句同时执行。reg_a的值在第二条always中被读取,在此时刻结束前的最后一刻,reg_a的值在第一条always语句中被刷新
always @(negedge clk)
reg_a <= data; // 写
always @(negedge clk)
reg_b <= reg_a; // 读

(3)使用原则

  1. 对always语句块外用到的变量进行赋值时,使用<=,计算中间结果时,使用​=​
  2. 注意
  • 同一个always块中不要两种赋值混用(不知道该生成哪种电路了)
  • 不允许在多个always块中对同一个变量进行赋值(因为是并行执行,这样会冲突)

(4)assign

  • Verilog语言使用一个或多个模块对数字电路建模,通常可以用三种方式:
  1. 结构描述方式:即调用其它已定义好的低层模块或直接调用Verilog内部度基本门级元件描述电路结构和功能。
  2. 数据流描述方式:连续使用赋值语句(assign)对电路的逻辑功能进行描述。
  3. 行为风格描述方式:使用过程块语句结构(initial和always语句)和比较抽象的高级程序语句对电路的逻辑功能进行描述。
  4. 也可以把这些方式混用,称为混合设计风格描述方式
  • assign语句就属于第二种。
  • 连续赋值语句用于对​​wire​​​型变量进答行赋值,它由关键字内​​assign​​开始,后面跟着由操作数和运算符组成的逻辑表达式。
  • assign语句中,左边变量的数据类型必须是​wire​模块的input和output如果容不特别声明类型,默认是wire类型。
  • 格式​​assgin [delay]LHS_net = RHS_expression​
  • assign右边的操作数无论何时发生变化,右边的表达式都会被重新计算,并且在指定的延时后(默认0),计算得到的值被赋予等号左边的线网变量。因此也称为 “连续赋值语句”
  • assign、always、initial等是并发执行的
  • 例如:
wire A,B,SEL,L;//声明4个线型变量
assign L=(A&~SEL)|(B&SEL);//连续赋值
  • 总之
  • assign常习惯用在模块的​output​赋值
  • 如果直接写​​out = ...​​,这是把内部的reg信号直接作为输出,这里其实暗含了用根导线直接把reg的输出与out连接起来
  • 如果写​​assign out = ...​​,这是把上面的隐含的线显式表示了
  • 这二者的RTL没有太大变化
  • 注意​​out​​​是一个​​wire​​类型
  • 从实用角度来说,assign意义比较大。当内部有多个信号需要输出,可是输出引脚只有一个,那么这时就可以进行选择。如下:​​assign lholda= (条件)? (lholda_ra): lholda_rb;​​ 可以嵌套使用。(这个真的很很很很常用!!)

4. 条件语句

  • 条件语句必须在过程块(即由 ​​initial​​​ 和 ​​always​​ 引导的块语句)中使用

(1)if_else语句

  1. 格式
//写法1--------------------------------------
if(a > b)
out = data_1;

//写法2--------------------------------------
if(a > b)
out = data_1;
else
out = data_2;

//写法3--------------------------------------
if(表达式1)
语句1;
else if(表达式2)
语句2;
...
else if(表达式n)
语句n;
else
语句n+1;
  1. 说明
  • 允许一定程度的简写
  • ​if(a)​​​ 等同于 ​​if(a==1)​
  • ​if(!a)​​​ 等同于 ​​if(a!=1)​
  • ​if​​ 语句对表达式的值进行判断
  • 若为 ​​0​​​ ,​​x​​​ ,​​z​​ 则按假处理
  • 若为 ​​1​​ 则按真处理
  • if​else​ 语句后面的操作语句可以用 ​begin​​end​ 包含多行语句
  • 允许 ​​if​​ 语句的嵌套

(2)case/casez/casex语句

  1. 格式
  2. 说明
  • 分支表达式的值互不相同
  • 所有表达式的位宽必须相等
  • 不要省略位宽用​​bx​​​代替​​nbx​​,因为省略写法默认位宽32位,如果case的信号不是32位易出错
  • ​casez​​语句
  • 比较时不考虑表达式中的高阻值​​z​
  • 示例分析
  • 第一个case,只要求高四位是​​1100​
  • 第二个case,要求高六位是​​1100XX​
  • ​casex​​语句
  • 比较时不考虑表达式中的高阻值​​z​​​和不定值​​x​
  • 如果程序还是上图那样,这时第一个和第二个case判断的一样了,报错


以上是关于FPGA/verilog 学习笔记—— verilog程序框架的主要内容,如果未能解决你的问题,请参考以下文章

FPGA:Verilog HDL程序的基本结构

FPGA verilog 小问题求解

基于 FPGA verilog 的 Ethercat 主站工程代码

实现FPGA Verilog HDL与NIOS II的通信数据交换——利用AVALON总线

[FPGA]Verilog 60s秒表计时器(最大可计时间长达9min)

FPGA Verilog HDL 系列实例--------步进电机驱动控制