iverilog -- ivlpp 词法与语法分析解读

ivlpp是一个独立的程序,主要完成文件的预处理,包括 `define和 `include 情形

首先看一下 词法分析,源文件在ivlpp/lexor.lex

第一部分(第一行到%%部分)是说明部分,首先是用%{ ... ... %}包含的部分,这里的内容是C 语言程序,在使用flex编译后会原样的搬到lexor.c中,这部分暂时先跳过

在说明部分的第二部分是对起始状态,排他状态、词法的声明

%option stack
%option nounput
%option noinput
%option noyy_top_state
%option noyywrap

%x PPINCLUDE
%x DEF_NAME
%x DEF_ARG
%x DEF_SEP
%x DEF_TXT
%x MA_START
%x MA_ADD
%x CCOMMENT
%x IFCCOMMENT
%x PCOMENT
%x CSTRING
%x ERROR_LINE

%x IFDEF_FALSE
%s IFDEF_TRUE
%x IFDEF_SUPR

W        [ \t\b\f]+

/* The grouping parentheses are necessary for compatibility with
 * older versions of flex (at least 2.5.31); they are supposed to
 * be implied, according to the flex manual.
 */
keywords (include|define|undef|ifdef|ifndef|else|elseif|endif)

其中 %s 标志的是起始状态,%x是排他状态

W [ \t\b\f]+ 匹配的是空格,制表符、退格符、换页符

keywords 匹配 宏关键字和include关键字


进入第二部分

"//"[^\r\n]* { ECHO; }

 /* detect multiline, c-style comments, passing them directly to the
  * output. This is necessary to allow for ignoring directives that
  * are included within the comments.
  */

"/*"              { comment_enter = YY_START; BEGIN(CCOMMENT); ECHO; }
[^\r\n] { ECHO; }
\n\r    |
\r\n    |
\n      |
\r      { istack->lineno += 1; fputc('\n', yyout); }
"*/"    { BEGIN(comment_enter); ECHO; }

首先处理代码中的注释,注释分为单行注释和多行注释

第一行的语法用于匹配单行注释,对于//后的除换行外的字符均匹配,ECHO; 表示直接输出

后几行用于匹配块注释,当识别到/*时进入排他态的CCOMMENT,当进入排他态时,第二部分的匹配语法,只有前面的<>中的状态与当前状态相同的词法是激活的,如此例中,进入CCOMENT状态后,只有后6行的词法是激活的

在CCOMENT状态中,对除*/以外的字符,普通字符直接输出,换行字符讲lineno(行数计数器) + 1,当遇到*/时,进入comment_enter状态,而此时的comment_enter是 /*时被赋值的YY_START,即初始状态

 /* Detect and pass multiline pragma comments. As with C-style
  * comments, pragma comments are passed through, and preprocessor
  * directives contained within are ignored. Contains macros are
  * expanded, however.
  */
"(*"{W}?")"      { ECHO; }
"(*"             { pragma_enter = YY_START; BEGIN(PCOMENT); ECHO; }
[^\r\n] { ECHO; }
\n\r    |
\r\n    |
\n      |
\r      { istack->lineno += 1; fputc('\n', yyout); }
"*)"    { BEGIN(pragma_enter); ECHO; }

`{keywords} {
    emit_pathline(istack);
    error_count += 1;
    fprintf(stderr, "error: macro names cannot be directive keywords "
            "('%s'); replaced with nothing.\n", yytext);
}

`[a-zA-Z][a-zA-Z0-9_$]* {
    if (macro_needs_args(yytext+1)) yy_push_state(MA_START); else do_expand(0);
}

这部分用于处理形如:

 (* ivl_synthesis_on *)
 (* ivl_synthesis_off *) 的词语

当遇到(*时进入PCOMMENT 状态,这也是一个排他态,可能会想到always@(*)中也有(*,的形式,但lex是采用最长匹配、只匹配一次的原则,因此(*)模式在"(*"{W}?")"中已经得到了匹配与输出

在PCOMMENT状态中,对于不以`开头的词语,直接进行输出,并在换行的过程中对lineno进行加一计数,当遇到*)时,转回初始状态

在PCOMMENT状态中遇到`开头的词语,若该词是keywords,则识别为错误,emit_pathline用于输出istack中记录的行信息,然后使用fprintf输出错误提示

例如在文件中存在

(*
`include
 ivl_synthesis_off *)

此时的编译输出为:

ivltests/dffsynth7.v:18: error: macro names cannot be directive keywords ('`include'); replaced with nothing.

可以看出ivltests/dffsynth7.v:18 指示了文件名和行数,这部分是emit_pathline函数完成的功能

最后一种匹配的词法是以`开头的除keywords外的词语,这里相当于是对宏定义的引用。将该词传入macro_needs_args()函数中

/*
 * When a macro is instantiated in the source, macro_needs_args() is
 * used to look up the name and return whether it is a macro that
 * takes arguments. A pointer to the macro descriptor is stored in
 * cur_macro so that do_expand() doesn't need to look it up again.
 */
static struct define_t* cur_macro = 0;

static int macro_needs_args(const char*text)
{
    cur_macro = def_lookup(text);

    if (cur_macro) {
        return (cur_macro->argc > 1);
    } else {
        emit_pathline(istack);
        fprintf(stderr, "warning: macro %s undefined (and assumed null) at this point.\n", text);
        return 0;
    }
}

从函数中可以看出,其处理方式是在宏定义表中查找该词是否已经出现过,若出现过则返回值为cur_macro->argc > 1,否则输出未定义的报错信息,若该宏在宏定义表中存在,则进入MA_START状态,需要注意,这里用的是yy_push_state()函数,在lexor.c中可以看出该函数实现了对于状态码的一个堆栈,通过push 和pop来完成状态的暂存与还原,若该词不在宏定义表中, 则此时执行do_expand(0),实际是不进行任何处理的.

/* Strings do not contain preprocessor directives, but can expand
  * macros. If that happens, they get expanded in the context of the
  * string.
  */
\"            { string_enter = YY_START; BEGIN(CSTRING); ECHO; }
\\\\ |
\\\" |
\\`  { ECHO; }
\r\n |
\n\r |
\n   |
\r   { fputc('\n', yyout); }
\"   { BEGIN(string_enter);  ECHO; }
.    { ECHO; }

接下来是识别"STRING"类型的语句的状态机,当见到"时,进入CSTRING状态,对于\\\\,每对\\表示对\的转义,即回归字符本身,进而表示匹配字符串中的"\\",这在引号包含的文件中,在文件的语句层面依旧表示对\的转义,因此直接输出即可,\\\"同理,匹配的是文件中的\",而文件中的\"是对"的转义,因此不是与左引号对应的右引号,因此只进行输出即可,对于换行符,使用\n替换,对于其他字符,直接输出即可,当遇到\",即文件中的"时,意味着找到了右引号,结束CSTRING状态,回归原状态

/* This set of patterns matches the include directive and the name
  * that follows it. when the directive ends, the do_include function
  * performs the include operation.
  */
^{W}?`include { yy_push_state(PPINCLUDE); }

`{keywords} {
    emit_pathline(istack);
    error_count += 1;
    fprintf(stderr, "error: macro names cannot be directive keywords "
            "('%s'); replaced with nothing.\n", yytext);
}

`[a-zA-Z][a-zA-Z0-9_]* {
    if (macro_needs_args(yytext+1)) yy_push_state(MA_START); else do_expand(0);
}

\"[^\"]*\" { include_filename(); }

[ \t\b\f] { ; }

  /* Catch single-line comments that share the line with an include
   * directive. And while I'm at it, I might as well preserve the
   * comment in the output stream. This will be printed after the
   * file has been included.
   */
"//"[^\r\n]* { standby->comment = strdup(yytext); }

 /* These finish the include directive (EOF or EOL) so I revert the
  * lexor state and execute the inclusion.
  */

 /* There is a bug in flex <= 2.5.34 that prevents the continued action '|'
  * from working properly when the final action is associated with <>.
  * Therefore, the action is repeated. */

\r\n    |
\n\r    |
\n      |
\r      { istack->lineno += 1; yy_pop_state(); do_include(); }
<> { istack->lineno += 1; yy_pop_state(); do_include(); }

 /* Anything that is not matched by the above is an error of some
  * sort. Print an error message and absorb the rest of the line.
  */
. {
    emit_pathline(istack);
    fprintf(stderr, "error: malformed `include directive. Did you quote the file name?\n");
    error_count += 1;
    BEGIN(ERROR_LINE);
}

接下来的这部分是处理include语句的,因为include语句经常与宏定义配合使用,进而可以达到在编译时,使用不同的宏来控制系统的效果,因此会出现状态穿插的问题,因此第一行,匹配到`include时,不是用BEGIN来进入某状态,而是采用了yy_push_state()的方式进入PPINCLUDE状态,在处理include语句中,是不会碰到宏定义控制链套娃以及include套娃的情况,因此在PPINCLUDE状态中,若再碰到keywords就一定是有问题,但类似于

`define STD "stdio.v"
`include STD

这种写法是合法的,因此在PPINCLUDE状态中遇到宏定义,跟上一个PCOMMENT一样,查宏定义表,有就返回指针,没有就报错.

\"[^\"]*\" 用于匹配双引号中间的文字,匹配后调用include_filename()函数,

static void include_filename(void)
{
    if(standby) {
        emit_pathline(istack);
        fprintf(stderr,
                "error: malformed `include directive. Extra junk on line?\n");
        exit(1);
    }

    standby = malloc(sizeof(struct include_stack_t));
    standby->path = strdup(yytext+1);
    standby->path[strlen(standby->path)-1] = 0;
    standby->lineno = 0;
    standby->comment = NULL;
}

include_filename是将刚刚匹配的yytext放入了standby结构体中保存,此时的comment是NULL

遇到制表符空格等字符直接跳过,若在include后面有对包含的文件的注释,则将匹配//[^\r\n]*规则,并将注释的内容赋值给standby->comment

对于各种换行,对lineno + 1,然后开始解析include的文件路径,执行do_include()函数

static void do_include(void)
{
    /* standby is defined by include_filename() */
    if (standby->path[0] == '/') {
	if ((standby->file = fopen(standby->path, "r"))) {
	    standby->file_close = fclose;
            goto code_that_switches_buffers;
	}
    } else {
        unsigned idx, start = 1;
        char path[4096];
        char *cp;
        struct include_stack_t* isp;

        /* Add the current path to the start of the include_dir list. */
        isp = istack;
        while(isp && (isp->path == NULL)) isp = isp->next;

        assert(isp);

	strcpy(path, isp->path);
	cp = strrchr(path, '/');

        /* I may need the relative path for a planned warning even when
         * we are not in relative mode, so for now keep it around. */
        if (cp != 0) {
            *cp = '\0';
            include_dir[0] = strdup(path);
            if (relative_include) start = 0;
        }

        for (idx = start ;  idx < include_cnt ;  idx += 1) {
            sprintf(path, "%s/%s", include_dir[idx], standby->path);

            if ((standby->file = fopen(path, "r"))) {
		standby->file_close = fclose;
                /* Free the original path before we overwrite it. */
                free(standby->path);
                standby->path = strdup(path);
                goto code_that_switches_buffers;
            }
        }
    }

    emit_pathline(istack);
    fprintf(stderr, "Include file %s not found\n", standby->path);
    exit(1);

code_that_switches_buffers:

    /* Clear the current files path from the search list. */
    free(include_dir[0]);
    include_dir[0] = 0;

    if (depend_file) {
        if (dep_mode == 'p') {
            fprintf(depend_file, "I %s\n", standby->path);
        } else if (dep_mode != 'm') {
            fprintf(depend_file, "%s\n", standby->path);
        }
    }

    if (line_direct_flag) {
        fprintf(yyout, "\n`line 1 \"%s\" 1\n", standby->path);
    }

    standby->next = istack;
    standby->stringify_flag = 0;

    istack->yybs = YY_CURRENT_BUFFER;
    istack = standby;

    standby = 0;

    yy_switch_to_buffer(yy_create_buffer(istack->file, YY_BUF_SIZE));
}

这个函数针对standby->path,若路径是从根路径开始写的,则尝试打开他,若文件可以读取,则用goto 跳转到code_that_Switches_buffers,若不存在则报错,若路径不是从根目录开始写的,则将此path中从右往左找的第一个\作为一个搜索路径0,然后在一个循环中将include_path与standby->path进行匹配,并尝试打开,若打开成功,则将目前的istack接在standby的后面,构成一个链表,并将standby作为新的文件读取的结构体istack,开始解读该include文件,当解读完成后读取istack->next,若没有套娃逻辑,则此时就返回到分析include后的位置继续分析,进而实现了include的实际功能:将包含的文件合并为一个文件

如果上述匹配均没有奏效,则进入错误处理,最后一个词法匹配就是对应与错误处理

接下来是define语句了

define有两种用法,一种是对某个宏进行声明,如`define INIT 0 另一种则是宏函数 如 `define MAX(a,b)  ((a)<(b) ? (b) : (a)),这两种词法的区别在于括号的使用,因此ivlpp采取的方式是:

  /* Detect the define directive, and match the name. If followed by a
   * '(', collect the formal arguments. Consume any white space, then
   * go into DEF_TXT mode and collect the defined value.
   */
`define{W} { yy_push_state(DEF_NAME); }

{keywords}{W}? {
    emit_pathline(istack);
    error_count += 1;
    BEGIN(ERROR_LINE);
    fprintf(stderr, "error: malformed `define directive: macro names "
            "cannot be directive keywords\n");
}

[a-zA-Z_][a-zA-Z0-9_$]*"("{W}? { BEGIN(DEF_ARG); def_start(); }
[a-zA-Z_][a-zA-Z0-9_$]*{W}?    { BEGIN(DEF_TXT); def_start(); }

 遇到`define关键词时,使用yy_push_state()函数进入DEF_NAME状态,在此状态中,若还遇到其他关键词如`define ifdef ifndef,这种属于语法错误,直接进行错误输出.否则,对于`define后面的词语,若在空格等分割符前,没有遇到左括号,进入DEF_ARG状态,否则进入DEF_TXT状态,def_start()将宏名字添加到def_tree中

先看简单一些的DEF_TXT状态

.*[^\r\n] { do_define(); }

(\n|"\r\n"|"\n\r"|\r) {
    if (def_is_done()) {
        def_finish();
        yy_pop_state();
    } else {
	def_continue();
    }

    istack->lineno += 1;
    fputc('\n', yyout);
}

 /* If the define is terminated by an EOF, then finish the define
  * whether there was a continuation or not.
  */
<> {
    def_finish();

    istack->lineno += 1;
    fputc('\n', yyout);

    yy_pop_state();

    if (!load_next_input())
        yyterminate();
}

对于宏定义的后半部分,匹配除换行外的所有字符,执行do_define函数,在do_define函数中,对于宏定义中存在注释的情况进行了剔除,例如`define MAX_INT  8'hff//8'b1111_1111 `define MAX_INT 8'hff/*8'b1111_1111*/-1都可以进行识别与注释的删除.在识别宏定义时,从尾部开始扫描,将尾部的空格等内容删除掉,然后检索是否有\,这在宏定义中表示强制换行,但实际是要连贯处理的,此时将define_continue_flag至为1以保存此记录,然后在去掉此强制换行符的字符串中去掉末尾的空格后添加一个\n,以保留相同的换行结构,后续的文字需在后续的匹配中进行.

识别完本行的宏定义后,当遇到换行符时,查看define_continue_flag的数值,若为0表示宏定义已结束,执行def_finish(),否则执行def_continue()

def_finish()主要完成一些变量的状态还原,并将此此识别的宏定义插入到def_table(这是一个根据name的字符序组织的二叉树结构)中.

def_continue()是将define_continue_flag置0,以继续匹配下一行的内容,当遇到文件的结束符时,执行def_finish(),并加载下一个文件(由之前的include组成的依赖树的回溯).

然后看更加复杂的带有参数的宏定义

  /* define arg:  =  */
[a-zA-Z_][a-zA-Z0-9_$]*{W}*"="[^,\)]*{W}? { BEGIN(DEF_SEP); def_add_arg(); }
  /* define arg:  */
[a-zA-Z_][a-zA-Z0-9_$]*{W}? { BEGIN(DEF_SEP); def_add_arg(); }

","{W}? { BEGIN(DEF_ARG); }
")"{W}? { BEGIN(DEF_TXT); }

"//"[^\r\n]* { ECHO; }
"/*"         { comment_enter = YY_START; BEGIN(CCOMMENT); ECHO; }
{W}          {}

(\n|"\r\n"|"\n\r"|\r){W}? { istack->lineno += 1; fputc('\n', yyout); }

. {
    emit_pathline(istack);
    fprintf(stderr, "error: malformed `define directive.\n");
    error_count += 1;
    BEGIN(ERROR_LINE);
}

这里需要补充一下,宏定义有种用法,宏定义函数的参数是可以有默认值的,类似于函数的默认值.

`define FOO(val=42, text="42") do_foo(val, text)

因此,在匹配到'('进入DEF_ARG状态后,分两种情况讨论,第一种是后续的字符串中存在=,就是上面提到的这种情况,另一种是没有=的,两种情况的匹配表达式不同,但处理的函数相同,都是进入DEF_SEP状态,def_add_arg()可以处理"=格式和格式,将字符串去掉多余空格,并按等号进行分割后,添加到def_buf中.

进入DEF_SEP状态后,剔除注释、无意义的换行和空格等字符,直到遇到‘,’ 或')',其中','表示开始识别下一个参数,因此转入DEF_ARG状态,右括号的话,说明宏定义的第一部分已经结束,开始处理宏定义的内容。

`undef{W}[a-zA-Z_][a-zA-Z0-9_$]*{W}?.* { def_undefine(); }

对于undef语句,识别到undef后,执行def_undefine(),实际的逻辑是去找到原宏定义,并将其删除,但这里有个小问题,当我把undef 语句写成

`undef FOO//delete the FOO definition

这时是无法进行宏定义的删除的,应该是没有考虑注释的影响


`ifdef{W}[a-zA-Z_][a-zA-Z0-9_$]* {
    char* name = strchr(yytext, '`'); assert(name);

    name += 6;
    name += strspn(name, " \t\b\f");

    ifdef_enter();

    if (is_defined(name))
        yy_push_state(IFDEF_TRUE);
    else
        yy_push_state(IFDEF_FALSE);
}

`ifndef{W}[a-zA-Z_][a-zA-Z0-9_$]* {
    char* name = strchr(yytext, '`'); assert(name);

    name += 7;
    name += strspn(name, " \t\b\f");

    ifdef_enter();

    if (!is_defined(name))
        yy_push_state(IFDEF_TRUE);
    else
        yy_push_state(IFDEF_FALSE);
}

接下来是关于宏定义条件语句方面的处理,对于ifdef和ifndef,处理逻辑相似,找到这个宏词语,然后查找宏定义表,若宏定义表中存在且是ifdef或宏定义表中不存在且是ifndef,属于命中状态,进入IFDEF_TRUE状态,否则进入IFDEF_FALSE状态。

`ifdef{W}  |
`ifndef{W} { ifdef_enter(); yy_push_state(IFDEF_SUPR); }

`elsif{W}[a-zA-Z_][a-zA-Z0-9_$]* { BEGIN(IFDEF_SUPR); }

`elsif{W}[a-zA-Z_][a-zA-Z0-9_$]* {
    char* name = strchr(yytext, '`'); assert(name);

    name += 6;
    name += strspn(name, " \t\b\f");

    if (is_defined(name))
        BEGIN(IFDEF_TRUE);
    else
        BEGIN(IFDEF_FALSE);
}

接下来开始处理套娃的控制了,若在IFDEF_FALSE或IFEDF_SUPR状态下,依旧见到例ifdef或ifndef,就继续嵌套处理,但这里没有匹配在IFDEF_TRUE状态下再遇到ifdef或ifndef的情况,这是因为在对状态定义时,IFDEF_TRUE是起始状态,不是排他状态,因此可以在上一个代码块中得到匹配,这里剩下的两个匹配是对于elsif的,IFDEF_FALSE状态下的elsif很好理解,而IFDEF_TRUE状态下的直接跳转到IFDEF_SUPR,是因为在IFDEF_TRUE下,相当于是控制语句命中的状态下,无论是else还是elsif都不会执行,因此不需要多余的处理.

`elsif{W}[a-zA-Z_][a-zA-Z0-9_$]* {
    char* name = strchr(yytext, '`'); assert(name);

    name += 6;
    name += strspn(name, " \t\b\f");

    if (is_defined(name))
        BEGIN(IFDEF_TRUE);
    else
        BEGIN(IFDEF_FALSE);
}

`elsif{W}[a-zA-Z_][a-zA-Z0-9_$]* {  }

`else  { BEGIN(IFDEF_SUPR); }
`else { BEGIN(IFDEF_TRUE); }
`else  {}

"//"[^\r\n]* {}

"/*" { comment_enter = YY_START; BEGIN(IFCCOMMENT); }

[^\r\n] {}
\n\r    |
\r\n    |
\n      |
\r      { istack->lineno += 1; fputc('\n', yyout); }
"*/"    { BEGIN(comment_enter); }

[^\r\n] {  }
\n\r    |
\r\n    |
\n      |
\r      { istack->lineno += 1; fputc('\n', yyout); }

`endif { ifdef_leave(); yy_pop_state(); }

但对于IFDEF_FALSE就不一样了,他现在属于控制条件不命中的状态,接下来的普通字符是不需要处理的,因此看倒数几行,在IFDEF_FALSE和IFDEF_SUPR状态下,除换行符需要对lineno+1外,其他字符不做处理.当遇到endif时,表示控制语句结束,开始退状态栈,还原状态码.总的来说就是IFDEF_TRUE状态下更关注接下来的字符处理,而不关注else或elsif后的内容,而IFDEF_FALSE状态下,不关注接下来的字符处理,更关注else或elsif分支是否可以将状态转移为IFDEF_TRUE.

`ifdef {
    error_count += 1;
    fprintf(stderr, "%s:%u: `ifdef without a macro name - ignored.\n",
            istack->path, istack->lineno+1);
}

`ifndef {
    error_count += 1;
    fprintf(stderr, "%s:%u: `ifndef without a macro name - ignored.\n",
            istack->path, istack->lineno+1);
}

`elsif {
    error_count += 1;
    fprintf(stderr, "%s:%u: `elsif without a macro name - ignored.\n",
            istack->path, istack->lineno+1);
}

`elsif{W}[a-zA-Z_][a-zA-Z0-9_$]* {
    error_count += 1;
    fprintf(stderr, "%s:%u: `elsif without a matching `ifdef - ignored.\n",
            istack->path, istack->lineno+1);
}

`else {
    error_count += 1;
    fprintf(stderr, "%s:%u: `else without a matching `ifdef - ignored.\n",
            istack->path, istack->lineno+1);
}

`endif {
    error_count += 1;
    fprintf(stderr, "%s:%u: `endif without a matching `ifdef - ignored.\n",
            istack->path, istack->lineno+1);
}

`{keywords} {
    emit_pathline(istack);
    error_count += 1;
    fprintf(stderr, "error: macro names cannot be directive keywords "
            "('%s'); replaced with nothing.\n", yytext);
}

在前面的词法中,已经对各种宏的合法情况进行了处理,若依旧可以匹配到,就一定是错误的使用方式,例如:

`endif

`elsif IVLOG

    reg a;
`endif

直接出现endif或 elsif的情况都属于不正确的使用,因此不会别正常的词法匹配所识别.(这里其实有一些偏语法了,但ivlpp只依靠词法分析来完成文件的预处理,所以会有语法的影子.

 /* This pattern notices macros and arranges for them to be replaced. */
`[a-zA-Z_][a-zA-Z0-9_$]* {
    if (macro_needs_args(yytext+1))
        yy_push_state(MA_START);
    else
        do_expand(0);
}

  /* Stringified version of macro expansion. This is an Icarus extension.
     When expanding macro text, the SV usage of `` takes precedence. */
``[a-zA-Z_][a-zA-Z0-9_$]* {
    assert(istack->file);
    assert(do_expand_stringify_flag == 0);
    do_expand_stringify_flag = 1;
    fputc('"', yyout);
    if (macro_needs_args(yytext+2))
        yy_push_state(MA_START);
    else
        do_expand(0);
}

接下来是宏替换方面的功能,遇到`开头的词语,将词语传入macro_needs_args()中,查找该词语是否在宏定义表中,若存在且不是宏函数,则执行do_expand(0),直接将对应的内容进行替换,否则则进入MA_START模式,在systemVerilog中,对于宏定义有一种``的使用方式,这是针对字符串的一种功能上的强化,可以在外面套一层双引号作为参数,对于输出时希望对内容用双引号括住的话,会更方便.这两种宏的处理方式是一样的.

\(  { BEGIN(MA_ADD); macro_start_args(); }

{W} {}

(\n|"\r\n"|"\n\r"|\r){W}? {
    istack->lineno += 1;
    fputc('\n', yyout);
}

. {
    emit_pathline(istack);

    fprintf(stderr, "error: missing argument list for `%s.\n", macro_name());
    error_count += 1;

    yy_pop_state();
    yyless(0);
}

进入MA_START状态后,跳过后面的空格符和换行符,直到遇见左括号,进入MA_ADD状态,若遇到其他符号,则属于宏定义使用错误,进行报错.


\"[^\"\n\r]*\" { macro_add_to_arg(0); }

\"[^\"\n\r]* {
    emit_pathline(istack);

    fprintf(stderr, "error: unterminated string.\n");
    error_count += 1;

    BEGIN(ERROR_LINE);
}

'[^\n\r]' { macro_add_to_arg(0); }

{W} { macro_add_to_arg(1); }

[({] { macro_add_to_arg(0); ma_parenthesis_level++; }

"," {
    if (ma_parenthesis_level > 0)
	macro_add_to_arg(0);
    else
	macro_finish_arg();
}

[)}] {
    if (ma_parenthesis_level > 0) {
        macro_add_to_arg(0);
        ma_parenthesis_level--;
    } else {
        macro_finish_arg();
        yy_pop_state();
        do_expand(1);
    }
}

(\n|"\r\n"|"\n\r"|\r){W}? {
    macro_add_to_arg(1);
    istack->lineno += 1;
    fputc('\n', yyout);
}

. { macro_add_to_arg(0); }

"//"[^\r\n]* { ECHO; }

"/*" { comment_enter = YY_START; BEGIN(CCOMMENT); ECHO; }

在宏函数的参数中,可以传变量,数字,也可以传递字符串,因此分情况进行处理,所匹配到双引号括住或单引号括住的情况,说明是字符串,执行macro_add_to_arg(0),若只有单边的双引号,则说明参数使用错误,进行报错.遇到空格符类,执行macro_add_to_arg(1),遇到括号套括号的情况,执行macro_add_to_arg(0),并将ma_parenthesis_level 加1,ma_parenthesis_level用于对括号的组队情况进行计数,左括号加1,有括号减1,当减到0时遇到的右括号一定是宏函数的右括号.执行macro_finish_arg()且用do_expand(1)完成参数的扩展与替换.在处理中遇到了单行注释则直接输出,多行注释则转入CCOMENT状态进行注释的处理.

看懂流程后,看一下macro_add_to_arg()这个函数.

static void macro_add_to_arg(int is_white_space)
{
    char* tail;
    int   length = yyleng;

    check_for_max_args();

    /* Replace any run of white space with a single space */
    if (is_white_space) {
        yytext[0] = ' ';
        yytext[1] = 0;
        length = 1;
    }

    /* Make sure there's room in the buffer for the new argument. */
    def_buf_grow_to_fit(length);

    /* Store the new text. */
    tail = &def_buf[def_buf_size - def_buf_free];
    strcpy(tail, yytext);
    def_buf_free -= length;
}

当传入1时,相当于将yytext截断为一个空格,然后将这个空格拷入def_buf中.

当传入0时,不对yytext进行改动,直接拷入def_buf中.看起来完成宏参数处理与替换工作的核心在于macro_finish_arg()函数和do_expand(1)

static void macro_finish_arg(void)
{
    int   offs;
    char* head;
    char* tail;

    check_for_max_args();

    offs = def_argo[def_argc-1] + def_argl[def_argc-1] + 1;
    head = &def_buf[offs];
    tail = &def_buf[def_buf_size - def_buf_free];

    /* Eat any leading and trailing white space. */
    if ((head < tail) && (*head == ' ')) {
	offs++;
	head++;
    }
    if ((tail > head) && (*(tail-1) == ' ')) {
	def_buf_free++;
	tail--;
    }

    *tail = 0;

    def_argo[def_argc] = offs;
    def_argl[def_argc] = tail - head;

    def_buf_free -= 1;
    def_argc++;
}

macro_finish_arg()函数的功能也比较简单,主要进行def_buf收尾字符串空格的删除.属于前期准备工作

static void do_expand(int use_args)
{
    if (cur_macro) {
        struct include_stack_t*isp;
        int head = 0;
        const char *cp;
        unsigned escapes = 0;
        char *str_buf = 0;

        if (cur_macro->keyword) {
            fprintf(yyout, "%s", cur_macro->value);
	    if (do_expand_stringify_flag) {
		do_expand_stringify_flag = 0;
		fputc('"', yyout);
	    }
            return;
        }

        if (use_args) {
	    int tail = 0;
            head = exp_buf_size - exp_buf_free;
            expand_using_args();
            tail = exp_buf_size - exp_buf_free;
            exp_buf_free += tail - head;

            if (tail == head) return;
        }

        isp = (struct include_stack_t*) calloc(1, sizeof(struct include_stack_t));

	isp->stringify_flag = do_expand_stringify_flag;
	do_expand_stringify_flag = 0;
        if (use_args) {
            isp->str = &exp_buf[head];
        } else if(cur_macro->magic) {
            // cast const char * to char * to suppress warning, since we won't
            // be modifying isp->str in place.
            isp->str = (char*)do_magic(cur_macro->name);
        } else {
            isp->str = cur_macro->value;
        }

        /* Escape some characters if we are making a string version. */
        for (cp = isp->str; (cp = strpbrk(cp, "\"\\")); cp += 1, escapes += 1);
        if (escapes && isp->stringify_flag) {
            unsigned idx = 0;
            str_buf = (char *) malloc(strlen(isp->str)+3*escapes+1);
            for (cp = isp->str; *cp; cp += 1) {
                if (*cp == '"') {
                   str_buf[idx] = '\\';
                   str_buf[idx+1] = '0';
                   str_buf[idx+2] = '4';
                   str_buf[idx+3] = '2';
                   idx += 4;
                   continue;
                }
                if (*cp == '\\') {
                   str_buf[idx] = '\\';
                   str_buf[idx+1] = '1';
                   str_buf[idx+2] = '3';
                   str_buf[idx+3] = '4';
                   idx += 4;
                   continue;
                }
                str_buf[idx] = *cp;
                idx += 1;
            }
            str_buf[idx] = 0;
            idx += 1;

            exp_buf_free -= idx;

            isp->str = str_buf;
        } else isp->str = strdup(isp->str);

        isp->orig_str = isp->str;
        isp->next = istack;
        istack->yybs = YY_CURRENT_BUFFER;
        istack = isp;

        yy_switch_to_buffer(yy_create_buffer(istack->file, YY_BUF_SIZE));
    } else {
	  if (do_expand_stringify_flag) {
		do_expand_stringify_flag = 0;
		fputc('"', yyout);
	  }
    }
}

do_expand()函数复杂一些.cur_macro是当前正在处理的宏定义的结构体,里面存有参数,名字,长度等信息.若keyword为1,则直接输出,这里对应的参数,如遇到:timescale时,是直接输出的.当use_args的值为1,所以会执行expand_using_args(),这个函数用于将宏定义函数中的变量与宏定义内容进行一对一的替换.替换好后,例化了一个include_stack_t结构体 isp,并将isp的str变量的内容设为经过宏替换的字符串,然后通过yy_switch_to_buffer()将lexor.c读取字符的指针指向了宏替换字符串.这里相当于将宏定义字符串抽象为一个include的文件中的内容,里面的处理就和正常的字符串一样了.


最后补充一下`timescale 这部分的处理过程,在处理中可以看出对于timescale的keyword值为1,但从lexor.lex中可以添加def_table的函数只有define_macro,且调用的都是

   define_macro(def_argv(0), "", 0, def_argc);

 define_macro(def_argv(0), define_text, 0, def_argc);

define_macro(name, buf, 0, argc);

其中倒数第二个参数就是keyword,可以看到这些都是0值,但查看def_table,确实发现有timescale这个宏的定义,这就比较奇怪,直到我看到了main函数,

int main(int argc, char*argv[])
{
      int opt, idx;
      unsigned lp;
      const char*flist_path = 0;
      unsigned flag_errors = 0;
      char*out_path = 0;
      FILE*out;
      char*precomp_out_path = 0;
      FILE*precomp_out = NULL;

	/* Define preprocessor keywords that I plan to just pass. */
	/* From 1364-2005 Chapter 19. */
      define_macro("begin_keywords",          "`begin_keywords", 1, 0);
      define_macro("celldefine",              "`celldefine", 1, 0);
      define_macro("default_nettype",         "`default_nettype", 1, 0);
      define_macro("end_keywords",            "`end_keywords", 1, 0);
      define_macro("endcelldefine",           "`endcelldefine", 1, 0);
      define_macro("line",                    "`line", 1, 0);
      define_macro("nounconnected_drive",     "`nounconnected_drive", 1, 0);
      define_macro("pragma",                  "`pragma", 1, 0);
      define_macro("resetall",                "`resetall", 1, 0);
      define_macro("timescale",               "`timescale", 1, 0);
      define_macro("unconnected_drive",       "`unconnected_drive", 1, 0);

	/* From 1364-2005 Annex D. */
      define_macro("default_decay_time",      "`default_decay_time", 1, 0);
      define_macro("default_trireg_strength", "`default_trireg_strength", 1, 0);
      define_macro("delay_mode_distributed",  "`delay_mode_distributed", 1, 0);
      define_macro("delay_mode_path",         "`delay_mode_path", 1, 0);
      define_macro("delay_mode_unit",         "`delay_mode_unit", 1, 0);
      define_macro("delay_mode_zero",         "`delay_mode_zero", 1, 0);

	/* From other places. */
      define_macro("disable_portfaults",      "`disable_portfaults", 1, 0);
      define_macro("enable_portfaults",       "`enable_portfaults", 1, 0);
      define_macro("endprotect",              "`endprotect", 1, 0);
      define_macro("nosuppress_faults",       "`nosuppress_faults", 1, 0);
      define_macro("protect",                 "`protect", 1, 0);
      define_macro("suppress_faults",         "`suppress_faults", 1, 0);
      define_macro("uselib",                  "`uselib", 1, 0);

破案了,在main函数中,先对这些保留的宏做了定义,可以看到这些宏的keyword都是1.这些宏是在main函数刚启动就添加的.

终于读完了iverilog的ivlpp的词法分析,看下来还有学到了一些新奇的设计的,但通篇的全局变量和指针的阅读性实在是太差了.

你可能感兴趣的:(iverilog)