HDL4SE:软件工程师学习Verilog语言
Posted 饶先宏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HDL4SE:软件工程师学习Verilog语言相关的知识,希望对你有一定的参考价值。
2 词法和预处理器
2.1 定个小目标
作为一个软件工程师,学习一种语言,最暴力的办法就是做一个这种语言的编译器(或解释器),如果没有做过某种语言的编译器,至少也得仔细看过这种语言的编译器实现,最不济也得看过语言的语法规范吧,否则怎么好意思说熟悉并精通这种语言嘛。如果编译结果没有目标硬件平台的话,那就再做一个模拟器来运行它好了,能用软件解决的事情绝不去花钱买硬件的,谁让我们是软件工程师呢。另外,学习语言的一个比较好的办法是用这种语言编个合适的程序,否则没有目标一通乱学,到头来还是支离破碎,逐渐忘到九霄云外,这种效果还不如不学呢。
因此学习verilog语言之前,我们先定个小目标,分三个子目标:
1.完成一个verilog语言的编译器,目标平台中的基本单元包括32位以下的整数加法器、乘法器、除法器(求商和余数)、比较器以及一到多位的与、或、非运算逻辑运算单元,一到多位的寄存器,存储器,这些基本单元都很理想,所有组合逻辑都能够在一个时钟周期内完成,存储器读延时一个时钟周期给出数据。编译器能接受IEEE. 1364-2005的输入,但是只有RTL相关的语法结构生成语法树,并编译成目标平台的基本单元,其它行为级描述和开关级描述一概忽略,也忽略一些系统任务方面的功能。编译器支持多个文件输入,并能指定顶层模型,在多个文件中找到需要的模型连接在一起。编译器的输出是各个基本单元以及它们之间的连接关系,俗称网表,可以用内存中的数据结构表示,也可以导出到一个网表文件。
2.完成一个目标平台模拟器,能够提供单时钟和单复位的信号,并提供目标平台中存储器写到文件系统文件中或从文件系统中读取映像到存储器中的功能。模拟器能够接受一个网表文件(前面规定的目标平台),并从文件系统中读入存储器映像到存储器中,然后给出时钟和复位信号(要求每个模块的时钟和复位信号名称固定),进行逐周期模拟运行(这个与verilog能够描述到任意时间延时不一样,我们简化处理,模拟到周期即可,这也基本满足RTL的要求了。能够指定记录每个寄存器在每个周期锁定的值。
3.完成一个RISC-V的CPU核(32位基本配置款,简单点,毕竟是初学嘛),并设计一个与模拟器连接的简单的I/O设备,并能将它在模拟器上运行起来,执行公版编译工具链编译出来的代码。
这几个目标好像稍微有点大了啊,不过作为一个软件工程师,得随时暗示自己,不难,不难,应该很简单才对…。有开源项目的支持,确实没有想象那么难整。
2.2 词法
2.2.1 字母表
verilog语言能够接受得字母表是ASCII字母表,包括可打印字符,制表符,换行回车等。对其中换页,删除,退格等控制字符,没有明确定义,不过一般得文本文件里已经没有这些字母出现了。一般的编译器可以接受扩展ASCII码,这样可以支持汉字(GB-2312编码),好像没有支持UNICODE编码。
2.2.2 空白符号
制表符,空格符号、换行符和换页符在Verilog语言中一般只作为分割符号使用,大部分时间是被忽略的,有两个例外,一个是在字符串常量中,制表符和空格符作为字符串的一部分,不能忽略,另一个是单行注释以换行符结束,`define编译指示也以换行符号结束。
2.2.3 注释
Verilog的注释跟C++一样,支持/* */形式的多行注释,也支持//开头的单行注释。多行注释不能嵌套,注释中可以出现任何ASCII字符,一般的编译器都能接受汉字GB-2312码(ASCII扩展码)作为注释。
2.2.4 数字
Verilog中的数字可以表示1到多位二进制的数字,因此比c/c++中的数字更多。
Verilog中的一位信号,可以取值0, 1,x, z四个值,其中0和1是标准的布尔量,x表示该位数据值不明确,也表示不关心该位数据的取值,当然能实际的电路中还是有值的,但是Verilog程序中不关心它的值,此时用x表示。z表示高阻状态,这是电路中特有的一个状态,输出这个状态的电路其实表达的意思是输出被阻断,电路设计好后,不可能临时物理断开,总不能设计一个继电器在芯片中吧,此时如果需要逻辑上断开,可以采用一种称为三态门的基本单元来实现,此时这个输出被断开,不会影响到所连接的电路上的信号。典型的用法就是多个输出信号要连接到一条总线上,按照电路的规则,最多只能由一个输出源来给出总线信号,否则假如一个信号源给高电平,一个给低电平,总线上的信号就处于未知状态了。此时可以用z表示输出线的状态。
表示多位数据时,可以选择在数字前面加一个十进制数字表示这个数字的二进制位数,这种表达方式后面的数字必须指定数字的基数,十进制用’d表示,二进制用’b表示,八进制用’o表示,十六进制用’h表示,然后后面在跟这各个基数下的数字。数字中间可以用_分割。下面是几种表示方法:
659 :不带宽度也不带基数的十进制数常数
'h 837FF:不带宽度的十六进制数
'o7460:不带宽度的八进制数
'b0000_010_011:不带宽度的二进制数
'd34:不带宽度的十进制数
4af:这不是一个数字了,如果不带基数,默认为十进制数,因此这个表达被解析为两个符号,一个是十进制数4,一个是标识符af
10‘b000x_0Zz_X11:10位二进制数中间带x和z,其中x和z可以用大小写
7’d34:7位数,用十进制表示
7’dx:等价于7’bxxx_xxxx,用十进制表示的x或z,只能全部都是x或z,因为十进制到二进制不能对应了,所以7’d2x是非法的
11’h3aa:11位数,用十六进制表示
基数符号还可以带s标识,表示是一个带符号数,比如
4’shf,表示一个4位的带符号数(补码表示)跟-4‘d1等价
在计算过程中,长短不一的数字和变量参与计算时,短的数据扩展到长的一个参与计算,带符号数的扩展就是带符号扩展,不带符号的数扩展则前面加0。
Verilog中的实数表示按照IEEE Std 754-1985的规范来,支持普通的表示和科学计数法:
比如:
1.2
0.1
2394.26331
1.2E12
1.30e-2
0.1e-0
23E10
29E-2
236.123_763_e-12:其中的下划线直接忽略就是了
verilog规定小数点两边必须由数字,因此
.12
9.
4.E3
.2e-
都不是合法的浮点数。
浮点数如果自动转换成整数时,转换到离它最近的整数(绝对值四舍五入)。
2.2.5 运算符和特殊符号
在运算符方面,Verilog跟C/C++非常类似,有单目运算符,双目运算符,包括:
单目运算符有一个输入,包括 +, - ,!, ~ ,& , ~& , | , ~| , ^ , ~^ , ^~
前几个是通常的单目运算符,从&开始的几个单目运算符,是将后面的表达式中生成的值逐位进行单目运算符指定的运算,得到一位结果,比如&4’b0100,表示后面的四位数字与在一起,结果是1’b0。
其中+,-是算术运算符,!是逻辑反运算符,后面的是将运算符后面的如干个位用运算符计算的结果。具体看后面的表达式说明。
双目运算符有两个输入,包括:+, - , * , / , % , == , != ,=== , !== ,< ,<= , > , >= , & , | , ^ , ^~ , ~^ , >> , << , >>> , <<< , && , || ,** 。其中=和<=还作为赋值符号使用,具体的用法见后面的表达式说明。
还有其他的特殊符号包括{},@#$`等符号,在后面的语法结构中使用。
2.2.6 字符串
verilog的字符串常量与c/c++中一样,用""括起来,如果字符串中要出现"字符,则用\\"方式表达。字符串在赋值时被解释位多个8位的数据连接在一起,一般不会这样使用。字符串中可以出现\\开始的转义字符,\\n表示回车符,\\r表示换行,\\\\表示\\字符,\\"表示"字符,\\ddd是三个八进制数表示的ASCII扩展码,最大不能超过\\377。
2.2.7 标识符、关键字与系统任务符号
verilog的标识符分为简单标识符和ESC标识符两种,简单标识符由字母和下划线开头,中间可以出现字母,下划线,数字和$,比如:
shiftreg_a
busa_index
error_condition
merge_ab
_bus3
n$657
都是合法的简单标识符。
ESC标识符由\\开始后面可以是任何的可打印字符,即ASCII码中33-126中的字符,ESC标识符以空白符结束(见前面的2.2.2)。比如:
\\busa+index
\\-clock
\\***error-condition***
\\net1/\\net2
\\{a,b}
\\a*(b+c
都是合法的ESC标识符,如果ESC标识符后面的字符串是一个简单标识符,该标识符就被认为与简单标识符一样。比如
\\_bus3与简单标识符_bus3一样。
标识符是大小写区分的。
下面的符号是verilog的关键字,不能够作为标识符使用(ESC标识符如果与关键字一样,可以作为标识符使用,这够乱套的了啊):
always,and,assign,automatic,begin,buf,bufif0,bufif1,case,casex,casez,cell,cmos,config,deassign,default,defparam,design,disable,edge,else,end,endcase,endconfig,endfunction,endgenerate,endmodule,endprimitive,endspecify,endtable,endtask,event,for,force,forever,fork,function,generate,genvar,highz0,highz1,if,ifnone,incdir,include,initial,inout,input,instance,integer,join,large,liblist,library,localparam,macromodule,medium,module,nand,negedge,nmos,nor,noshowcancelled,not,notif0,notif1,or,output,parameter,pmos,posedge,primitive,pull0,pull1,pulldown ,pullup,pulsestyle_onevent,pulsestyle_ondetect,rcmos,real,realtime,reg,release,repeat,rnmos,rpmos,rtran,rtranif0,rtranif1,scalared,showcancelled,signed,,small,specify,specparam,strong0,strong1,supply0,supply1,table,task,time,tran,tranif0,tranif1,tri,tri0,tri1,triand,trior,trireg,unsigned,use,uwire,vectored,wait,wand,weak0,weak1,while,wire,wor,xnor,xor
verilog还规定了系统任务和系统函数的名称,都是以$开始的简单标识符。这些任务和函数几乎都是为模拟服务的,不是RTL描述中内容,这里就不多介绍了,我们的编译器和模拟器也不生成和执行其中的任何功能
2.2.8 编译指示
verilog跟c/c++一样也有编译指示,不过在verilog中#符号有特别用途,因此编译指示符号不是以#开始,而是符号`开始,这个符号是在键盘左上角跟~同一键的那个,我不知道怎么读了,反单引号?编译指示包括:
`celldefine `endcelldefine :是为PLI使用的,我们不理它。
`default_nettype:这是定义默认的线网类型的,我们只支持一种线网类型,或者说根本不支持线网功能,因此不理他就是
`define :定义一个宏,后面可以跟个标识符,形式参表以及宏的内容。与c/c++不同的是,在verilog程序中要使用这个宏,得用`宏名称[(实参表)]得格式使用,在c/c++中是直接使用宏名称即可。
比如`define MSIZE 5
`MSIZE’d4等价于5’d4
`undef :取消宏的定义,后面跟要取消的宏标识符
`else :与ifdef和ifndef配套使用
`elsif:相当于一个endif后满紧跟着一个ifdef
`endif :结束一个ifdef/ifndef
`ifdef:跟一个宏的名称,如果该宏定义过,则后面的代码有效,配套的else之后的代码无效,直到配套的endif为止。
`ifndef :跟一个宏的名称,功能与ifdef类似,只不过宏是否定义刚好反着。
`include:与c/c++中的#include一样,将一个文件读入到当前位置继续编译,注意读入时优先查找当前目录开始的相对目录,如果没有找到,则在编译器指定的include目录序列中逐个查找,如果都没有找到,则报错。
`resetall:初始化所有的的编译指示设置,注意到编译指示是跨文件的,因此用这个指示可以消除一些可能存在的来自于其他文件的设置。
`line:为编译器指定源文件的文件名和行号以及包括级别,如果正在编译的verilog源文件是根据其他源文件生成的,可以用这个编译指示指出原始的源文件位置。
`timescale:指示verilog中的时间表述的精度和单位。verilog中的延时等时间描述是不带单位和精度的,用这个指示可以给出时间描述的单位和精度,比如`timescale 10 ns / 1 ns指定时间单位是10纳秒,精度是1纳秒,那么#1.55给出的时间延时是15.5纳秒,按照精度舍入到16纳秒。
`nounconnected_drive ,`unconnected_drive:设置没有连接的端口的默认连接,这里不理它。
`pragma:编译器参数,这个跟具体的编译器相关,这里不理它
`begin_keywords `end_keywords:用来指定这两个编译指示之间的verilog的关键字版本号,可以是 “1364-1995”,“1364-2001”,“1364-2001-noconfig”,“1364-2005”,这里用来兼容前面的版本,不同版本的关键字表不同,可能前面版本中可以用作普通标识符的符号到高版本中就变成关键字了,这样前面版本中写的代码到高版本中就编译不过了,这里指定使用指定版本号的关键字表,可以解决这个问题。我们的编译器也不处理这个问题,我们默认就是1364-2005好了。
编译指示的处理办法是设计一个预处理器,由预处理器来处理所有的编译指示,处理完的代码(宏展开,条件指示摘取相应的有效代码,include读入指定的文件,摘除所有的注释)再送到verilog的主编译器中编译,这样可以减少主编译器中词法和语法分析的难度。
这里的办法是:预处理器用c语言写,主编译器中的词法和语法用yacc方式来描述。
2.3 预处理器
我们用LCOM来定义通用预处理的接口,LCOM相关的内容,请参见LCOM:轻量级组件对象模型。下面是通用预处理器的接口定义:
DEFINE_GUID(IID_PREPROCESS, 0x38f9f002, 0xa71f, 0x4b40, 0xae, 0x8a, 0xfd, 0xc9, 0xb6, 0xdf, 0x6c, 0xfc);
enum PreActionOP {
PA_DEFINE,
PA_UNDEF,
PA_IFDEF,
PA_IFNDEF,
PA_IF,
PA_ELSEIFDEF,
PA_ELSEIF,
PA_ELSE,
PA_ENDIF,
PA_INCLUDE,
PA_MACRO,
PA_NETTYPE,
};
typedef struct sIPreprocess {
OBJECT_INTERFACE
int (*SymbolEmitEnabled)(HOBJECT object);
int (*SetFile)(HOBJECT object, const char * filename, int reset);
int (*AddIncludePath)(HOBJECT object, const char * filename);
int (*GetCurrentFile)(HOBJECT object, char *filename, int buflen, int *lineno, int *linepos, int *filepos);
int (*SetParam)(HOBJECT object, const char * paramname, char *paramvalue);
int (*GetParam)(HOBJECT object, const char * param, char * value, int buflen);
int (*GetText)(HOBJECT object, char * buf, int maxsize);
int (*GetLog)(HOBJECT object, char * value, int vlen);
int (*PreAction)(HOBJECT object, int action, const char * macro, const char * value);
}IPreprocess;
#define PREPROCESS_VARDECLARE
#define PREPROCESS_VARINIT(_objptr, _sid)
#define PREPROCESS_FUNCDECLARE(_obj, _clsid, _localstruct) \\
static int _obj##_preprocess_SymbolEmitEnabled(HOBJECT object); \\
static int _obj##_preprocess_SetFile(HOBJECT object, const char * filename, int reset); \\
static int _obj##_preprocess_AddIncludePath(HOBJECT object, const char * filename); \\
static int _obj##_preprocess_GetCurrentFile(HOBJECT object, char *filename, int buflen, int *lineno, int *linepos, int *filepos); \\
static int _obj##_preprocess_SetParam(HOBJECT object, const char * paramname, const char *paramvalue); \\
static int _obj##_preprocess_GetParam(HOBJECT object, const char * param, char * value, int buflen); \\
static int _obj##_preprocess_GetText(HOBJECT object, char * buf, int maxsize); \\
static int _obj##_preprocess_GetLog(HOBJECT object, char * value, int vlen); \\
static int _obj##_preprocess_PreAction(HOBJECT object, int action, const char * macro, const char * value); \\
static const IPreprocess _obj##_preprocess_interface = { \\
INTERFACE_HEADER(_obj, IPreprocess, _localstruct) \\
_obj##_preprocess_SymbolEmitEnabled, \\
_obj##_preprocess_SetFile, \\
_obj##_preprocess_AddIncludePath, \\
_obj##_preprocess_GetCurrentFile, \\
_obj##_preprocess_SetParam, \\
_obj##_preprocess_GetParam, \\
_obj##_preprocess_GetText, \\
_obj##_preprocess_GetLog, \\
_obj##_preprocess_PreAction, \\
};
该接口中:SymbolEmitEnabled用来指示目前是否处于编译指示的无效区域,无效区域得到的单词,是直接丢弃,不返回到语法分析中的,SetFile来指定预处理主文件,AddIncludePath来指定包含文件目录,GetCurrentFile来获取当前的文件名称和位置,GetText来得到预处理后的源代码,GetLog得到预处理过程中报的错误信息,PreAction来指示预处理器执行相应的预处理动作,可以支持外部词法分析器协同处理预处理器动作。如果是预处理内部处理所有动作,这个就不需要这个接口了。
有了这个接口,我们可以实现verilog或者c/c++的预处理模块,用来支持编译器的预处理功能。
2.4 词法分析器
我们用yacc的格式给出词法分析器,用flex工具来生成词法分析源代码。bison/flex在win64下的工具可以从我上传的资源下载。在64位windows下的bison 3.7和flex 2.6.4
这里先给出verilog 1364-2005的词法描述,该文件最初来自开源项目verilog-parser,该项目是1364-2001规范的,这里根据1364-2005进行了调整。并纳入了预处理器的处理模式。
%{
#include "verilog_parser.tab.h"
#include "object.h"
#include "preprocess.h"
#include "verilog_preprocess.h"
static IPreprocess ** preprocess = NULL;
static char * __macroname = NULL;
int SetPreProcess(HOBJECT object)
{
return objectQueryInterface(object, IID_PREPROCESS, (const void **)&preprocess);
}
extern int verilog_find_reserved_word(const char * ident);
#define YY_NO_UNISTD_H
#define YY_INPUT(buf, result, max_size) \\
{ \\
result = objectCall2(preprocess, GetText, buf, max_size); \\
}
#define EMIT_TOKEN(x) if (objectCall0(preprocess, SymbolEmitEnabled)) { return x; }
%}
%option outfile="verilog_scanner.c"
%option yylineno
%option nodefault
%option noyywrap
/* Single character tokens */
NEWLINE "\\n"|"\\r\\n"
SPACE " "
TAB "\\t"
AT "@"
COMMA ","
HASH "#"
DOT "."
EQ "="
COLON ":"
IDX_PRT_SEL "+:"|"-:"
SEMICOLON ";"
OPEN_BRACKET "\\("
CLOSE_BRACKET "\\)"
OPEN_SQ_BRACKET "\\["
CLOSE_SQ_BRACKET "\\]"
OPEN_SQ_BRACE "{"
CLOSE_SQ_BRACE "}"
/* Tokens related to numbers */
EXP "e"|"E"
UNDERSCORE "_"
SIGN {PLUS}|{MINUS}
X "x"|"X"
Z "z"|"Z"|"?"
DIGIT_DECIMAL [0-9]
DIGIT_DECMIAL_NZ [1-9]
DIGIT_BINARY [0-1]|{X}|{Z}
DIGIT_OCTAL [0-7]|{X}|{Z}
DIGIT_HEX [0-9a-fA-F]|{X}|{Z}
DIGIT_DEC [0-9]|{X}|{Z}
BASE_DECIMAL '[sS]?[dD]
BASE_BINARY '[sS]?[bB]
BASE_OCTAL '[sS]?[oO]
BASE_HEX '[sS]?[hH]
NUM_REAL_EXP {NUM_UNSIGNED}({DOT}{NUM_UNSIGNED})?{EXP}({SIGN})?{NUM_UNSIGNED}
BIN_VALUE {DIGIT_BINARY}({UNDERSCORE}|{DIGIT_BINARY})*
OCT_VALUE {DIGIT_OCTAL}({UNDERSCORE}|{DIGIT_OCTAL})*
HEX_VALUE {DIGIT_HEX}({UNDERSCORE}|{DIGIT_HEX})*
DEC_VALUE {DIGIT_DEC}({UNDERSCORE}|{DIGIT_DEC})*
%x in_dec_val
%x in_hex_val
%x in_oct_val
%x in_bin_val
%s in_number
NUM_REAL {NUM_UNSIGNED}{DOT}{NUM_UNSIGNED}|{NUM_REAL_EXP}
NUM_UNSIGNED {DIGIT_DECIMAL}({UNDERSCORE}|{DIGIT_DECIMAL})*
/* Identifiers */
SYSTEM_ID \\$[a-zA-Z0-9_\\$]+
SIMPLE_ID [a-zA-Z_][a-zA-Z0-9_$]*
ESCAPED_ID \\\\{SIMPLE_ID}
/* Attributes */
ATTRIBUTE_START \\(\\*
ATTRIBUTE_END \\*\\)
/* Strings */
STRING \\".*\\"
/* Operators */
STAR "\\*"
PLUS "+"
MINUS "-"
ASL "<<<"
ASR ">>>"
LSL "<<"
LSR ">>"
DIV "/"
POW "**"
MOD "%"
GTE ">="
LTE "<="
GT ">"
LT "<"
L_NEG "!"
L_AND "&&"
L_OR "||"
C_EQ "==="
L_EQ "=="
C_NEQ "!=="
L_NEQ "!="
B_NEG "~"
B_AND "&"
B_OR "|"
B_XOR "^"
B_EQU "^~"|"~^"
B_NAND "~&"
B_NOR "~|"
TERNARY "?"
%%
{ATTRIBUTE_START} {EMIT_TOKEN(ATTRIBUTE_START);}
{ATTRIBUTE_END} {EMIT_TOKEN(ATTRIBUTE_END);}
{AT} {EMIT_TOKEN(AT);}
{COMMA} {EMIT_TOKEN(COMMA);}
{HASH} {EMIT_TOKEN(HASH);}
{DOT} {EMIT_TOKEN(DOT);}
{EQ} {yylval.operator = OPERATOR_L_EQ; EMIT_TOKEN(EQ);}
{COLON} {EMIT_TOKEN(COLON);}
{IDX_PRT_SEL} {EMIT_TOKEN(IDX_PRT_SEL);}
{SEMICOLON} {EMIT_TOKEN(SEMICOLON);}
{OPEN_BRACKET} {EMIT_TOKEN(OPEN_BRACKET);}
{CLOSE_BRACKET} {EMIT_TOKEN(CLOSE_BRACKET);}
{OPEN_SQ_BRACKET} {EMIT_TOKEN(OPEN_SQ_BRACKET);}
{CLOSE_SQ_BRACKET} {EMIT_TOKEN(CLOSE_SQ_BRACKET);}
{OPEN_SQ_BRACE} {EMIT_TOKEN(OPEN_SQ_BRACE);}
{CLOSE_SQ_BRACE} {EMIT_TOKEN(CLOSE_SQ_BRACE);}
{STAR} { yylval.operator=OPERATOR_STAR ; EMIT_TOKEN(STAR);}
{PLUS} { yylval.operator=OPERATOR_PLUS ; EMIT_TOKEN(PLUS);}
{MINUS} { yylval.operator=OPERATOR_MINUS ; EMIT_TOKEN(MINUS);}
{ASL} { yylval.operator=OPERATOR_ASL ; EMIT_TOKEN(ASL);}
{ASR} { yylval.operator=OPERATOR_ASR ; EMIT_TOKEN(ASR);}
{LSL} { yylval.operator=OPERATOR_LSL ; EMIT_TOKEN(LSL);}
{LSR} { yylval.operator=OPERATOR_LSR ; EMIT_TOKEN(LSR);}
{DIV} { yylval.operator=OPERATOR_DIV ; EMIT_TOKEN(DIV);}
{POW} { yylval.operator=OPERATOR_POW ; EMIT_TOKEN(POW);}
{MOD} { yylval.operator=OPERATOR_MOD ; EMIT_TOKEN(MOD);}
{GTE} { yylval.operator=OPERATOR_GTE ; EMIT_TOKEN(GTE);}
{LTE} { yylval.operator=OPERATOR_LTE ; EMIT_TOKEN(LTE);}
{GT} { yylval.operator=OPERATOR_GT ; EMIT_TOKEN(GT);}
{LT} { yylval.operator=OPERATOR_LT ; EMIT_TOKEN(LT);}
{L_NEG} { yylval.operator=OPERATOR_L_NEG ; EMIT_TOKEN(L_NEG);}
{L_AND} { yylval.operator=OPERATOR_L_AND ; EMIT_TOKEN(L_AND);}
{L_OR} { yylval.operator=OPERATOR_L_OR ; EMIT_TOKEN(L_OR);}
{C_EQ} { yylval.operator=OPERATOR_C_EQ ; EMIT_TOKEN(C_EQ);}
{L_EQ} { yylval.operator=OPERATOR_L_EQ ; EMIT_TOKEN(L_EQ);}
{C_NEQ} { yylval.operator=OPERATOR_C_NEQ ; EMIT_TOKEN(C_NEQ);}
{L_NEQ} { yylval.operator=OPERATOR_L_NEQ ; EMIT_TOKEN(L_NEQ);}
{B_NEG} { yylval.operator=OPERATOR_B_NEG ; EMIT_TOKEN(B_NEG);}
{B_AND} { yylval.operator=OPERATOR_B_AND ; EMIT_TOKEN(B_AND);}
{B_OR} { yylval.operator=OPERATOR_B_OR ; EMIT_TOKEN(B_OR);}
{B_XOR} { yylval.operator=OPERATOR_B_XOR ; EMIT_TOKEN(B_XOR);}
{B_EQU} { yylval.operator=OPERATOR_B_EQU ; EMIT_TOKEN(B_EQU);}
{B_NAND} { yylval.operator=OPERATOR_B_NAND ; EMIT_TOKEN(B_NAND);}
{以上是关于HDL4SE:软件工程师学习Verilog语言的主要内容,如果未能解决你的问题,请参考以下文章