FPGA入门学习(一):Verilog基本语法学习

写在前面:自学笔记,推荐小伙伴一起学习,欢迎提出宝贵意见。参考内容有Verilog菜鸟教程,徐文波版《Xilinx FPGA开发实用教程》;

目录

一、Verilog的基本语法

1.1 Verilog模块的基本概念

1.2 模块内容

1.2.1 I/O说明

1.2.2 内部信号说明

1.3 数值表示

数值种类

整数数值表示方法

1.4 阻塞赋值与非阻塞赋值

1.5 运算符和表达式

1.6 描述语句

1.6.1 结构描述形式

1.6.2 数据流描述形式(assign)

1.6.3 行为描述形式

过程结构(initial、always)

语句块(begin...end/fork...join)

时序控制        

流控制(if、case、for、while、forever、repeat)

二、 Testbench编写

2.1 模块例化

一、Verilog的基本语法

1.1 Verilog模块的基本概念

        Verilog程序是由一个或者多个模块组成的,每个模块可以实现特定的功能,在设计时应该优先确定每个模块的功能,之后在进行代码编写。每个模块要进行端口定义,并说明输入、输出口,然后对模块的功能进行描述,即代码的编写。Verilog程序包括4个主要部分:端口定义、I/O说明、内部信号说明、功能定义。下面的代码即为一个模块:

//二选一多路选择器为例
module module_name(out, a, b, sl);
    input a, b, sl; //定义输入信号
    output out;    //定义输出信号
    
    reg out;        //定义存储器
    
    always @(sl or a or b)  //语法体
        if(!sl) 
            out = a;
        else
            out = b;
endmodule
/*module_name是模块名字,在定义时替换成合适的名字,字母和下划线,举例:uart_xx;*/
  • module起始,以endmodule结尾,其内部每个语句和数据定义的最后必须有分号(;)。
  • 每个文件只包含一个module,而且module名要小写,并且与文件名保持一致;
  • 不要书写空的模块,即:一个模块至少要有一个输入和一个输出;

暂时这么多,之后会补充


写在前面,关于Verilog的代码特点

  1. 区分大小写;
  2. 书写自由,一行可以写多个语句,一个语句也可以分行;
  3. 空白符(换行、制表、空格)都没有实际的意义,在编译阶段可忽略。

1.2 模块内容

        在模块的端口声明了模块的输入输出口,其格式如下:

        module  模块名(口1, 口2, 口3, ....);

这里只是声明了输入输出口,接下来需要具体指明每个端口是输入还是输出。

1.2.1 I/O说明

输入口—— 模块从外界读取数据的接口,在模块内不可写。

                 input   [ 信号位宽 - 1 : 0 ]   端口 i 名;

                input   [7:0]  a;  //说明了一个8位的输入端口 "a"

输出口——模块往外界送出数据的接口,在模块内不可读。

                output   [ 信号位宽 - 1 : 0 ]   端口 j 名;

                 output   [7:0]  out;  //说明了一个8位的输入端口 "out"

输入/输出口——可读取数据也可以送出数据,数据可双向流动。

                inout   [ 信号位宽 - 1 : 0 ]   端口1名;

                inout   [7:0]  in_out;  //说明了一个8位的输入/输出端口 "in_out"

注意:如果不定义位宽则默认位1

1.2.2 内部信号说明

线网(wire):wire 类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动。如果没有驱动元件连接到 wire 型变量,缺省值一般为 "Z"。(模块内的连接线)
        wire [7:0]   addr ;       //声明8bit位宽的线型变量addr,

寄存器(reg):寄存器(reg)用来表示存储单元,它会保持数据原有的值,直到被改写。(模块内的寄存器)

        reg [7:0]   addr ;       //声明8bit位宽的线型变量addr,

        reg [15:0]   ROMA   [7:0] ;       //声明了一个存储位宽为16位,存储深度为8的一个存储器。该存储器的地址范围是0到8。(memory型)

常量(parameter):定义常数(常量)。parameter  参数名1 = 数据;

        parameter  [3:0] S0 = 4'h0;   

1.3 数值表示

数值种类

Verilog HDL 有下列四种基本的值来表示硬件电路中的电平逻辑:

  • 0:逻辑 0 或 "假"
  • 1:逻辑 1 或 "真"
  • x 或 X:未知
  • z 或 Z:高阻

x 意味着信号数值的不确定(这里不区分大小写),即在实际电路里,信号可能为 1,也可能为 0。z 意味着信号处于高阻状态,常见于信号(input, reg)没有驱动时的逻辑结果。例如一个 pad 的 input 呈现高阻状态时,其逻辑值和上下拉的状态有关系。上拉则逻辑值为 1,下拉则为 0 。

整数数值表示方法

整技术表示格式:[长度] ' 基数  数值;

4'b1011         // 4bit 数值
5'o8            // 5位八进制数
9'd6             // 9位十进制数
32'h3022_c0de   // 32bit 的数值
/*不区分大小写;下划线是为了增加可读性; */
//一般直接写数字时,默认为十进制表示,例如下面的 3 种写法是等效的:
counter = 'd100 ;    //一般会根据编译器自动分频位宽,常见的为32bit
counter = 100 ;
counter = 32'h64 ;   //16进制64 == 十进制100

负数表示:通常在表示位宽的数字前面加一个减号来表示负数,

-6'd15  
-15
4'd-2 //非法说明,错误

        其中负数使用补码形式,-15 在 5 位二进制中的形式为 5'b10001(补码:取反加一为实际值,最高位1表示符号位,0001取反加一等于1111), 在 6 位二进制中的形式为 6'b11_0001(最高位1为符号扩展位)。

注意:减号放在基数和数字之间是非法的,

实数:实数可以用两种形式定义:十进制计数法和科学计数法。

1.十进制计数法
    2.0
    3.1415926
2.科学计数法
    235.12e2   实际值23512
    5e-4       实际值0.0005
其中e与E相同,根据Verilog语法定义,实数通过四舍五入隐式的转换为最相近的整数

字符串:字符串是双引号内的字符序列,字符串不能分成多行书写,用8位ASCII值表示的字符可看作是无符号整数,一个ASCII码占8位。

reg [1:8*7] Char;
Char = "counter";

1.4 阻塞赋值与非阻塞赋值

1.5 运算符和表达式

1.6 描述语句

1.6.1 结构描述形式

1.6.2 数据流描述形式(assign)

        数据流型描述一般都采用(assign)连续赋值语句来实现,主要用于实现组合功能。连续赋值语句右边所有的变量受持续监控,只要这些变量有一个发生变化,整个表达式将被重新赋值给左端。这种方法只能用于实现组合逻辑电路。

        assign  L_s = R_s;

//一个利用数据流描述的移位器
module mlshift2 (a, b)
    input a;
    output b;

    assign b == a << 2;
endmodule
//只要a值发生变化,b就会被重新赋值,所赋的值为a左移两位后的值

1.6.3 行为描述形式

        行为级描述主要包括过程结构、语句块、时序控制、流控制4个方面,主要用于时序逻辑功能的实现。(个人以为只需要了解关键字的意义即可)

过程结构(initial、always)
  • initial 模块        

        initial模块从模拟0时刻开始执行,且在仿真过程中只执行一次,在执行完一次后,该initial就被挂起,不在执行。如果仿真中有多个initial模块,则同时从0时刻开始并行执行。(initial模块是面向仿真的,是不可增和的,通常被用来测试模块初始化、监视、波形生成等功能)

initial begin
    //初始化输入
    clk = 0;
    ai = 0;
    bi = 0;
    //等待100ns,全局reset信号有效
    # 100;
    ai = 20;
    bi = 10;
end
  • always 模块

        always模块是一只重复执行的,并且可被综合。敏感事件表的目的就是触发always模块的运行,而initial后面是不允许有敏感事件表的,always过程块的触发条件是:只要a、b、c信号的电平有任意一个发生变化。

always @(a or b or c) begin
    ...
 end

        always模块主要是对硬件功能的行为进行描述,可以实现锁存器和触发器,也可以用来实现组合逻辑。利用always实现组合逻辑时,要将所有的信号放进敏感列表,而实现时序逻辑时却不一定要将所有的结果放进敏感信息列表。(上例中只有a、b、c中存在变化时就会执行语句块,如果存在其他的信号,比如e发生变化则不会执行语句块)

语句块(begin...end/fork...join)

        语句块就是initial模块或者always模块中位于begin...end/fork...join块定义语句之间的一组行为语句。

begin ... end   串行块,用来组合需要顺序执行的语句
fork ... join   并行块,用来组合需要并行执行的语句

//例:下面两段程序都是用来产生一系列延迟波形
parameter d = 50;
reg [7:0] r;
begin
    # d r = 'h35;    //语句1
    # d r = 'h35;    //语句2
    # d r = 'h35;    //语句3
    # d r = 'h35;    //语句4
    # d -> end_wave; //语句5,触发事件end_wave
end

parameter d = 50;
reg [7:0] r;
fork
    # d r = 'h35;     //语句1
    # 2d r = 'h35;    //语句2
    # 3d r = 'h35;    //语句3
    # 4d r = 'h35;    //语句4
    # 5d -> end_wave; //语句5,触发事件end_wave
join
//由于并行块中语句是同步进行的,所以为了产生间隔d的波形需要递增延时时间
时序控制        
  • 延时控制        always #10 Clk = ~Clk; 延时控制只能在仿真时使用,是不可综合的。在综合时所有的延时控制都会被忽略。
  • 事件控制        边沿触发事件与电平触发事件
//边沿触发事件计数器(计算时钟变化次数)
reg [4:0] cnt;
always @(posedge Clk or negedge Reset_n)begin      //posedge上升沿关键字  negedge下降沿关键字
    if(!Reset_n)
		cnt <= 0;
    else 
        cnt <= cnt + 1;
end

//电平触发事件计数器(记录a,b,c变化的次数)
reg [4:0] cnt;
always @(a or b or c)begin
    if(!Reset_n)
		cnt <= 0;
    else 
        cnt <= cnt + 1;
end
???如果a,b,c同时变化只记录一次???
流控制(if、case、for、while、forever、repeat)

流控制语句包含3类,即跳转、分支、循环语句。

//if语句    
always @(a1 or b1)begin
    if(a1) q <= d;
    else q <= 0   //else分支也可以默认,但会产生一些不可预料的结果,生成成本不期望的锁存器。
end

//case语句
always @(a1[1:0] or b1)begin
    case (a1)
    2'b00 : q <= b1;
    2'b01 : q <= b1 + 1;
    default : q <= b1 + 2;   //default分支虽然可以默认,但一般不要默认,可能会生成锁存器。
end

//for循环
for(i = 1; i <= size; i = i + 1)
    result = result + (a << (i - 1));

//while循环
while (temp)begin
    count = count + 1;
end

//forever循环 
/*forever必须写在initial模块中,用于产生周期性波形,系统函数 $finish 可退出 forever。*/
initial forever begin
    if (d) a = b + c;
    else a = 0;
end

//repeat循环
repeat (size)begin   //指定循环次数
    c = b << 1;
end

二、 Testbench编写

2.1 模块例化

        在一个模块中引用另一个模块,对其端口进行相关连接,叫做模块例化(一个模块其实就是一个器件,将器件连接起来的操作就是例化)。模块例化建立了描述的层次。信号端口可以通过位置或名称关联,端口连接也必须遵循一些规则。如果某些输出端口并不需要在外部连接,例化时 可以悬空不连接,甚至删除。一般来说,input 端口在例化时不能删除,否则编译报错,output 端口在例化时可以删除。建议 input 端口不要做悬空处理,无其他外部连接时赋值其常量,

        当例化端口与连续信号位宽不匹配时,端口会通过无符号数的右对齐或截断方式进行匹配。子模块的端口信号位宽大于例化对应端口,则截断;反之补位右对齐。

点击阅读上面例子的原文

用 generate 进行模块例化


 
  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于研究生来说,学习FPGA可以按照以下路线进行: 1. 入门基础知识:首先需要学习数字电路的基础知识,包括逻辑门、布尔代数、时序电路等。可以通过教材、在线课程或者视频教程进行学习。 2. HDL语言学习:掌握硬件描述语言(如VHDL或Verilog)是学习FPGA的重要一步。了解HDL的语法和基本概念,掌握模块化设计和状态机的原理。 3. FPGA架构和工具:了解FPGA的基本架构和内部结构,掌握常见的FPGA开发工具(如Xilinx Vivado或Altera Quartus)的使用方法。 4. RTL设计:学习使用HDL语言进行RTL(Register Transfer Level)设计,掌握常见的RTL设计技术,例如组合逻辑、时序逻辑和状态机设计等。 5. 静态时序分析:学习时序分析的基本原理和方法,了解时钟约束和时钟域的概念,掌握静态时序分析工具的使用。 6. IP核和系统集成:学习使用IP核进行快速设计和系统集成,掌握IP核配置和接口连接的方法。 7. 高级主题:进一步深入学习FPGA的高级主题,如嵌入式处理器系统设计、高速接口设计、数字信号处理(DSP)等。 8. 应用开发:根据个人兴趣和研究方向,选择合适的应用进行开发和实践,如图像处理、通信系统或者嵌入式系统等。 在学习的过程中,多动手实践是非常重要的。可以通过完成一些小项目或者参加FPGA竞赛来提升自己的实践能力。此外,阅读相关的学术论文和参考书籍也是进一步深入了解FPGA技术的好方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值