FPGA开发环境的搭建和verilog代码的实现

描述

FPGA需要良好的数电模电基础,verilog需要良好C语言基础。

FPGA的准备工作:下载Quartus II,Modelsim和notepad++(notepad++是文本编辑器,quartus有自带的,可以不装,也可以用其他的)。后台回复“FPGA软件下载”获取安装包。安装完成后,实现Quartus II与Modelsim和notepad++的关联(不关联notepad++的话默认用quartus自带的)。方法是:tools栏的option,如下图选中Win64。

开发环境

关联notepad++如图,在文件夹中选择安装好的exe应用程序。

开发环境

Verilog可能是FPGA路上的第一个绊脚石,但是代码类的学习都是熟能生巧,掌握规律只需3天即可上手。

基础语法

1、区分大小写,分号结尾(空格、换行无意义),单行注释用//,多行注释用/* 代码 */,标识符区分大小写,关键字小写。

2、0表示假,1表示真,X/x表示未知,Z/z表示高阻。

3、十进制('d 或 'D),十六进制('h 或 'H),二进制('b 或 'B),八进制('o 或 'O),4'b1011中4表示位数,b表示进制,1011是数值。 字符串用双引号且不能换行。

4、最常用的数据类型:wire和reg,wire 表示物理连线,reg表示存储单元。

5、表达式由操作符和操作数构成,其中操作符有算术、关系、等价、逻辑、按位、归约、移位、拼接、条件共9种,操作数可以是任意的数据类型。注意逻辑与(&&)、按位与(&)。

数电中有组合逻辑电路和时序逻辑电路。组合逻辑电路模块:

6、连续赋值语句以assign开头用于对 wire 型变量进行赋值

7、普通时延:assign #10 Z = A & B ; 把A与B的结果延时10个单位再赋值给Z。 隐式时延:wire #10 Z = A & B; 声明wire型变量时对其进行包含一定时延的连续赋值。 A 或 B 任意一个变量发生变化,那么 Z会有 10 个时间单位的时延。 如果在这 10 个时间单位内,A 或 B 任意一个值又发生了变化,那么计算 Z 的新值时会取 A 或 B 当前的新值。 所以称之为惯性时延,即信号脉冲宽度小于时延时,对输出没有影响。 因此仿真时,时延一定要合理设置,防止某些信号不能进行有效的延迟。

时序逻辑电路模块:

8、一个模块中可以包含多个 initial 和 always 语句,但 2 种语句不能嵌套使用。 语句在模块间并行执行,与前后顺序无关。 语句内部是顺序执行的(非阻塞赋值除外)。 **initial **语句从 0 时刻开始执行,只执行一次,多个 initial 块之间是相互独立的。 如果 initial 块内包含多个语句,需要使用关键字 begin 和 end 组成一个块语句。 只包含一条语句可以不用。 **always **语句是重复执行的。 always 语句块从 0 时刻开始执行其中的行为语句; 当执行完最后一条语句后,便再次执行语句块中的第一条语句,如此循环反复。 always 语句多用于仿真时钟的产生,信号行为的检测等。

后面会结合数电详细讲解。

推荐使用hdlbits网站刷题,非常基础,给出了模块的框架,只需要按照需求填入关键语句。

1、熟悉wire

开发环境

module top_module(
    input a,
    input b,
    input c,
    input d,
    output out,
    output out_n ); 
    assign out=(a&b)|(c&d);
    assign out_n=~((a&b)|(c&d));
endmodule

verilog采用自顶向下的设计方法,分模块设计,这样做的好处是,不同的人可以负责不同的模块,最后组装起来。 每个模块都以Module 模块名开始,以endmodule结尾,且需要定义输入输出,以及输出与输入的关系。 在这个实例中,需要填入的就是两个assign语句,得到out和out_n。

2、熟悉向量

当位宽大于 1 时,wire 或 reg 即可声明为向量的形式。

开发环境

module top_module( 
    input [2:0] a,
    input [2:0] b,
    output [2:0] out_or_bitwise,
    output out_or_logical,
    output [5:0] out_not
);
assign out_or_bitwise=a|b;
assign    out_or_logical=a||b;
assign  out_not={~b,~a};
endmodule

逻辑或||和位或|的区别:位或是两个 N 位向量之间的每一位都进行或运算形成N位新向量,而逻辑运算将整个向量视为布尔值(true = 非零,false = 零)并生成 1 位输出。{向量1,向量2,...}可以用于合并向量。位数少时可以用于向量反转,例如in[3:0]反转可以使out={in[0],in[1],in[2],in[3]}。

开发环境

module top_module (
    input a, b, c, d, e,
    output [24:0] out );//
    assign out=~{{5{a}},{5{b}},{5{c}},{5{d}},{5{e}}}^{5{a,b,c,d,e}};
endmodule

向量复制的格式是{复制次数{向量}},注意复制后的向量需要用{}。异或是a^b,同或是~a^b。

3、熟悉模块

和之前在python中一样,模块实例化有非常重要的作用。如果事先定义一个模块的功能,后面每一次使用时,都只需要让自己定义的端口对应上原模块的端口,就可以调用。如果不这样做,每一次使用都需要写一次这个模块的代码。

实例化有两种方式,按位置:mod_a instance1 ( wa, wb, wc );按名称:mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );前一种不建议使用,因为如果模块的端口列表发生更改,则还需要查找并更改模块的所有实例化以匹配新模块。后一种虽然写起来比较复杂,但是将每一个端口都对应上,与位置无关。

开发环境

按名称:

module top_module ( input a, input b, output out );
mod_a instance1 (
    .out(out),
    .in1(a), 
    .in2(b) 
 );
endmodule

按位置:

module top_module ( input a, input b, output out );
    mod_a instance1 (a,b,out);
endmodule

top_module是模块名,这个模块有两个输入端口和一个输出端口。这个模块里用到了一个事先定义好的函数mod_a,也有两个输入端口和输出端口。将端口对应起来,这个模块就可以实现mod_a的功能。

下面看几个复杂的例子:

移位寄存器

开发环境

module top_module ( input clk, input d, output q );
wire q1,q2;
my_dff d0(.clk(clk),.d(d),.q(q1));
 my_dff d1(.clk(clk),.d(q1),.q(q2));
 my_dff d2(.clk(clk),.d(q2),.q(q));
endmodule

这个模块名是top_module,有时钟,输入和输出。实现定义了一个函数my_dff,整个模块使用了三次D触发器,因此需要实例化三次。对于函数d0,d1,d2都需要把端口对应上,由于q1,q2是引入的变量,用来将前一级触发器输出和后一级触发器输入连接上,所以需要先声明q1,q2是wire型变量。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分