Cyclone FPGA踏足笔记(二):Verilog语法学习总结

前言

花了一个月时间零零碎碎看了下Verilog的语法,终于把Verilog的基本语法学了个大概,可以自己写点小东西了,由于笔者自己都没学完,所以这篇文章肯定不是一篇介绍Verilog语法的方方面面的教程文章(我也没那能耐),而是侧重于写自己学习过程中的一些理念和一些个人认为在学习时必须掌握的一些语法要点。

资料推荐

这里谈一下一些笔者自己学习过程中参考过的资料。首先是《你好FPGA:一本可以听的入门书》这本书,这是一本入门的书,目前笔者自己是看到了第六章,正好把第六章看完。个人感觉这本书讲的是有点浅的,而且条理性不强,并没有系统化的讲语法,而是通过一个个小项目和实例来讲语法,这样的的好处是读的过程中边读边动手,会对各种语法的使用有更深刻的认识,但是缺点就是可能会缺乏对整个Verilog语法体系的系统化认识。为了补足这一点,笔者同时结合了菜鸟教程上的一份Verilog教程:菜鸟教程:Verilog教程,这份教程讲解的更深刻系统点,但是觉得不适合一上来就直接拿着看,因为实在讲的太细节了。可以在看其他浅显的入门教程时,大概预览一遍这份教程的目录,知道Verilog都有些什么东西,先建立一个对Verilog的总体印象,之后看书本教程时碰到感兴趣的知识点想深入点了解,再去翻阅菜鸟教程上的这份 Verilog教程的对应章节。也就是把它当工具书用了。拿菜鸟上的这份教程举例来说,当大概掌握了 5.2 Verilog 模块例化 这一章节以及之前的大部分内容时,也就可以用Verilog来做点事了。目前笔者正处于这一步,感觉有必要对先前的内容要点进行总结,至于其后的函数、任务、状态机等内容,这些都是值得单独各自开一篇博文细细梳理总结的。

一些思考

其实感觉有一个要点,要明白Verilog最后生成的是硬件电路,写Verilog时最好不要当作在写程序,而是当作是在画硬件电路,写下Verilog语句时,脑子里大概想一下实际的电路的模样,同时学会用RTL Viewer来在跑仿真之前验证自己的设计(虽然笔者自己目前也看的稀里糊涂)。由于最后生成的是硬件电路,所以一切事情都是在编译阶段就决定了的,硬件电路定了就是定了,不像程序还可以做动态加载之类的操作,所以在灵活性上应该是比不上软件了,但是有利的一面就是硬件电路的实时性会很好,而且可以实现真正的并行工作。明白FPGA的利弊,那么在写Verilog时可以时刻思考怎么把FPGA的实时性和并行的长处发挥出来,而规避其短处。
还有就是感觉不要把掌握语法看成学习FPGA中最重要的事,对Verilog语法的理解够用就行,要把FPGA用好,重点不是对Verilog有多么熟悉,而是对要用FPGA实现出来的那个东西的理解有多深刻,FPGA、单片机、还有各种语言这些东西毕竟只是工具,重要的是依托这些工具,将知识、想法和理念实现出来。工具用的多了,自然会慢慢得心应手。

Verilog知识点

虚的说多了,还是得写点干货的,以下列举一些笔者学到现在,认为需要注意的一些知识点(由于笔者自己是写嵌入式软件的,所以主要注重与C语言等软件编程语言有差异的地方)。写这些最主要还是想加深自己的理解,如果以后可以顺带帮助看到这篇文章的人,那当然是更好的。

知识点1:堵塞赋值与非堵塞赋值

书本上对这个知识点是这么解释的,这里摘抄一些:

以赋值操作符“<=”来标识的赋值操作称为“非阻塞型过程赋值(Nonblocking Assignment),在begin-end串行语句块中,一条非阻塞过程语句的执行不会阻塞下一条语句的执行,也就是说,在本条非阻塞型过程赋值语句对应的赋值操作执行完之前,下一条语句也可以开始执行。
以赋值操作符”=“来标识的赋值操作称为”阻塞型过程赋值(Blocking Assignment),begin-end串行块中的各条阻塞型过程赋值语句将以它们在顺序块中的排列次序依次得到执行。
非阻塞过程赋值主要用于实现时序逻辑电路,阻塞过程赋值主要用于实现组合逻辑电路。

其实要理解这个知识点,结合它们生成的硬件电路会更好点。国内很多博客介绍这个都只是侧重于这两种赋值有什么区别,好一点的可能加一句

非阻塞过程赋值主要用于实现时序逻辑电路,阻塞过程赋值主要用于实现组合逻辑电路

但是为什么是这样的呢?国外有一篇资料比较好的讲了后面的原理:I. Blocking vs. Nonblocking Assignments,是一份PDF文档,可以下载下来看看。
笔者总结了一下,对于组合逻辑电路来说,其由多个逻辑门单元组合而成。比如下图级联了两个与门,我的后一级与门要输出结果,就必须先计算出前一级与门的结果,这时就必须用阻塞过程赋值来实现,而这是如果用非堵塞过程赋值,我的前一级与门都没有完成计算,后一级与门如果这时依照前一级的输出来计算结果,最终结果就是错误的。
在这里插入图片描述而对于时序逻辑电路呢,拿串联的触发器举例会好一点,比如我想实现如下由D触发器组成的时序电路,就得用非堵塞过程赋值了,原因我直接敲图里了:
在这里插入图片描述

知识点2:缩减运算符

缩减运算是对单个操作数进行与或非递推运算,最后的运算结果是一位的二进制数。

比如:a = 1000_1000,此时b = &a的缩减运算结果是0,b = |a的缩减运算结果是1。对应到硬件电路上,其实就是多个输入的的与门和或门了,如下图所示。
在这里插入图片描述在这里插入图片描述

知识点3:位拼运算符

位拼运算主要用于将两个向量拼接起来合成一个新的向量。这里列举几种位拼运算符常用的用法,根据用法不同,实际对应的硬件电路也不同:

/**
 * 把两个向量拼合成一个向量
 */
 module test
 (
 	a,
 	b,
 	sda
 );

input [3:0] a;
input [3:0] b;
output reg [7:0] sda;

always sda = {a,b}; //将向量a置于高位,向量b置于低位,组成新的8位向量赋值给sda

endmodule 
/**
 * 交换一个16位向量的高8位与低8位(用来处理大小端问题?)
 */
 module test
 (
 	a,
 	sda
 );

input [16:0] a;
output reg [16:0] sda;

always sda = {a[7:0],a[15:8]}; //自己拼自己,只不过改变了高低字节的顺序

endmodule 
/**
 * 用位拼运算来做高位在前的并转串输出
 */
 module test
 (
 	a,
 	sda,
	lock,
	clk
 );

input wire [8:0] a;
input wire clk;
input wire lock;
output wire sda;

reg [7:0] lock_data;

assign sda = lock_data[0]; //sda网线信号固定输出lock_data[0]上的数据

always @ (posedge clk or negedge lock)
	begin
		if(!lock)
			begin
				//在rst_n的下降沿锁存a输入的数据到内部寄存器
				lock_data <= a; 
			end
			else //如果lock信号不为低,那么就是clk的上升沿触发了
			begin
				//将当前的高位数据通过位拼移动到低位,8个时钟后就完成了所有8个字节的输出,同时输出是高位在前
				lock_data <= {lock_data[6:0],lock_data[7]}; 
			end
	end

endmodule 

上面这段代码生成的电路由一系列D触发器组成,如下图所示,其实可以根据生成的RTL示图来推断下代码行为是否符合预期。
在这里插入图片描述

  1. 在lock信号的下降沿,a[7:0]的数据被加载到D触发器组(也就是lock_data[7:0]),这时触发器从左到右内容依次为([0]代表lock_data中的第0位,这样简化表示):[1]->[2]->[3]->[4]->[5]->[6]->[7]->[0]->sda,这时sda输出的值其实对应lock_data中的第0位。
  2. 当第一个时钟上升沿到来,所有D触发器被触发,将输入端D的数值输出到输出端Q,这时所有数据整体向右移动,同时最右边的D触发器的Q输出被移动到最左边,这时触发器从左到右内容依次为[0]->[1]->[2]->[3]->[4]->[5]->[6]->[7]->sda,这时sda输出的值其实对应lock_data中的第7位。
  3. 8个时钟上升沿后,sda依次从高位到低位输出了lock_data中的所有位,电路状态其实又返回了第1步的状态,即这时触发器从左到右内容依次为:[1]->[2]->[3]->[4]->[5]->[6]->[7]->[0]->sda,这时sda输出的值其实对应lock_data中的第0位。

知识点4:层次化设计

将功能模块化,一个Verilog文件负责实现一个小的功能模块,然后用一个顶层Verilog文件负责实例化这些功能模块,并把它们连接起来,关于这些其实如果画过电路图,体会会深点。层次化设计主要有以下几个好处:

  • 方便验证,分为一个个细分模块后,可以对每个模块分别验证,而且由于每个模块只负责某个特定功能,所有编写起testbench来更简单,也容易覆盖每种工作情况。确保每个模块不出问题后,再通过顶层Verilog文件组合起来,做整体验证。
  • 容易维护,需要变更某个功能时,只需要变更对应模块,改出了问题也只是变更的模块出问题,更容易定位解决。
  • 可以多人协同开发,只需要大家把模块之间的接口协商好,就可以多个人同时开始开发不同模块了,缩短工作周期。

感觉这种偏方法论的东西,只能多积累踩坑才能有更深的感悟。教材里说的也顶多初步建立层次化的认识,还是不指望教材了。

知识点5:generate语句

如果学过C语言的话,可以把generate语句理解成C语言中的预处理指令,generate语句其实是生成语句的语句,这么说可能有点绕,举个例子,比如看如下代码:

/*
 *
 * 对输入的字节进行逆序后输出
 *
 */

module for_test(lock, out, in);

input lock;
input [7:0] in;
output reg [7:0] out;

genvar i;

generate 
	for(i = 0; i < 8; i=i+1)
		begin : for_block
			always @ (posedge lock)
				begin
					out[i] <= in[7 - i];
				end
		end
endgenerate 

endmodule 

以上代码编译时会直接生成电路吗?当然不会,中间会有一个将generate语句展开的过程,上面例子中,用generate语句中的for语句生成了8个always语句块,展开之后如下所示:

/*
 *
 * 对输入的字节进行逆序后输出
 *
 */

module for_test(lock, out, in);

input lock;
input [7:0] in;
output reg [7:0] out;

always @ (posedge lock)
	begin
		out[0] <= in[7];
	end
	
always @ (posedge lock)
	begin
		out[1] <= in[6];
	end
	
always @ (posedge lock)
	begin
		out[2] <= in[5];
	end
	
always @ (posedge lock)
	begin
		out[3] <= in[4];
	end
	
always @ (posedge lock)
	begin
		out[4] <= in[3];
	end
	
always @ (posedge lock)
	begin
		out[5] <= in[2];
	end
	
always @ (posedge lock)
	begin
		out[6] <= in[1];
	end
	
always @ (posedge lock)
	begin
		out[7] <= in[0];
	end

endmodule 

然后再去生成硬件电路
在这里插入图片描述对generate相关的知识点,笔者总结了一个思维导图:
在这里插入图片描述

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值