FPGA设计心得(8)Verilog中的编译预处理语句

写在前面

相关博文
博客首页
注:学习交流使用!

正文

看稍微复杂一点的IP Core以及模块等 ,都会遇到大量的编译预处理语句,它和参数定义语句通常都是为了模块复用以及增强代码可读性等等。

`define 与localparam和parameter最大的区别就是它可以跨文件传递参数;parameter只能在模块间传递参数;而localparam只能在其所在的module中起作用,不能参与参数传递。

诸如以`开头的编译预处理语句,

如 `define等(你敢信,这个符号,我用markdown编辑不了,因为他也是markdown的一个语法符号!
它不是一般的语句,Verilog编译器会对其进行预处理,然后预处理的结果和源程序一起进行通常的编译处理。其作用范围从定义开始到文件结束。
下面谈几个常用的。

宏定义

格式:

`define 宏名 宏内容

如`define signal string
它的作用就是指定标识符signal替代字符串string。

有几点注意:

  • 在引用已定义的宏名时,必须在宏名前面加上符号`,表示该名字是一个经过宏定义的名字。
  • 宏定义是用一个宏名代替一个字符串,也就是做简单的置换,不做语法检查。预处理时照样带入,不论含义正确与否。只有编译已被宏展开后的源程序时才检查语法。
  • 宏定义不是Verilog语句,定义末尾一定不要加上分号,否则连带分号一起带入替换。
  • 宏定义后可以进行注释,注释语句不会被认为是宏内容。

例子:

// module name
`define CNT_MODULE_NAME vcnt

// counter type = [BINARY, GRAY, LFSR]
//`define CNT_TYPE_BINARY
`define CNT_TYPE_GRAY
//`define CNT_TYPE_LFSR

// q as output
`define CNT_Q
// for gray type counter optional binary output
`define CNT_Q_BIN

// up/down, forward/rewind
`define CNT_REW

// number of CNT bins
`define CNT_LENGTH 4

// async reset value
`define CNT_RESET_VALUE `CNT_LENGTH'h0

// clear
`define CNT_CLEAR

// set
`define CNT_SET
`define CNT_SET_VALUE `CNT_LENGTH'h9

// wrap around creates shorter cycle than maximum length
//`define CNT_WRAP
`define CNT_WRAP_VALUE `CNT_LENGTH'h9

// clock enable
`define CNT_CE

文件包含

文件包含命令可以节省设计人员的重复劳动,可以将一些常用的宏定义和任务组成一个文件,然后用文件包含命令`include将其包含到自己的源文件中,相当于工业上的标准元件拿来使用。

一图以蔽之:
在这里插入图片描述

举例:

`include "versatile_counter_defines.v"
`define LFSR_LENGTH `CNT_LENGTH
`include "lfsr_polynom.v"

条件编译

一般情况下,Verilog HDL源程序中所有的行都参加编译。但是有时候希望对其中的一部份内容只有在条件满足的时候才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足条件时对一组语句进行编译,当条件不满足时则对另外一组语句进行编译。

条件编译命令的几种形式:

1)`ifdef 宏名(标识符)

        程序段1

        `else

        程序段2

       `endif

它的作用是当宏名已经被定义过(此处需要采用`define命令定义),则对程序段1进行编译,程序段2将被忽略;否则编译程序段2,程序段1将被忽落。

其中`else部分可以没有,即:

(2)`ifdef宏名(标识符)

           程序段1

   `endif

这里的“宏名”是一个Verilog HDL 的标识符,“程序段”可以是Verilog HDL语句组,也可以是命令行。这些命令可以出现在源程序的任何地方。

例如:

////////////////////////////////
`ifdef SIMULATION
wire BitTick = 1'b1;  // output one bit per clock cycle
`else
wire BitTick;
BaudTickGen #(ClkFrequency, Baud) tickgen(.clk(clk), .enable(TxD_busy), .tick(BitTick));
`endif

注意:被忽略掉不进行编译的程序段部分也要符合Verilog HDL程序的语言规则。

通常在Verilog HDL程序中用到`ifdef、`else、`endif编译命令
的情况有以下几种:
 (1)选择一个模板的不同代表部分。

 (2)选择不同的时序或结构信息。

 (3)对不同的EDA工具,选择不同的激励。

最常用的情况是:Verilog HDL代码中的一部分可能适用于某个编译环境,但不使用于另一个环境,如果设计者不想为两个环境创建两个不同版本的Verilog 设计,还有一种方法就是所谓的条件编译,即设计者在代码中指定其中某一部分只有在设置了特定的标志后,这一段代码才能被编译,即设计者在代码中指定其中某一部分只有在设置了特定的标识后,这一段代码才能编译。

设计者也可能希望在程序的运行中,只有当设置了某个标志后,才能执行Verilog 设计的某些部分,这就是所谓的条件执行。

Verilog文件中,条件编译标志可以用`define语句设置。如果没有设置条件编译标志,那么Verilog编译器会简单地跳过该部分。

条件生成语句问题

我们都知道条件生成语句中的generate case,语句框架为:

generate
	case(const)
//...
	endcase
endgenerate

case条件必须为常量,这是必然的。
那么今天想知道的是,如果const是`define定义的宏行不行呢?
事实是可以的!

下面做一个简单测试:
设计文件:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: Reborn Lee
// Create Date: 2020/06/01 11:35:22
// Module Name: generate_case
// Additional Comments: used for test.
// 
//////////////////////////////////////////////////////////////////////////////////
// `define MCRO
`define SEL_1 1
module generate_case #(
parameter SEL = 1
	)(
	output [3:0] out

    );


`ifdef MCRO
    generate
    	case(SEL)
    	1: begin
    		assign out = 4'b0001;
    	end
    	2: begin
    		assign out = 4'b0010;
    	end
    	3: begin
    		assign out = 4'b0100;
    	end
    	default: begin
    		assign out = 4'b0000;
    	end
    	endcase
    	
    endgenerate
`else
	generate
    	case(`SEL_1)
    	1: begin
    		assign out = 4'b1000;
    	end
    	2: begin
    		assign out = 4'b0100;
    	end
    	3: begin
    		assign out = 4'b0010;
    	end
    	default: begin
    		assign out = 4'b0000;
    	end
    	endcase
    	
    endgenerate
	
`endif


endmodule

如果定义了宏MCRO,则会将parameter定义的参数代入,如果未定义,则会代入宏SEL_1;
测试文件:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: Reborn Lee
// Create Date: 2020/06/01 11:47:50
// Module Name: case_tb
// Revision 0.01 - File Created
// Additional Comments: Only for test
// 
//////////////////////////////////////////////////////////////////////////////////


module case_tb(

    );

wire [3:0]out;

generate_case #(
	.SEL(1)
	)t1(
	.out(out)
);

endmodule

仿真波形:
在这里插入图片描述
和测试符合。

回顾

在Opencores里面下载了一个多功能计数器IP核,可以实现二进制,格雷码,LFSR等,且位宽可选,本想研究下它是如何实现的,于是使用Vivado对其进行仿真。
但是语法就没通过,里面使用了大量的编译预处理语句,让人看得十分费劲!
我知道实现这么多功能,使用预编译语句是可以让代码 更加简洁,但是可读性呢?是否增强了,可能我还陌生。由于代码未通过,所以我决定自己写一个多功能计数器。对于预编译指令的使用,不求速会,但求先实现基本功能,在考虑如何组织框架,让模块更加通用!
最后想提出的是,在使用define定义的宏的时候,一定在前面加一个符号`,这是容易遗忘的。

参考资料

参考资料1
参考资料2


参考资料3:
IEEE Std 1800-2017
Verilog数字系统设计教程(第2版)
链接:https://pan.baidu.com/s/1ZTRl5AvFpe4m54AVaTtzSg
提取码:hkuc
nandland

交个朋友

FPGA/IC技术交流2020

猜你喜欢

转载自blog.csdn.net/Reborn_Lee/article/details/106464407