diff --git a/Makefile.am b/Makefile.am index 7bc5faf6d..3a44f1f8c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -47,6 +47,7 @@ include thirdparty/Makemodule.am include src/Makemodule.am include src/cov/Makemodule.am include src/psl/Makemodule.am +include src/sdf/Makemodule.am include src/rt/Makemodule.am include src/jit/Makemodule.am include src/vhpi/Makemodule.am diff --git a/src/common.c b/src/common.c index 41d4a5e9b..aec573ca2 100644 --- a/src/common.c +++ b/src/common.c @@ -28,6 +28,8 @@ #include "thread.h" #include "type.h" #include "vlog/vlog-phase.h" +#include "sdf/sdf-phase.h" +#include "sdf/sdf-util.h" #include #include @@ -2485,6 +2487,18 @@ void analyse_file(const char *file, jit_t *jit, unit_registry_t *ur) } } break; + + case SOURCE_SDF: + { + sdf_file_t *sdf_file = sdf_parse(file, 0); + progress("analysed SDF file: %s", file); + + if (sdf_file != NULL) { + warnf("SDF is not yet supported"); + sdf_file_free(sdf_file); + } + } + break; } } diff --git a/src/elab.c b/src/elab.c index 0f838d564..951ab8024 100644 --- a/src/elab.c +++ b/src/elab.c @@ -64,6 +64,7 @@ typedef struct _elab_ctx { unit_registry_t *registry; lower_unit_t *lowered; cover_data_t *cover; + sdf_file_t *sdf; void *context; driver_set_t *drivers; hash_t *modcache; @@ -1397,6 +1398,7 @@ static void elab_inherit_context(elab_ctx_t *ctx, const elab_ctx_t *parent) ctx->library = ctx->library ?: parent->library; ctx->out = ctx->out ?: parent->out; ctx->cover = parent->cover; + ctx->sdf = parent->sdf; ctx->inst = ctx->inst ?: parent->inst; ctx->modcache = parent->modcache; ctx->depth = parent->depth + 1; @@ -2246,7 +2248,8 @@ void elab_set_generic(const char *name, const char *value) generic_override = new; } -tree_t elab(object_t *top, jit_t *jit, unit_registry_t *ur, cover_data_t *cover) +tree_t elab(object_t *top, jit_t *jit, unit_registry_t *ur, cover_data_t *cover, + sdf_file_t *sdf) { make_new_arena(); @@ -2276,6 +2279,7 @@ tree_t elab(object_t *top, jit_t *jit, unit_registry_t *ur, cover_data_t *cover) .cover = cover, .library = work, .jit = jit, + .sdf = sdf, .registry = ur, .modcache = hash_new(16), .dotted = lib_name(work), diff --git a/src/lexer.l b/src/lexer.l index 5f37544f4..4de8bf89e 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -100,6 +100,9 @@ VHDL_ID ({LOWER}|{UPPER})(_?({LOWER}|{UPPER}|[0-9]))* BAD_ID ({LOWER}|{UPPER}|_)({LOWER}|{UPPER}|[0-9_])* EXID \\([^\\\n]|\\\\)*\\ VLOG_ID [a-zA-Z_]([a-zA-Z0-9_$])* +/* TODO: Add "escaped_character" handling to SDF ID */ +/* TODO: According to spec, identifier could even start with number*/ +SDF_ID ([a-zA-Z$_])([a-zA-Z$_0-9])* SYSTASK \$[a-zA-Z_$]([a-zA-Z0-9_$])* STRING (\"([^\"\n]|\"\")*\")|(\%([^\"\n\%]|\%\%)*\%) BADSTRING (\"([^\"\n]|\"\")*)|(\%([^\"\n\%]|\%\%)*) @@ -129,8 +132,10 @@ VLOG_NUMBER {INTEGER}?\'[bhd]{HEX} UDP_LEVEL [01xX?bB] UDP_EDGE [rRfFpPnN*] UDP_INDICATOR "("{UDP_LEVEL}{UDP_LEVEL}")" +SCALAR_ZERO ('b0|'B0|1'b0|1'B0) +SCALAR_ONE ('b1|'B1|1'b1|1'B1) -%x COMMENT C_COMMENT PSL VLOG UDP +%x COMMENT C_COMMENT PSL VLOG UDP SDF SDF_EXPR ENTITY ?i:entity IS ?i:is @@ -274,6 +279,59 @@ NONDET ?i:nondet NONDET_V ?i:nondet_vector UNION ?i:union +INTERCONNECT ?i:interconnect +DELAYFILE ?i:delayfile +SDFVERSION ?i:sdfversion +DESIGN ?i:design +DATE ?i:date +VENDOR ?i:vendor +PROGRAM ?i:program +VERSION ?i:version +DIVIDER ?i:divider +VOLTAGE ?i:voltage +TEMPERATURE ?i:temperature +TIMESCALE ?i:timescale +CELL ?i:cell +CELLTYPE ?i:celltype +INSTANCE ?i:instance +ABSOLUTE ?i:absolute +PATHPULSE ?i:pathpulse +PATHPULSEP ?i:pathpulsepercent +INCREMENT ?i:increment +IOPATH ?i:iopath +DELAY ?i:delay +SETUP ?i:setup +HOLD ?i:hold +SETUPHOLD ?i:setuphold +RECOVERY ?i:recovery +REMOVAL ?i:removal +RECREM ?i:recrem +WIDTH ?i:width +PERIOD ?i:period +SKEW ?i:skew +BIDIRSKEW ?i:bidirectskew +NOCHANGE ?i:nochange +NETDELAY ?i:netdelay +DEVICE ?i:device +COND ?i:cond +CONDELSE ?i:condelse +PATHCONSTRAINT ?i:PATHCONSTRAINT +PERIODCONSTRAINT ?i:PERIODCONSTRAINT +SUM ?i:SUM +DIFF ?i:DIFF +SKEWCONSTRAINT ?i:SKEWCONSTRAINT +ARRIVAL ?i:ARRIVAL +DEPARTURE ?i:DEPARTURE +SLACK ?i:SLACK +WAVEFORM ?i:WAVEFORM +NAME ?i:NAME +EXCEPTION ?i:EXCEPTION +TIMINGCHECK ?i:TIMINGCHECK +TIMINGENV ?i:TIMINGENV +RETAIN ?i:RETAIN +SCOND ?i:SCOND +CCOND ?i:CCOND + %% {SYNTH_OFF} { TOKEN(tSYNTHOFF); } @@ -614,6 +672,106 @@ UNION ?i:union {SEVERITY} { TOKEN(tSEVERITY); } {REPORT} { TOKEN(tREPORT); } + +% /* SDF rules separated from rest for better maintainability */ + +% /* SDF hierarchical_identifier may conflict with SDF keywords -> + Must be scanned in separate mode! Tokens that may be lookahead + after an identifier must be scanned too. */ +"!" { TOKEN(tBANG); } +{TICK} { TOKEN(tTICK); } +"<<" { TOKEN(tLTLT); } +">>" { TOKEN(tGTGT); } +"<=" { TOKEN(tLE); } +">=" { TOKEN(tGE); } +"&&" { TOKEN(tDBLAMP); } +"~&" { TOKEN(tTILDEAMP); } +"~|" { TOKEN(tTILDEBAR); } +"^~" { TOKEN(tTILDECARET); } +"~^" { TOKEN(tTILDECARET); } +"%" { TOKEN(tPERCENT); } +"||" { TOKEN(tLOGOR); } +"^" { TOKEN(tCARET); } +"==" { TOKEN(tLOGEQ); } +"!=" { TOKEN(tLOGNEQ); } +"===" { TOKEN(tCASEEQ); } +"!==" { TOKEN(tCASENEQ); } + +"//" { comment_caller = YY_START; BEGIN(COMMENT); } +"/*" { comment_caller = YY_START; + BEGIN(C_COMMENT); + } + +{SCALAR_ZERO} { TOKEN(tSCALARZERO); } +{SCALAR_ONE} { TOKEN(tSCALARONE); } + +% /* TODO: Crosscheck SDF definition of decimal */ +{DECIMAL} { return parse_decimal_literal(yytext); } + +% /* VLOG_STRING is defined almost equally as SDF qstring */ +{VLOG_STRING} { return parse_string(yytext); } + +{DELAYFILE} { TOKEN(tDELAYFILE); } +{SDFVERSION} { TOKEN(tSDFVERSION); } +{DESIGN} { TOKEN(tDESIGN); } +{DATE} { TOKEN(tDATE); } +{VENDOR} { TOKEN(tVENDOR); } +{PROGRAM} { TOKEN(tPROGRAM); } +{VERSION} { TOKEN(tVERSION); } +{DIVIDER} { TOKEN(tDIVIDER); } +{VOLTAGE} { TOKEN(tVOLTAGE); } +{PROCESS} { TOKEN(tPROCESS); } +{TEMPERATURE} { TOKEN(tTEMPERATURE); } +{TIMESCALE} { TOKEN(tTIMESCALE); } +{CELL} { TOKEN(tCELL); } +{CELLTYPE} { TOKEN(tCELLTYPE); } +{INSTANCE} { TOKEN(tINSTANCE); } +{DELAY} { TOKEN(tDELAY); } +{ABSOLUTE} { TOKEN(tABSOLUTE); } +{PATHPULSE} { TOKEN(tPATHPULSE); } +{PATHPULSEP} { TOKEN(tPATHPULSEP); } +{INCREMENT} { TOKEN(tINCREMENT); } +{IOPATH} { TOKEN(tIOPATH); } +"posedge" { TOKEN(tPOSEDGE); } +"negedge" { TOKEN(tNEGEDGE); } +{SETUP} { TOKEN(tSETUP); } +{HOLD} { TOKEN(tHOLD); } +{SETUPHOLD} { TOKEN(tSETUPHOLD); } +{RECOVERY} { TOKEN(tRECOVERY); } +{REMOVAL} { TOKEN(tREMOVAL); } +{RECREM} { TOKEN(tRECREM); } +{WIDTH} { TOKEN(tWIDTH); } +{PERIOD} { TOKEN(tPERIOD); } +{SKEW} { TOKEN(tSKEW); } +{BIDIRSKEW} { TOKEN(tBIDIRSKEW); } +{NOCHANGE} { TOKEN(tNOCHANGE); } +{PORT} { TOKEN(tPORT); } +{INTERCONNECT} { TOKEN(tINTERCONNECT); } +{NETDELAY} { TOKEN(tNETDELAY); } +{DEVICE} { TOKEN(tDEVICE); } +{COND} { TOKEN(tSDFCOND); } +{CONDELSE} { TOKEN(tSDFCONDELSE); } +{PATHCONSTRAINT} { TOKEN(tPATHCONSTR); } +{PERIODCONSTRAINT} { TOKEN(tPERIODCONSTR); } +{SUM} { TOKEN(tSUM); } +{DIFF} { TOKEN(tDIFF); } +{SKEWCONSTRAINT} { TOKEN(tSKEWCONSTR); } +{ARRIVAL} { TOKEN(tARRIVAL); } +{DEPARTURE} { TOKEN(tDEPARTURE); } +{SLACK} { TOKEN(tSLACK); } +{WAVEFORM} { TOKEN(tWAVEFORM); } +{NAME} { TOKEN(tNAME); } +{EXCEPTION} { TOKEN(tEXCEPTION); } +{LABEL} { TOKEN(tLABEL); } +{TIMINGCHECK} { TOKEN(tTIMINGCHECK); } +{TIMINGENV} { TOKEN(tTIMINGENV); } +{RETAIN} { TOKEN(tRETAIN); } +{SCOND} { TOKEN(tSCOND); } +{CCOND} { TOKEN(tCCOND); } + +{SDF_ID} { yylval.str = xstrdup(yytext); TOKEN(tID); } + + {UTF8_MB} { warn_utf8(yytext); REJECT; } {VHDL_ID} { return parse_id(yytext); } @@ -951,6 +1109,16 @@ void scan_as_udp(void) BEGIN(UDP); } +void scan_as_sdf(void) +{ + BEGIN(SDF); +} + +void scan_as_sdf_expr(void) +{ + BEGIN(SDF_EXPR); +} + bool is_scanned_as_psl(void) { return (YY_START == PSL); diff --git a/src/nvc.c b/src/nvc.c index 153893feb..f98ad7007 100644 --- a/src/nvc.c +++ b/src/nvc.c @@ -19,7 +19,6 @@ #include "common.h" #include "cov/cov-api.h" #include "diag.h" -#include "eval.h" #include "jit/jit-llvm.h" #include "jit/jit.h" #include "lib.h" @@ -365,6 +364,7 @@ static int elaborate(int argc, char **argv, cmd_state_t *state) { "dump-vcode", optional_argument, 0, 'v' }, { "cover", optional_argument, 0, 'c' }, { "cover-spec", required_argument, 0, 's' }, + { "sdf", required_argument, 0, 'f' }, { "verbose", no_argument, 0, 'V' }, { "no-save", no_argument, 0, 'N' }, { "jit", no_argument, 0, 'j' }, @@ -374,7 +374,7 @@ static int elaborate(int argc, char **argv, cmd_state_t *state) bool use_jit = DEFAULT_JIT, no_save = false; cover_mask_t cover_mask = 0; - char *cover_spec_file = NULL; + char *cover_spec_file = NULL, *sdf_args = NULL; int cover_array_limit = 0; const int next_cmd = scan_cmd(2, argc, argv); int c, index = 0; @@ -415,6 +415,9 @@ static int elaborate(int argc, char **argv, cmd_state_t *state) case 's': cover_spec_file = optarg; break; + case 'f': + sdf_args = optarg; + break; case 0: // Set a flag break; @@ -448,6 +451,11 @@ static int elaborate(int argc, char **argv, cmd_state_t *state) cover_load_spec_file(cover, cover_spec_file); } + if (sdf_args != NULL) { + // TODO: Pass min-max spec to underlying sdf_parse somehow + analyse_file(sdf_args, NULL, NULL); + } + if (state->registry != NULL) { unit_registry_free(state->registry); state->registry = NULL; @@ -463,7 +471,7 @@ static int elaborate(int argc, char **argv, cmd_state_t *state) jit_enable_runtime(state->jit, false); - tree_t top = elab(obj, state->jit, state->registry, cover); + tree_t top = elab(obj, state->jit, state->registry, cover, NULL); if (top == NULL) return EXIT_FAILURE; diff --git a/src/phase.h b/src/phase.h index d5702fc76..5058da30d 100644 --- a/src/phase.h +++ b/src/phase.h @@ -39,7 +39,7 @@ void bounds_check(tree_t top); // Elaborate a top level design unit tree_t elab(object_t *top, jit_t *jit, unit_registry_t *ur, - cover_data_t *cover); + cover_data_t *cover, sdf_file_t *sdf); // Set the value of a top-level generic void elab_set_generic(const char *name, const char *value); diff --git a/src/prim.h b/src/prim.h index 16c95ba91..91daa5b1c 100644 --- a/src/prim.h +++ b/src/prim.h @@ -33,6 +33,7 @@ typedef struct _ident *ident_t; typedef struct _tree *tree_t; typedef struct _type *type_t; typedef struct _vlog_node *vlog_node_t; +typedef struct _sdf_file sdf_file_t; typedef struct loc loc_t; typedef struct _fbuf fbuf_t; typedef struct _hash hash_t; diff --git a/src/rt/shell.c b/src/rt/shell.c index 5f5a11c43..967a6680c 100644 --- a/src/rt/shell.c +++ b/src/rt/shell.c @@ -581,7 +581,7 @@ static int shell_cmd_elaborate(ClientData cd, Tcl_Interp *interp, jit_enable_runtime(sh->jit, false); - tree_t top = elab(tree_to_object(unit), sh->jit, sh->registry, NULL); + tree_t top = elab(tree_to_object(unit), sh->jit, sh->registry, NULL, NULL); if (top == NULL) return TCL_ERROR; diff --git a/src/scan.c b/src/scan.c index 686db02e3..355e2a373 100644 --- a/src/scan.c +++ b/src/scan.c @@ -84,6 +84,9 @@ void input_from_buffer(const char *buf, size_t len, hdl_kind_t kind) case SOURCE_VHDL: reset_vhdl_parser(); break; + case SOURCE_SDF: + reset_sdf_parser(); + break; } } @@ -94,6 +97,9 @@ void input_from_file(const char *file) size_t len = strlen(file); if (len > 2 && file[len - 2] == '.' && file[len - 1] == 'v') kind = SOURCE_VERILOG; + else if (len > 4 && !strcmp(&(file[len - 4]), ".sdf")) { + kind = SOURCE_SDF; + } int fd; if (strcmp(file, "-") == 0) @@ -191,6 +197,24 @@ const char *token_str(token_t tok) buf[0] = tok; return buf; } + // TODO: When SDF tokens are appended behind rest of tokens, this special case + // shall be removed! + else if (tok > 499) { + static const char *sdf_token_strs[] = { + "delay file", "sdf version", "design", "date", "vendor", "program", + "version", "divider", "voltage", "temperature", "cell", "celltype", + "instance", "delay", "timing check", "timing env", "path pulse", + "path pulse percent", "IO path", "retain", "cond", "condelse", + "interconnect", "net delay", "device", "setup", "hold", "setuphold", + "recovery", "removal", "recrem", "skew", "bidirectional skew", "width", + "period", "nochange", "cond", "scond", "ccond", "path constraint", + "period constraint", "sum", "diff", "skew constraint", "exception", + "name", "arrival", "departure", "slack", "waveform", "increment", + "absolute", "~&", "~|", "~^", + }; + if (tok >= 500 && tok - 500 < ARRAY_LEN(sdf_token_strs)) + return sdf_token_strs[tok - 500]; + } else { static const char *token_strs[] = { "identifier", "entity", "is", "end", "generic", "port", "constant", @@ -226,6 +250,7 @@ const char *token_str(token_t tok) "(*", "*)", "number", "forever", "[[", "]]", "specify", "endspecify", "primitive", "endprimitive", "table", "endtable", "assign", "level symbol", "edge symbol", "edge indicator", "buf", "||", + "scalar constant (0)", "scalar constant (1)" }; if (tok >= 200 && tok - 200 < ARRAY_LEN(token_strs)) diff --git a/src/scan.h b/src/scan.h index b8579cd3b..4eec28014 100644 --- a/src/scan.h +++ b/src/scan.h @@ -31,7 +31,11 @@ typedef union { // Functions shared between VHDL and Verilog scanners -typedef enum { SOURCE_VHDL, SOURCE_VERILOG } hdl_kind_t; +typedef enum { + SOURCE_VHDL, + SOURCE_VERILOG, + SOURCE_SDF +} hdl_kind_t; typedef int token_t; @@ -49,6 +53,8 @@ void scan_as_psl(void); void scan_as_vhdl(void); void scan_as_verilog(void); void scan_as_udp(void); +void scan_as_sdf(void); +void scan_as_sdf_expr(void); // Private interface to Flex scanners @@ -57,6 +63,7 @@ int get_next_char(char *b, int max_buffer); void reset_vhdl_parser(void); void reset_verilog_parser(void); +void reset_sdf_parser(void); bool is_scanned_as_psl(void); @@ -88,6 +95,7 @@ bool is_scanned_as_psl(void); #define tHASH '#' #define tTILDE '~' #define tBANG '!' +#define tPERCENT '%' #define tID 200 #define tENTITY 201 @@ -315,5 +323,67 @@ bool is_scanned_as_psl(void); #define tUDPIND 423 #define tBUF 424 #define tLOGOR 425 +#define tSCALARZERO 426 +#define tSCALARONE 427 + +// SDF only keywords - separated for better maintainability +// during implementation of SDF annotation. +// TODO: Once finally merged, this can be appended to the last +// defined token! +#define tDELAYFILE 500 +#define tSDFVERSION 501 +#define tDESIGN 502 +#define tDATE 503 +#define tVENDOR 504 +#define tPROGRAM 505 +#define tVERSION 506 +#define tDIVIDER 507 +#define tVOLTAGE 508 +#define tTEMPERATURE 509 +#define tCELL 510 +#define tCELLTYPE 511 +#define tINSTANCE 512 +#define tDELAY 513 +#define tTIMINGCHECK 514 +#define tTIMINGENV 515 +#define tPATHPULSE 516 +#define tPATHPULSEP 517 +#define tIOPATH 518 +#define tRETAIN 519 +#define tSDFCOND 520 +#define tSDFCONDELSE 521 +#define tINTERCONNECT 522 +#define tNETDELAY 523 +#define tDEVICE 524 +#define tSETUP 525 +#define tHOLD 526 +#define tSETUPHOLD 527 +#define tRECOVERY 528 +#define tREMOVAL 529 +#define tRECREM 530 +#define tSKEW 531 +#define tBIDIRSKEW 532 +#define tWIDTH 533 +#define tPERIOD 534 +#define tNOCHANGE 535 +#define tCOND 536 +#define tSCOND 537 +#define tCCOND 538 +#define tPATHCONSTR 539 +#define tPERIODCONSTR 540 +#define tSUM 541 +#define tDIFF 542 +#define tSKEWCONSTR 543 +#define tEXCEPTION 544 +#define tNAME 545 +#define tARRIVAL 546 +#define tDEPARTURE 547 +#define tSLACK 548 +#define tWAVEFORM 549 +#define tINCREMENT 550 +#define tABSOLUTE 551 +#define tTILDEAMP 552 +#define tTILDEBAR 553 +#define tTILDECARET 554 #endif // _SCAN_H diff --git a/src/sdf/Makemodule.am b/src/sdf/Makemodule.am new file mode 100644 index 000000000..aa320206c --- /dev/null +++ b/src/sdf/Makemodule.am @@ -0,0 +1,5 @@ +lib_libnvc_a_SOURCES += \ + src/sdf/sdf-phase.h \ + src/sdf/sdf-parse.c \ + src/sdf/sdf-util.h \ + src/sdf/sdf-util.c diff --git a/src/sdf/sdf-parse.c b/src/sdf/sdf-parse.c new file mode 100644 index 000000000..3ccb145c6 --- /dev/null +++ b/src/sdf/sdf-parse.c @@ -0,0 +1,2328 @@ +// +// Copyright (C) 2024 Nick Gasson +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "util.h" +#include "diag.h" +#include "ident.h" +#include "scan.h" +#include "sdf/sdf-phase.h" +#include "sdf/sdf-util.h" + +#include +#include +#include +#include +#include + +// Lexer symbols +extern yylval_t yylval; +extern loc_t yylloc; + +// Global state, currently parsed file +static sdf_file_t *sdf_file = NULL; + +/////////////////////////////////////////////////////////////////////////////// +// TODO: The common parser functions and macros are copy-paste from VLOG parser. +// This could be refactored for one universal parser type. +/////////////////////////////////////////////////////////////////////////////// + +#define RECOVER_THRESH 5 +#define TRACE_PARSE 0 +#define TRACE_RECOVERY 0 +#define TOKENQ_SIZE 8 + +typedef struct { + token_t token; + yylval_t lval; + loc_t loc; +} tokenq_t; + +typedef struct { + loc_t start_loc; + loc_t last_loc; + const char *hint_str; + int n_correct; + tokenq_t tokenq[TOKENQ_SIZE]; + int tokenq_head; + int tokenq_tail; + yylval_t last_lval; + token_t opt_hist[8]; + int nopt_hist; +#if TRACE_PARSE + int depth; +#endif +} parse_state_t; + +typedef struct { + const char *old_hint; + loc_t old_start_loc; +} rule_state_t; + +static parse_state_t state; + +#define scan(...) _scan(1, __VA_ARGS__, -1) +#define expect(...) _expect(1, __VA_ARGS__, -1) +#define one_of(...) _one_of(1, __VA_ARGS__, -1) +#define not_at_token(...) ((peek() != tEOF) && !_scan(1, __VA_ARGS__, -1)) +#define peek() peek_nth(1) + +#define parse_error(loc, ...) do { \ + if (state.n_correct >= RECOVER_THRESH) \ + error_at((loc), __VA_ARGS__); \ + } while (0) + +#if TRACE_PARSE +static void _push_state(const rule_state_t *s); +#else +#define _push_state(s) +#endif + +#define EXTEND(s) \ + __attribute__((cleanup(_pop_state), unused)) \ + const rule_state_t _state = { state.hint_str, state.start_loc }; \ + state.hint_str = s; \ + _push_state(&_state); + +#define BEGIN(s) \ + EXTEND(s); \ + state.start_loc = LOC_INVALID; \ + +static inline void _pop_state(const rule_state_t *r) +{ +#if TRACE_PARSE + printf("%*s<-- %s\n", state.depth--, "", state.hint_str); +#endif + + state.hint_str = r->old_hint; + + if (r->old_start_loc.first_line != LINE_INVALID) + state.start_loc = r->old_start_loc; +} + +#if TRACE_PARSE +static inline void _push_state(const rule_state_t *r) +{ + printf("%*s--> %s\n", state.depth++, "", state.hint_str); +} +#endif + +static token_t peek_nth(int n) +{ + while (((state.tokenq_head - state.tokenq_tail) & (TOKENQ_SIZE - 1)) < n) { + const token_t token = processed_yylex(); + + int next = (state.tokenq_head + 1) & (TOKENQ_SIZE - 1); + assert(next != state.tokenq_tail); + + extern yylval_t yylval; + + state.tokenq[state.tokenq_head].token = token; + state.tokenq[state.tokenq_head].lval = yylval; + state.tokenq[state.tokenq_head].loc = yylloc; + + state.tokenq_head = next; + } + + const int pos = (state.tokenq_tail + n - 1) & (TOKENQ_SIZE - 1); + return state.tokenq[pos].token; +} + +static void drop_token(void) +{ + assert(state.tokenq_head != state.tokenq_tail); + + if (state.start_loc.first_line == LINE_INVALID) + state.start_loc = state.tokenq[state.tokenq_tail].loc; + + state.last_lval = state.tokenq[state.tokenq_tail].lval; + state.last_loc = state.tokenq[state.tokenq_tail].loc; + + state.tokenq_tail = (state.tokenq_tail + 1) & (TOKENQ_SIZE - 1); + + state.nopt_hist = 0; +} + +static void _vexpect(va_list ap) +{ + if (state.n_correct >= RECOVER_THRESH) { + diag_t *d = diag_new(DIAG_ERROR, &(state.tokenq[state.tokenq_tail].loc)); + diag_printf(d, "unexpected $yellow$%s$$ while parsing %s, expecting ", + token_str(peek()), state.hint_str); + + bool first = true; + for (int i = 0; i < state.nopt_hist; i++) { + diag_printf(d, "%s$yellow$%s$$", i == 0 ? "one of " : ", ", + token_str(state.opt_hist[i])); + first = false; + } + + int tok = va_arg(ap, int); + while (tok != -1) { + const int tmp = tok; + tok = va_arg(ap, int); + + if (first && (tok != -1)) + diag_printf(d, "one of "); + else if (!first) + diag_printf(d, (tok == -1) ? " or " : ", "); + + diag_printf(d, "$yellow$%s$$", token_str(tmp)); + + first = false; + } + + diag_hint(d, &(state.tokenq[state.tokenq_tail].loc), + "this token was unexpected"); + diag_emit(d); + } + + state.n_correct = 0; + + drop_token(); +} + +static void _expect(int dummy, ...) +{ + va_list ap; + va_start(ap, dummy); + _vexpect(ap); + va_end(ap); +} + +static bool consume(token_t tok) +{ + const token_t got = peek(); + if (tok != got) { + expect(tok); + return false; + } + else { + state.n_correct++; + drop_token(); + return true; + } +} + +static bool optional(token_t tok) +{ + if (peek() == tok) { + consume(tok); + return true; + } + else { + if (state.nopt_hist < ARRAY_LEN(state.opt_hist)) + state.opt_hist[state.nopt_hist++] = tok; + return false; + } +} + +static bool _scan(int dummy, ...) +{ + va_list ap; + va_start(ap, dummy); + + token_t p = peek(); + bool found = false; + + while (!found) { + const int tok = va_arg(ap, token_t); + if (tok == -1) + break; + else if (p == tok) + found = true; + } + + va_end(ap); + return found; +} + +static int _one_of(int dummy, ...) +{ + va_list ap; + va_start(ap, dummy); + + token_t p = peek(); + bool found = false; + + while (!found) { + const int tok = va_arg(ap, token_t); + if (tok == -1) + break; + else if (p == tok) + found = true; + } + + va_end(ap); + + if (found) { + consume(p); + return p; + } + else { + va_start(ap, dummy); + _vexpect(ap); + va_end(ap); + + return -1; + } +} + +static ident_t error_marker(void) +{ + return well_known(W_ERROR); +} + +/////////////////////////////////////////////////////////////////////////////// +// VLOG copy-paste ends here! +/////////////////////////////////////////////////////////////////////////////// + +#define PARSE_MIN_DELAYS (!!(sdf_file->min_max_spec & S_F_MIN_VALUES)) +#define PARSE_TYP_DELAYS (!!(sdf_file->min_max_spec & S_F_TYP_VALUES)) +#define PARSE_MAX_DELAYS (!!(sdf_file->min_max_spec & S_F_MAX_VALUES)) + +static inline bool is_next_tok_signed_number(void) +{ + return scan(tINT, tREAL, tPLUS, tMINUS); +} + +#define EPSILON 0.0000000000001 + +static bool doubles_equal(double a, double b) +{ + if (fabs(a - b) < EPSILON) + return true; + return false; +} + +static ident_t p_identifier(void) +{ + // identifier ::= + // character { character } + + BEGIN("identifier"); + + ident_t id; + if (consume(tID)) { + id = ident_new(state.last_lval.str); + free(state.last_lval.str); + } + else + id = error_marker(); + + return id; +} + +static ident_t p_qstring(void) +{ + // qstring ::= + // " { any_character } " + + BEGIN("qstring"); + + // qstring identifier is stored including the surrounding + // apostrophes. + consume(tSTRING); + ident_t str = ident_new(state.last_lval.str); + free(state.last_lval.str); + + return str; +} + +static ident_t p_hierarchical_identifier(void) +{ + // hierarchical_identifier ::= + // identifier { hchar identifier } + + BEGIN("hierarchical identifier"); + + scan_as_sdf_expr(); + + ident_t id = p_identifier(); + + // Hierarchical identifier is purposefully converted to '.' separator. + // This-way, "ident_walk_selected" can be used to walk the hierarchy. + // To dump the identifier, dumper walks the ID and replaces '.' by + // actual hierarchy separator to get to the original source! + while (scan(tDOT, tOVER)) { + int tok = peek(); + consume(tok); + + if (tok != sdf_file->hchar) + warn_at(&state.last_loc, "Used hierarchy separator: %c " + "but hierarchy separator defined in SDF header is: %c", + tok, sdf_file->hchar); + + id = ident_prefix(id, p_identifier(), '.'); + } + + scan_as_sdf(); + + return id; +} + +static int64_t p_integer(void) +{ + // integer ::= + // decimal_digit { decimal_digit } + + BEGIN("integer") + + consume(tINT); + + return yylval.i64; +} + +static double p_real_number(void) +{ + // real_number ::= + // integer + // | integer [ . integer ] + // | integer [ . integer ] e [ sign ] integer + + BEGIN("real number"); + + switch (one_of(tINT, tREAL)) { + case tINT: + return (double)yylval.i64; + case tREAL: + return yylval.real; + default: + return 0.0; + } +} + +static double p_signed_real_number(void) +{ + // signed_real_number ::= + // [ sign ] real_number + + BEGIN("signed real number"); + + switch (peek()) { + case tMINUS: + consume(tMINUS); + return -p_real_number(); + case tPLUS: + consume(tPLUS); + // Fall-through + default: + return p_real_number(); + } +} + +static void p_scalar_constant(void) +{ + // scalar_constant ::= + // 0 // logical zero + // | 'b0 + // | 'B0 + // | 1'b0 + // | 1'B0 + // | 1 // logical one + // | 'b1 + // | 'B1 + // | 1'b1 + // | 1'B1 + + BEGIN("scalar constant"); + + int64_t val = 0; + + switch (one_of(tINT, tSCALARZERO, tSCALARONE)) { + case tINT: + val = yylval.i64; + if (val != 1 && val != 0) + parse_error(&state.last_loc, "scalar_constant shall be one of: " + "1'b0, 1'B0, 'b0, 'B0, 0, " + "1'b1, 1'B1, 'b1, 'B1, 1"); + break; + case tSCALARONE: + val = 1; + break; + case tSCALARZERO: + val = 0; + break; + } +} + +static void p_scalar_node(void) +{ + // scalar_node ::= + // scalar_port + // | scalar_net + // scalar_port ::= + // hierarchical_identifier + // | hierarchical_identifier [ integer ] + // scalar_net ::= + // hierarchical_identifier + // | hierarchical_identifier [ integer ] + + BEGIN("scalar node"); + + p_hierarchical_identifier(); + + if (optional(tLSQUARE)) { + p_integer(); + consume(tRSQUARE); + } +} + +static void p_port_instance(void) +{ + // port_instance ::= + // port + // | hierarchical_identifier hchar port + // port ::= + // scalar_port + // | bus_port + // scalar_port ::= + // hierarchical_identifier + // | hierarchical_identifier [ integer ] + // bus_port ::= + // hierarchical_identifier [ integer : integer ] + // hierarchical_identifier ::= + // identifier { hchar identifier } + // + // Note: + // "port_instance" is equivalent to "port" because "hierarchical_identifier hchar port" + // production can completeyl dervied from "port" by: + // port -> scalar_port -> hierarchical_identifier -> identifier { hchar identifier } + // + // Therefore "hierarchical_identifier hchar port" is givan as: + // <----hierarchical_identifier----> <--port--> + // {identifier { hchar identifier }} hchar identifier + + BEGIN("port instance"); + + p_hierarchical_identifier(); + + if (optional(tLSQUARE)) { + p_integer(); + + if (optional(tCOLON)) + p_integer(); + + consume(tRSQUARE); + } +} + +static void p_port_or_scalar_constant(void) +{ + if (scan(tINT, tSCALARONE, tSCALARZERO)) + return p_scalar_constant(); + + return p_port_instance(); +} + +static void p_port_edge(void) +{ + // port_edge ::= + // ( edge_identifier port_instance ) + + BEGIN("port edge"); + + consume(tLPAREN); + + // TODO: Implement other edge identifier types! + one_of(tPOSEDGE, tNEGEDGE); + + p_port_instance(); + + consume(tRPAREN); +} + +static void p_port_spec(void) +{ + // port_spec ::= + // port_instance + // | port_edge + + BEGIN("port spec"); + + if (scan(tLPAREN)) + return p_port_edge(); + + return p_port_instance(); +} + +static inline sdf_binary_expr_kind_t p_binary_operator_optional(void) +{ + // binary_operator ::= + // + // arithmetic sum + // | - // arithmetic difference + // | * // arithmetic product + // | / // arithmetic quotient + // | % // modulus + // | == // logical equality + // | != // logical inequality + // | === // case equality + // | !== // case inequality + // | && // logical AND + // | || // logical OR + // | < // relational + // | <= // relational + // | > // relational + // | >= // relational + // | & // bit-wise binary AND + // | | // bit-wise binary inclusive OR + // | ^ // bit-wise binary exclusive OR + // | ^~ // bit-wise binary equivalence + // | ~^ // bit-wise binary equivalence (alternative) + // | >> // right shift + // | << // left shift + + BEGIN("binary operator") + + sdf_binary_expr_kind_t rv = S_BINARY_EXPR_NONE; + + int tok = peek(); + switch (tok) { + case tPLUS: + rv = S_BINARY_EXPR_PLUS; + break; + case tMINUS: + rv = S_BINARY_EXPR_MINUS; + break; + case tTIMES: + rv = S_BINARY_EXPR_MULT; + break; + case tOVER: + rv = S_BINARY_EXPR_DIV; + break; + case tLOGEQ: + rv = S_BINARY_EXPR_LOGEQ; + break; + case tLOGNEQ: + rv = S_BINARY_EXPR_LOGNEQ; + break; + case tCASEEQ: + rv = S_BINARY_EXPR_CASEEQ; + break; + case tCASENEQ: + rv = S_BINARY_EXPR_CASENEQ; + break; + case tDBLAMP: + rv = S_BINARY_EXPR_LOGAND; + break; + case tLOGOR: + rv = S_BINARY_EXPR_LOGOR; + break; + case tLT: + rv = S_BINARY_EXPR_LT; + break; + case tGT: + rv = S_BINARY_EXPR_GT; + break; + case tLE: + rv = S_BINARY_EXPR_LTEQ; + break; + case tGE: + rv = S_BINARY_EXPR_GTEQ; + break; + case tAMP: + rv = S_BINARY_EXPR_BITAND; + break; + case tBAR: + rv = S_BINARY_EXPR_BITOR; + break; + case tCARET: + rv = S_BINARY_EXPR_BITXOR; + break; + case tPERCENT: + rv = S_BINARY_EXPR_MOD; + break; + case tTILDECARET: + rv = S_BINARY_EXPR_BITXNOR; + break; + case tLTLT: + rv = S_BINARY_EXPR_SHLEFT; + break; + case tGTGT: + rv = S_BINARY_EXPR_SHRIGHT; + break; + } + + if (rv != S_BINARY_EXPR_NONE) + consume(tok); + + return rv; +} + +static sdf_unary_expr_kind_t p_unary_operator_optional(void) +{ + // unary_operator ::= + // + // arithmetic identity + // | - // arithmetic negation + // | ! // logical negation + // | ~ // bit-wise unary negation + // | & // reduction unary AND + // | ~& // reduction unary NAND + // | | // reduction unary OR + // | ~| // reduction unary NOR + // | ^ // reduction unary XOR + // | ^~ // reduction unary XNOR + // | ~^ // reduction unary XNOR (alternative) + + BEGIN("unary operator"); + + sdf_unary_expr_kind_t rv = S_UNARY_EXPR_NONE; + + int tok = peek(); + switch (tok) { + case tPLUS: + rv = S_UNARY_EXPR_PLUS; + break; + case tMINUS: + rv = S_UNARY_EXPR_MINUS; + break; + case tBANG: + rv = S_UNARY_EXPR_LOGNOT; + break; + case tTILDE: + rv = S_UNARY_EXPR_BITNOT; + break; + case tCARET: + rv = S_UNARY_EXPR_XOR; + break; + case tBAR: + rv = S_UNARY_EXPR_OR; + break; + case tAMP: + rv = S_UNARY_EXPR_AND; + break; + case tTILDEAMP: + rv = S_UNARY_EXPR_NAND; + break; + case tTILDEBAR: + rv = S_UNARY_EXPR_NOR; + break; + case tTILDECARET: + rv = S_UNARY_EXPR_XNOR; + break; + } + + if (rv != S_UNARY_EXPR_NONE) + consume(tok); + + return rv; +} + +static void p_simple_expression(void) +{ + // simple_expression ::= + // ( simple_expression ) + // | unary_operator ( simple_expression ) + // | port + // | unary_operator port + // | scalar_constant + // | unary_operator scalar_constant + // | simple_expression ? simple_expression : simple_expression + // | { simple_expression [ concat_expression ] } + // | { simple_expression { simple_expression [ concat_expression ] } } + + BEGIN("simple expression"); + + if (optional(tLPAREN)) { + p_simple_expression(); + consume(tRPAREN); + return; + } + + // unary_operator + sdf_unary_expr_kind_t unary_kind = S_UNARY_EXPR_NONE; + if ((unary_kind = p_unary_operator_optional()) != S_UNARY_EXPR_NONE) { + if (optional(tLPAREN)) { + p_simple_expression(); + consume(tRPAREN); + } + else + p_port_or_scalar_constant(); + + return; + } + + // simple_expression + if (optional(tLBRACE)) { + p_simple_expression(); + + while (optional(tCOMMA)) + p_simple_expression(); + + if (optional(tRBRACE)) { + p_simple_expression(); + + while (optional(tCOMMA)) + p_simple_expression(); + + consume(tRBRACE); + } + consume(tRBRACE); + } + + p_port_or_scalar_constant(); + + // simple_expression ? simple_expression : simple_expression + if (optional(tQUESTION)) { + p_simple_expression(); + consume(tCOLON); + p_simple_expression(); + } + } + +static void p_conditional_port_expr(void) +{ + // conditional_port_expr ::= + // simple_expression + // | ( conditional_port_expr ) + // | unary_operator ( conditional_port_expr ) + // | conditional_port_expr binary_operator conditional_port_expr + + BEGIN("conditional port expression"); + + sdf_unary_expr_kind_t unary_kind = S_UNARY_EXPR_NONE; + + if (optional(tLPAREN)) { + p_conditional_port_expr(); + consume(tRPAREN); + } + // unary_operator + else if ((unary_kind = p_unary_operator_optional()) != S_UNARY_EXPR_NONE) { + p_conditional_port_expr(); + } + // When we consume unary expression and open parenthesis, we get only to + // "conditional_port_expr" and "simple_expression". + // "conditional port_expr" is taken care-of by recursion, + // only "simple_expression" remain + else + p_simple_expression(); + + // binary_operator + sdf_binary_expr_kind_t binary_kind = S_BINARY_EXPR_NONE; + if ((binary_kind = p_binary_operator_optional()) != S_BINARY_EXPR_NONE) { + p_conditional_port_expr(); + } +} + +static void p_timing_check_condition(void) +{ + // timing_check_condition ::= + // scalar_node + // | inversion_operator scalar_node + // | scalar_node equality_operator scalar_constant + + BEGIN("timing check condition"); + + // inversion_operator + if (scan(tBANG, tTILDE)) { + int tok = peek(); + consume(tok); + + p_scalar_node(); + + return; + } + + p_scalar_node(); + + // equality_operator + if (scan(tLOGEQ, tLOGNEQ, tCASEEQ, tCASENEQ)) { + int tok = peek(); + consume(tok); + + p_scalar_constant(); + } +} + +static void p_neg_pair(void) +{ + // neg_pair ::= + // ( negedge signed_real_number [ signed_real_number ] ) + // ( posedge signed_real_number [ signed_real_number ] ) + + BEGIN("neg pair"); + + consume (tLPAREN); + consume (tNEGEDGE); + + p_signed_real_number(); + + if (is_next_tok_signed_number()) + p_signed_real_number(); + + consume (tRPAREN); + + consume (tLPAREN); + consume (tPOSEDGE); + + p_signed_real_number(); + + if (is_next_tok_signed_number()) + p_signed_real_number(); + + consume (tRPAREN); +} + +static void p_pos_pair(void) +{ + // pos_pair ::= + // ( posedge signed_real_number [ signed_real_number ] ) + // ( negedge signed_real_number [ signed_real_number ] ) + + BEGIN("pos pair"); + + consume (tLPAREN); + consume (tPOSEDGE); + + p_signed_real_number(); + + if (is_next_tok_signed_number()) + p_signed_real_number(); + + consume (tRPAREN); + + consume (tLPAREN); + consume (tNEGEDGE); + + p_signed_real_number(); + + if (is_next_tok_signed_number()) + p_signed_real_number(); + + consume (tRPAREN); +} + +static void p_edge_list(void) +{ + // edge_list ::= + // pos_pair { pos_pair } + // | neg_pair { neg_pair } + + BEGIN("edge list") + + p_pos_pair(); + while (peek_nth(2) == tPOSEDGE) + p_pos_pair(); + + p_neg_pair(); + while (peek_nth(2) == tNEGEDGE) + p_neg_pair(); +} + +static ident_t p_cell_instance(void) +{ + // cell_instance ::= + // ( INSTANCE [ hierarchical_identifier ] ) + // | ( INSTANCE * ) + + BEGIN("cell instance"); + + consume(tLPAREN); + consume(tINSTANCE); + + ident_t hier = NULL; + if (scan(tTIMES, tID)) { + if (optional(tTIMES)) + hier = ident_new("*"); + else + hier = p_hierarchical_identifier(); + } + + consume(tRPAREN); + + return hier; +} + +static void p_real_number_or_tripple(void) +{ + // real_number ::= + // integer + // | integer [ . integer ] + // | integer [ . integer ] e [ sign ] integer + // triple ::= + // real_number : [ real_number ] : [ real_number ] + // | [ real_number ] : real_number : [ real_number ] + // | [ real_number ] : [ real_number ] : real_number + + // If parsing tripple one following scenarios can occur: + // : + // (tINT|tREAL): + // + // Detect if parsing tripple or only real_number + + bool is_tripple = false; + if (scan(tCOLON) || peek_nth(2) == tCOLON) + is_tripple = true; + + if (is_tripple) { + bool number_present = false; + + if (not_at_token(tCOLON)) { + p_real_number(); + number_present = true; + } + + consume(tCOLON); + + if (not_at_token(tCOLON)) { + p_real_number(); + number_present = true; + } + + consume(tCOLON); + + if (not_at_token(tRPAREN)) { + p_real_number(); + number_present = true; + } + + if (!number_present) + parse_error(&state.last_loc, + "'tripple' shall have at least one number specified"); + + return; + } + + p_real_number(); +} + +static void p_value(void) +{ + // value ::= + // ( [ real_number ] ) + // | ( [triple] ) + + BEGIN("value"); + + consume(tLPAREN); + + if (not_at_token(tRPAREN)) + p_real_number_or_tripple(); + + consume(tRPAREN); +} + +static void p_signed_real_number_or_rtripple(void) +{ + // signed_real_number ::= + // [ sign ] real_number + // rtriple ::= + // signed_real_number : [ signed_real_number ] : [ signed_real_number ] + // | [ signed_real_number ] : signed_real_number : [ signed_real_number ] + // | [ signed_real_number ] : [ signed_real_number ] : signed_real_number + + // If parsing rtripple one following scenarios can occur: + // : + // (tINT|tREAL): + // (tPLUS|tMINUS)(tINT|tREAL): + // + // Detect if parsing rtripple or only signed_real_number + + bool is_rtripple = false; + if (scan(tCOLON) || peek_nth(2) == tCOLON || peek_nth(3) == tCOLON) + is_rtripple = true; + + if (is_rtripple) { + bool number_present = false; + + if (not_at_token(tCOLON)) { + p_signed_real_number(); + number_present = true; + } + + consume(tCOLON); + + if (not_at_token(tCOLON)) { + p_signed_real_number(); + number_present = true; + } + + consume(tCOLON); + + if (not_at_token(tRPAREN)) { + p_signed_real_number(); + number_present = true; + } + + if (!number_present) + parse_error(&state.last_loc, + "'rtripple' shall have at least one number specified"); + + return; + } + + p_signed_real_number(); +} + +static void p_rvalue(void) +{ + // rvalue ::= + // ( [ signed_real_number ] ) + // | ( [ rtriple ] ) + + BEGIN("rvalue"); + + consume(tLPAREN); + + if (not_at_token(tRPAREN)) + p_signed_real_number_or_rtripple(); + + consume(tRPAREN); +} + +static void p_name(void) +{ + // name ::= + // ( NAME [ qstring ] ) + + BEGIN("name"); + + consume(tLPAREN); + consume(tNAME); + + if (scan(tSTRING)) + p_qstring(); + + consume(tRPAREN); +} + +static void p_path_constraint(void) +{ + // path_constraint ::= + // ( PATHCONSTRAINT [ name ] port_instance port_instance { port_instance } + // rvalue rvalue ) + + BEGIN("path constraint"); + + consume(tLPAREN); + consume(tPATHCONSTR); + + if (peek_nth(2) == tNAME) + p_name(); + + p_port_instance(); + p_port_instance(); + + while (scan(tID)) + p_port_instance(); + + p_rvalue(); + p_rvalue(); + + consume(tRPAREN); +} + +static void p_exception(void) +{ + // exception ::= + // ( EXCEPTION cell_instance { cell_instance } ) + + BEGIN("exception"); + + consume(tLPAREN); + consume(tEXCEPTION); + + p_cell_instance(); + + while (peek_nth(2) == tINSTANCE) + p_cell_instance(); + + consume(tRPAREN); +} + +static void p_period_constraint(void) +{ + // period_constraint ::= + // ( PERIODCONSTRAINT port_instance value [ exception ] ) + + BEGIN("period constraint"); + + consume(tLPAREN); + consume(tPERIODCONSTR); + + p_port_instance(); + p_value(); + + if (peek_nth(2) == tEXCEPTION) + p_exception(); + + consume(tRPAREN); +} + +static void p_constraint_path(void) +{ + // constraint_path ::= + // ( port_instance port_instance ) + + BEGIN("constraint path"); + + consume(tLPAREN); + + p_port_instance(); + p_port_instance(); + + consume(tRPAREN); +} + +static void p_sum_constraint(void) +{ + // sum_constraint ::= + // ( SUM constraint_path constraint_path { constraint_path } rvalue [ rvalue ] ) + + BEGIN("sum constraint"); + + consume(tLPAREN); + consume(tSUM); + + p_constraint_path(); + p_constraint_path(); + + // Both next follow-up tokens start with tLPAREN. + // Decide based on second next token + while (peek_nth(2) == tID) + p_constraint_path(); + + p_rvalue(); + if (scan(tLPAREN)) + p_rvalue(); + + consume(tRPAREN); +} + +static void p_diff_constraint(void) +{ + // diff_constraint ::= + // ( DIFF constraint_path constraint_path value [ value ] ) + + BEGIN("diff constraint"); + + consume(tLPAREN); + consume(tDIFF); + + p_constraint_path(); + p_constraint_path(); + + p_value(); + if (scan(tLPAREN)) + p_value(); + + consume(tRPAREN); +} + +static void p_skew_constraint(void) +{ + // skew_constraint ::= + // ( SKEWCONSTRAINT port_spec value ) + + BEGIN("skew constraint"); + + consume(tLPAREN); + consume(tSKEWCONSTR); + + p_port_spec(); + p_value(); + + consume(tRPAREN); +} + +static void p_arrival_env(void) +{ + // arrival_env ::= + // ( ARRIVAL [ port_edge ] port_instance rvalue rvalue rvalue rvalue ) + + BEGIN("arrival env"); + + consume(tLPAREN); + consume(tARRIVAL); + + if (scan(tLPAREN)) + p_port_edge(); + p_port_instance(); + + for (int i = 0; i < 4; i++) + p_rvalue(); + + consume(tRPAREN); +} + +static void p_departure_env(void) +{ + // departure_env ::= + // ( DEPARTURE [ port_edge ] port_instance rvalue rvalue rvalue rvalue ) + + BEGIN("departure env"); + + consume(tLPAREN); + consume(tDEPARTURE); + + if (scan(tLPAREN)) + p_port_edge(); + + p_port_instance(); + + for (int i = 0; i < 4; i++) + p_rvalue(); + + consume(tRPAREN); +} + +static void p_slack_env(void) +{ + // slack_env ::= + // ( SLACK port_instance rvalue rvalue rvalue rvalue [ real_number ] ) + + BEGIN("slack env"); + + consume(tLPAREN); + consume(tSLACK); + + p_port_instance(); + + for (int i = 0; i < 4; i++) + p_rvalue(); + + if (scan(tREAL, tINT)) + p_real_number(); + + consume(tRPAREN); +} + +static void p_waveform_env(void) +{ + // waveform_env ::= + // ( WAVEFORM port_instance real_number edge_list ) + + BEGIN("waveform env"); + + consume(tLPAREN); + consume(tWAVEFORM); + + p_port_instance(); + p_real_number(); + + // sdf_value of S_TENV_KIND_WAVEFORM + // 0 - real_number + // 1 - X - Edge pairs + + p_edge_list(); + + consume(tRPAREN); +} + +static void p_te_spec(void) +{ + // te_spec ::= + // ( TIMINGENV te_def { te_def } ) + + BEGIN("te spec"); + + consume(tLPAREN); + consume(tTIMINGENV); + + do { + switch (peek_nth(2)) { + case tPATHCONSTR: + p_path_constraint(); + break; + case tPERIODCONSTR: + p_period_constraint(); + break; + case tSUM: + p_sum_constraint(); + break; + case tDIFF: + p_diff_constraint(); + break; + case tSKEWCONSTR: + p_skew_constraint(); + break; + case tARRIVAL: + p_arrival_env(); + break; + case tDEPARTURE: + p_departure_env(); + break; + case tSLACK: + p_slack_env(); + break; + case tWAVEFORM: + p_waveform_env(); + break; + default: + consume(tLPAREN); + one_of(tPATHCONSTR, tPERIODCONSTR, tSUM, tDIFF, tSKEWCONSTR, + tARRIVAL, tDEPARTURE, tSLACK, tWAVEFORM); + } + } while (scan(tLPAREN)); + + consume(tRPAREN); +} + +static void p_delval(void) +{ + // delval ::= + // rvalue + // | ( rvalue rvalue ) + // | ( rvalue rvalue rvalue ) + + BEGIN("delval"); + + // Multiple rvalues in delval + if (peek_nth(2) == tLPAREN) { + consume(tLPAREN); + + if (scan(tLPAREN)) + p_rvalue(); + if (scan(tLPAREN)) + p_rvalue(); + if (scan(tLPAREN)) + p_rvalue(); + + consume(tRPAREN); + } + // Single rvalue in delval + else + p_rvalue(); +} + +static void p_delval_list(void) +{ + // delval_list ::= + // delval + // | delval delval + // | delval delval delval + // | delval delval delval delval [ delval ] [ delval ] + // | delval delval delval delval delval delval delval + // [ delval ] [ delval ] [ delval ] [ delval ] [ delval ] + + BEGIN("delval list"); + + while (scan(tLPAREN)) + p_delval(); + + // if (sdf_values(delay) > 12) + // parse_error(&state.last_loc, + // "'delval_list' shall have at most 12 'delval' entries"); +} + +static void p_lbl_def(sdf_flags_t flag) +{ + // lbl_def ::= + // ( identifier delval_list ) + + BEGIN("lbl def"); + + consume(tLPAREN); + + p_identifier(); + + // Reuse delay for current label. Delvals are both "value" for these + p_delval_list(); + + consume(tRPAREN); +} + +static void p_lbl_type(void) +{ + // lbl_type ::= + // ( INCREMENT lbl_def { lbl_def } ) + // | ( ABSOLUTE lbl_def { lbl_def } ) + + BEGIN("lbl type"); + + consume(tLPAREN); + + sdf_flags_t flag; + if (one_of(tABSOLUTE, tINCREMENT) == tABSOLUTE) + flag = S_F_VALUE_ABSOLUTE; + else + flag = S_F_VALUE_INCREMENT; + + while (scan(tLPAREN)) + p_lbl_def(flag); + + consume(tRPAREN); +} + +static void p_lbl_spec(void) +{ + // bl_spec ::= + // ( LABEL lbl_type { lbl_type } ) + + BEGIN("lbl spec"); + + consume(tLPAREN); + consume(tLABEL); + + do + p_lbl_type(); + while (scan(tLPAREN)); + + consume(tRPAREN); +} + +static void p_port_tchk(void) +{ + // port_tchk ::= + // port_spec + // | ( COND [ qstring ] timing_check_condition port_spec ) + + BEGIN("port tcheck"); + + if (peek_nth(2) == tSDFCOND) { + consume(tLPAREN); + consume(tSDFCOND); + + if (scan(tSTRING)) + p_qstring(); + + p_timing_check_condition(); + p_port_spec(); + + consume(tRPAREN); + + return; + } + + return p_port_spec(); +} + +static void p_nochange_timing_check(void) +{ + // nochange_timing_check ::= + // ( NOCHANGE port_tchk port_tchk rvalue rvalue ) + + BEGIN("nochange timing check"); + + consume(tLPAREN); + consume(tNOCHANGE); + + p_port_tchk(); + p_port_tchk(); + p_rvalue(); + p_rvalue(); + + consume(tRPAREN); +} + +static void p_bidirectskew_timing_check(void) +{ + // bidirectskew_timing_check ::= + // ( BIDIRECTSKEW port_tchk port_tchk value value ) + + BEGIN("bidirectskew timing check"); + + consume(tLPAREN); + consume(tBIDIRSKEW); + + p_port_tchk(); + p_port_tchk(); + p_value(); + p_value(); + + consume(tRPAREN); +} + +static void p_skew_timing_check(void) +{ + // skew_timing_check ::= + // ( SKEW port_tchk port_tchk rvalue ) + + BEGIN("skew timing check"); + + consume(tLPAREN); + consume(tSKEW); + + p_port_tchk(); + p_port_tchk(); + p_rvalue(); + + consume(tRPAREN); +} + +#define DEFINE_1P1V_TCHK(func_name, msg, tok, subkind) \ + static void func_name(void) \ + { \ + /* width_timing_check ::= */ \ + /* ( WIDTH port_tchk value ) */ \ + /* period_timing_check ::= */ \ + /* ( PERIOD port_tchk value ) */ \ + \ + BEGIN(msg); \ + \ + consume(tLPAREN); \ + consume(tok); \ + \ + p_port_tchk(); \ + p_value(); \ + \ + consume(tRPAREN); \ + } + +DEFINE_1P1V_TCHK(p_width_timing_check, "width timing check", tWIDTH, S_TCHECK_WIDTH); +DEFINE_1P1V_TCHK(p_period_timing_check, "period timing check", tPERIOD, S_TCHECK_PERIOD); + +static void p_scond_or_ccond(sdf_cond_kind_t kind) +{ + // scond ::= + // ( SCOND [ qstring ] timing_check_condition ) + // ccond ::= + // ( CCOND [ qstring ] timing_check_condition ) + + BEGIN("scond or ccond"); + + consume(tLPAREN); + if (kind == S_COND_CCOND) + consume(tCCOND); + else + consume(tSCOND); + + if (scan(tSTRING)) + p_qstring(); + + p_timing_check_condition(); + + consume(tRPAREN); + + return; +} + +#define DEFINE_2P2RV_TCHK(func_name, msg, tok, subkind) \ + static void func_name(void) \ + { \ + /* setuphold_timing_check ::= */ \ + /* ( SETUPHOLD port_tchk port_tchk rvalue rvalue ) */ \ + /* | ( SETUPHOLD port_spec port_spec rvalue rvalue */ \ + /* [ scond ] [ ccond ] ) */ \ + \ + BEGIN(msg); \ + \ + consume(tLPAREN); \ + consume(tok); \ + \ + bool cond_seen = false; \ + if (peek_nth(2) == tSDFCOND) \ + cond_seen = true; \ + p_port_tchk(); \ + \ + if (peek_nth(2) == tSDFCOND) \ + cond_seen = true; \ + p_port_tchk(); \ + \ + p_rvalue(); \ + p_rvalue(); \ + \ + if (!cond_seen && (peek_nth(2) == tSCOND)) \ + p_scond_or_ccond(S_COND_SCOND); \ + if (!cond_seen && (peek_nth(2) == tCCOND)) \ + p_scond_or_ccond(S_COND_CCOND); \ + \ + consume(tRPAREN); \ + } + +DEFINE_2P2RV_TCHK(p_setuphold_timing_check, "setuphold timing check", tSETUPHOLD, S_TCHECK_SETUPHOLD); +DEFINE_2P2RV_TCHK(p_recrem_timing_check, "recrem timing check", tRECREM, S_TCHECK_RECREM); + +#define DEFINE_2P1V_TCHK(func_name, msg, tok, subkind) \ + static void func_name(void) \ + { \ + /* setup_timing_check ::= */ \ + /* ( SETUP port_tchk port_tchk value ) */ \ + /* hold_timing_check ::= */ \ + /* ( HOLD port_tchk port_tchk value ) */ \ + /* recovery_timing_check ::= */ \ + /* ( RECOVERY port_tchk port_tchk value ) */ \ + /* removal_timing_check ::= */ \ + /* ( REMOVAL port_tchk port_tchk value ) */ \ + \ + BEGIN(msg); \ + \ + consume(tLPAREN); \ + consume(tok); \ + \ + p_port_tchk(); \ + p_port_tchk(); \ + p_value(); \ + \ + consume(tRPAREN); \ + } + +DEFINE_2P1V_TCHK(p_setup_timing_check, "setup timing check", tSETUP, S_TCHECK_SETUP); +DEFINE_2P1V_TCHK(p_hold_timing_check, "hold timing check", tHOLD, S_TCHECK_HOLD); +DEFINE_2P1V_TCHK(p_recovery_timing_check, "recovery timing check",tRECOVERY, S_TCHECK_RECOVERY); +DEFINE_2P1V_TCHK(p_removal_timing_check, "removal timing check", tREMOVAL, S_TCHECK_REMOVAL); + +static void p_tc_spec(void) +{ + // tc_spec ::= + // ( TIMINGCHECK tchk_def { tchk_def } ) + + BEGIN("tc spec"); + + consume(tLPAREN); + consume(tTIMINGCHECK); + + do { + switch (peek_nth(2)) { + case tSETUP: + p_setup_timing_check(); + break; + case tHOLD: + p_hold_timing_check(); + break; + case tRECOVERY: + p_recovery_timing_check(); + break; + case tREMOVAL: + p_removal_timing_check(); + break; + case tSETUPHOLD: + p_setuphold_timing_check(); + break; + case tRECREM: + p_recrem_timing_check(); + break; + case tWIDTH: + p_width_timing_check(); + break; + case tPERIOD: + p_period_timing_check(); + break; + case tSKEW: + p_skew_timing_check(); + break; + case tBIDIRSKEW: + p_bidirectskew_timing_check(); + break; + case tNOCHANGE: + p_nochange_timing_check(); + break; + default: + consume(tLPAREN); + one_of(tSETUP, tHOLD, tSETUPHOLD, tRECREM, tWIDTH, + tPERIOD, tSKEW, tBIDIRSKEW, tNOCHANGE); + }; + } while (scan(tLPAREN)); + + consume(tRPAREN); +} + +static void p_pathpulsepercent_deltype(void) +{ + // pathpulsepercent_deltype ::= + // ( PATHPULSEPERCENT [ input_output_path ] value [ value ] ) + + BEGIN("pathpulsepercent deltype"); + + consume(tLPAREN); + consume(tPATHPULSEP); + + if (scan(tID)) { + p_port_instance(); + p_port_instance(); + } + + p_value(); + if (scan(tLPAREN)) + p_value(); + + consume(tRPAREN); +} + +static void p_pathpulse_deltype(void) +{ + // pathpulse_deltype ::= + // ( PATHPULSE [ input_output_path ] value [ value ] ) + + BEGIN("pathpulse deltype"); + + consume(tLPAREN); + consume(tPATHPULSE); + + if (scan(tID)) { + p_port_instance(); + p_port_instance(); + } + + p_value(); + if (scan(tLPAREN)) + p_value(); + + consume(tRPAREN); +} + +static void p_retain_def(void) +{ + // retain_def ::= + // ( RETAIN retval_list ) + // retval_list ::= + // delval + // | delval delval + // | delval delval delval + + BEGIN("retain def"); + + consume(tLPAREN); + consume(tRETAIN); + + while (scan(tLPAREN)) + p_delval(); + + // if (sdf_values(retain) > 3) + // parse_error(&state.last_loc, + // "'retval_list' shall have at most 3 'delval' entries"); + + consume(tRPAREN); + + return; +} + +static void p_iopath_def(void) +{ + // iopath_def ::= + // ( IOPATH port_spec port_instance { retain_def } delval_list ) + + BEGIN("iopath def"); + + consume(tLPAREN); + consume(tIOPATH); + + p_port_spec(); + p_port_instance(); + + if (peek_nth(2) == tRETAIN) + p_retain_def(); + + p_delval_list(); + + consume(tRPAREN); +} + +static void p_condelse_def(void) +{ + // condelse_def ::= + // ( CONDELSE iopath_def ) + + BEGIN("condelse def"); + + consume(tLPAREN); + consume(tSDFCONDELSE); + + p_iopath_def(); + + consume(tRPAREN); +} + +static void p_cond_def(void) +{ + // cond_def ::= + // ( COND [ qstring ] conditional_port_expr iopath_def ) + + BEGIN("cond def"); + + consume(tLPAREN); + consume(tSDFCOND); + + if (scan(tSTRING)) + p_qstring(); + + p_conditional_port_expr(); + + p_iopath_def(); + + consume(tRPAREN); +} + +static void p_port_def(void) +{ + // port_def ::= + // ( PORT port_instance delval_list ) + + BEGIN("port def"); + + consume(tLPAREN); + consume(tPORT); + + p_port_instance(); + + p_delval_list(); + + consume(tRPAREN); +} + +static void p_interconnect_def(void) +{ + // interconnect_def ::= + // ( INTERCONNECT port_instance port_instance delval_list ) + + BEGIN("interconnect def"); + + consume(tLPAREN); + consume(tINTERCONNECT); + + p_port_instance(); + p_port_instance(); + + p_delval_list(); + + consume(tRPAREN); +} + +static void p_netdelay_def(void) +{ + // netdelay_def ::= + // ( NETDELAY net_spec delval_list ) + + BEGIN("netdelay def"); + + consume(tLPAREN); + consume(tNETDELAY); + + p_port_spec(); + + p_delval_list(); + + consume(tRPAREN); +} + +static void p_device_def(void) +{ + // device_def ::= + // ( DEVICE [ port_instance ] delval_list ) + + BEGIN("device def"); + + consume(tLPAREN); + consume(tDEVICE); + + if (scan(tID)) + p_port_instance(); + + p_delval_list(); + + consume(tRPAREN); +} + +static void p_deldef_list(sdf_flags_t flag) +{ + // del_def { del_def } + + BEGIN("deldef list"); + + int tok = peek_nth(2); + while (tok == tIOPATH || tok == tSDFCOND || tok == tSDFCONDELSE || + tok == tPORT || tok == tINTERCONNECT || tok == tNETDELAY || + tok == tDEVICE) { + + switch (tok) { + case tIOPATH: + p_iopath_def(); + break; + case tSDFCOND: + p_cond_def(); + break; + case tSDFCONDELSE: + p_condelse_def(); + break; + case tPORT: + p_port_def(); + break; + case tINTERCONNECT: + p_interconnect_def(); + break; + case tNETDELAY: + p_netdelay_def(); + break; + default: // tDEVICE + p_device_def(); + } + + tok = peek_nth(2); + }; +} + +static void p_increment_deltype(void) +{ + // increment_deltype ::= + // ( INCREMENT del_def { del_def } ) + + BEGIN("increment deltype") + + consume(tLPAREN); + consume(tINCREMENT); + + p_deldef_list(S_F_VALUE_INCREMENT); + + consume(tRPAREN); +} + +static void p_absolute_deltype(void) +{ + // absolute_deltype ::= + // ( ABSOLUTE del_def { del_def } ) + + BEGIN("absolute deltype"); + + consume(tLPAREN); + consume(tABSOLUTE); + + p_deldef_list(S_F_VALUE_ABSOLUTE); + + consume(tRPAREN); +} + +static void p_del_spec(void) +{ + // del_spec ::= + // ( DELAY deltype { deltype } ) + + BEGIN("del spec"); + + consume(tLPAREN); + consume(tDELAY); + + do { + switch (peek_nth(2)) { + case tABSOLUTE: + p_absolute_deltype(); + break; + case tINCREMENT: + p_increment_deltype(); + break; + case tPATHPULSE: + p_pathpulse_deltype(); + break; + case tPATHPULSEP: + p_pathpulsepercent_deltype(); + break; + default: + consume(tLPAREN); + one_of(tABSOLUTE, tINCREMENT, tPATHPULSE, tPATHPULSEP); + } + } while (scan(tLPAREN)); + + consume(tRPAREN); +} + +static void p_cell(void) +{ + // cell ::= + // ( CELL celltype cell_instance { timing_spec } ) + // celltype ::= + // ( CELLTYPE qstring ) + // cell_instance ::= + // ( INSTANCE [ hierarchical_identifier ] ) + // | ( INSTANCE * ) + + BEGIN("cell"); + + consume(tLPAREN); + consume(tCELL); + + // celltype + consume(tLPAREN); + consume(tCELLTYPE); + + p_qstring(); + + consume(tRPAREN); + + // cell_instance + p_cell_instance(); + + // { timing_spec } + int tok = peek_nth(2); + while (tok == tDELAY || tok == tTIMINGCHECK || + tok == tLABEL || tok == tTIMINGENV) { + + switch (tok) { + case tDELAY: + p_del_spec(); + break; + case tTIMINGCHECK: + p_tc_spec(); + break; + case tLABEL: + p_lbl_spec(); + break; + default: // tTIMINGENV + p_te_spec(); + } + + tok = peek_nth(2); + } + + consume(tRPAREN); +} + +static void p_timescale(void) +{ + // time_scale ::= + // ( TIMESCALE timescale_number timescale_unit ) + // timescale_number ::= + // 1 | 10 | 100 | 1.0 | 10.0 | 100.0 + // timescale_unit ::= + // s | ms | us | ns | ps | fs + + BEGIN("timescale"); + + consume(tLPAREN); + consume(tTIMESCALE); + + double mult_fact = p_real_number(); + + // This accepts also timescale_number like 1.000000 + if (!doubles_equal(mult_fact, 1.0) && + !doubles_equal(mult_fact, 10.0) && + !doubles_equal(mult_fact, 100.0)) + parse_error(&state.last_loc, "Invalid timescale_number: %.1f. " + "Allowed values are: 1, 10, 100, 1.0, 10.0, 100.0", + mult_fact); + + ident_t timescale_unit = p_identifier(); + const char *ustr = istr(timescale_unit); + double unit_fact = 1; + + if (!strcmp(ustr, "s")) + unit_fact = 1E15; + else if (!strcmp(ustr, "ms")) + unit_fact = 1E12; + else if (!strcmp(ustr, "us")) + unit_fact = 1E9; + else if (!strcmp(ustr, "ns")) + unit_fact = 1E6; + else if (!strcmp(ustr, "ps")) + unit_fact = 1E3; + else if (strcmp(ustr, "fs")) + parse_error(&state.last_loc, "Invalid timescale_unit: %s. " + "Allowed values are: s, ms, us, ps, fs.", ustr); + + // Multiplication factor for real numbers in the SDF file + // to convert to 1 fs + sdf_file->unit_mult = mult_fact * unit_fact; + + consume(tRPAREN); +} + +static void p_temperature(void) +{ + // temperature ::= + // ( TEMPERATURE rtriple ) + // | ( TEMPERATURE signed_real_number ) + + BEGIN("temperature"); + + consume(tLPAREN); + consume(tTEMPERATURE); + + p_signed_real_number_or_rtripple(); + + consume(tRPAREN); +} + +static void p_voltage(void) +{ + // voltage ::= + // ( VOLTAGE rtriple ) + // | ( VOLTAGE signed_real_number ) + + BEGIN("voltage"); + + consume(tLPAREN); + consume(tVOLTAGE); + + p_signed_real_number_or_rtripple(); + + consume(tRPAREN); +} + +static void p_hierarchy_divider(void) +{ + // hierarchy_divider ::= + // ( DIVIDER hchar ) + + BEGIN("hierarchy divider"); + + consume(tLPAREN); + consume(tDIVIDER); + + char hchar = one_of(tDOT, tOVER); + if (hchar == -1) + parse_error(&state.last_loc, "Invalid SDF Hierarchy separator: %c. " + "SDF hierarchy separator shall be one of: '/' , '.' ", + hchar); + + sdf_file->hchar = hchar; + + if (hchar == tDOT) + sdf_file->hchar_other = tOVER; + else + sdf_file->hchar_other = tDOT; + + consume(tRPAREN); +} + +static void p_qstring_header_entry(int header_entry_token, + sdf_header_item_kind_t header_item_kind, + const char *info) +{ + // design_name ::= + // ( DESIGN qstring ) + // date ::= + // ( DATE qstring ) + // vendor ::= + // ( VENDOR qstring ) + // program_name ::= + // ( PROGRAM qstring ) + // program_version ::= + // ( VERSION qstring ) + // process ::= + // ( PROCESS qstring ) + + BEGIN(info); + + consume(tLPAREN); + consume(header_entry_token); + + p_qstring(); + + consume(tRPAREN); +} + +static void p_sdf_version(void) +{ + // sdf_version ::= + // ( SDFVERSION qstring ) + + BEGIN("sdf version"); + + consume(tLPAREN); + consume(tSDFVERSION); + + ident_t id = p_qstring(); + + sdf_file->std = SDF_STD_4_0; + + if (ident_glob(id, "*1.0*", -1)) + sdf_file->std = SDF_STD_1_0; + else if (ident_glob(id, "*2.0*", -1)) + sdf_file->std = SDF_STD_2_0; + else if (ident_glob(id, "*2.1*", -1)) + sdf_file->std = SDF_STD_2_1; + else if (ident_glob(id, "*3.0*", -1)) + sdf_file->std = SDF_STD_3_0; + else if (ident_glob(id, "*4.0*", -1)) + sdf_file->std = SDF_STD_4_0; + else + parse_error(&state.last_loc, "Invalid SDF version: %s. " + "SDF version shall contain one of: " + "\"1.0\", \"2.0\", \"2.1\", \"3.0\" or \"4.0\".", + istr(id)); + + consume(tRPAREN); +} + +static void p_sdf_header(void) +{ + // sdf_header ::= + // sdf_version [ design_name ] [ date ] [ vendor ] [ program_name ] + // [ program_version ] [hierarchy_divider ] [ voltage ] + // [ process ] [temperature ] [ time_scale ] + + BEGIN("sdf header"); + + p_sdf_version(); + + if (peek_nth(2) == tDESIGN) + p_qstring_header_entry(tDESIGN, S_HEADER_DESIGN, "design_name"); + + if (peek_nth(2) == tDATE) + p_qstring_header_entry(tDATE, S_HEADER_DATE, "date"); + + if (peek_nth(2) == tVENDOR) + p_qstring_header_entry(tVENDOR, S_HEADER_VENDOR, "vendor"); + + if (peek_nth(2) == tPROGRAM) + p_qstring_header_entry(tPROGRAM, S_HEADER_PROGRAM, "program"); + + if (peek_nth(2) == tVERSION) + p_qstring_header_entry(tVERSION, S_HEADER_VERSION, "program_version"); + + if (peek_nth(2) == tDIVIDER) + p_hierarchy_divider(); + + if (peek_nth(2) == tVOLTAGE) + p_voltage(); + + if (peek_nth(2) == tPROCESS) + p_qstring_header_entry(tPROCESS, S_HEADER_PROCESS, "process"); + + if (peek_nth(2) == tTEMPERATURE) + p_temperature(); + + // Default time unit is 1 ns + // See section 5.2.11 of IEEE 1497-2001 + sdf_file->unit_mult = 1E6; + + if (peek_nth(2) == tTIMESCALE) + p_timescale(); +} + +static sdf_file_t *p_delay_file(const char *file, sdf_flags_t min_max_spec) +{ + // delay_file ::= + // ( DELAYFILE sdf_header cell { cell } ) + + BEGIN("delay file"); + + consume(tLPAREN); + consume(tDELAYFILE); + + sdf_file = sdf_file_new(16384, 256); + + sdf_file->hchar = '.'; + sdf_file->min_max_spec = min_max_spec; + + p_sdf_header(); + + while (peek_nth(2) == tCELL) + p_cell(); + + consume(tRPAREN); + + return sdf_file; +} + +/////////////////////////////////////////////////////////////////////////////// +// Public API +/////////////////////////////////////////////////////////////////////////////// + +sdf_file_t *sdf_parse(const char *file, sdf_flags_t min_max_spec) +{ + scan_as_sdf(); + + reset_sdf_parser(); + + if (peek() == tEOF) + return NULL; + + return p_delay_file(file, min_max_spec); +} + +void reset_sdf_parser(void) +{ + state.tokenq_head = state.tokenq_tail = 0; +} diff --git a/src/sdf/sdf-phase.h b/src/sdf/sdf-phase.h new file mode 100644 index 000000000..e45dd13a7 --- /dev/null +++ b/src/sdf/sdf-phase.h @@ -0,0 +1,26 @@ +// +// Copyright (C) 2024 Nick Gasson +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef _SDF_PHASE_H +#define _SDF_PHASE_H + +#include "prim.h" +#include "sdf/sdf-util.h" + +sdf_file_t* sdf_parse(const char *file, sdf_flags_t min_max_spec); + +#endif // _SDF_PHASE_H diff --git a/src/sdf/sdf-util.c b/src/sdf/sdf-util.c new file mode 100644 index 000000000..4dba8d826 --- /dev/null +++ b/src/sdf/sdf-util.c @@ -0,0 +1,40 @@ +// +// Copyright (C) 2024 Nick Gasson +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "util.h" +#include "hash.h" +#include "sdf/sdf-util.h" + +#include + +sdf_file_t *sdf_file_new(int exp_hier_cells, int exp_wild_cells) +{ + sdf_file_t *sdf_file = xcalloc(sizeof(struct _sdf_file)); + + sdf_file->hier_map = hash_new(exp_hier_cells); + sdf_file->name_map = hash_new(exp_wild_cells); + + return sdf_file; +} + +void sdf_file_free(sdf_file_t *sdf_file) +{ + hash_free(sdf_file->name_map); + hash_free(sdf_file->hier_map); + + free(sdf_file); +} diff --git a/src/sdf/sdf-util.h b/src/sdf/sdf-util.h new file mode 100644 index 000000000..cf181576c --- /dev/null +++ b/src/sdf/sdf-util.h @@ -0,0 +1,160 @@ +// +// Copyright (C) 2024 Nick Gasson +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef _SDF_UTIL_H +#define _SDF_UTIL_H + +#include "prim.h" + +// +// SDF standard revisions +// +typedef enum { + SDF_STD_1_0, + SDF_STD_2_0, + SDF_STD_2_1, + SDF_STD_3_0, + SDF_STD_4_0 +} sdf_std_t; + +typedef enum { + S_F_POSEDGE = (1 << 0), + S_F_NEGEDGE = (1 << 1), + S_F_VALUE_ABSOLUTE = (1 << 2), + S_F_VALUE_INCREMENT = (1 << 3), + S_F_MIN_VALUES = (1 << 4), + S_F_TYP_VALUES = (1 << 5), + S_F_MAX_VALUES = (1 << 6) +} sdf_flags_t; + +#define S_F_MIN_MAX_SPEC_ALL (S_F_MIN_VALUES | S_F_TYP_VALUES | S_F_MAX_VALUES) +#define S_F_DELTYPE_ALL (S_F_DELTYPE_ABSOLUTE | S_F_DELTYPE_INCREMENT) + +typedef enum { + S_HEADER_SDF_VERSION = (1 << 0), + S_HEADER_DESIGN = (1 << 1), + S_HEADER_DATE = (1 << 2), + S_HEADER_VENDOR = (1 << 3), + S_HEADER_PROGRAM = (1 << 4), + S_HEADER_VERSION = (1 << 5), + S_HEADER_DIVIDER = (1 << 6), + S_HEADER_VOLTAGE = (1 << 7), + S_HEADER_PROCESS = (1 << 8), + S_HEADER_TEMPERATURE = (1 << 9), + S_HEADER_TIMESCALE = (1 << 10), +} sdf_header_item_kind_t; + +typedef enum { + S_COND_COND, + S_COND_CONDELSE, + S_COND_SCOND, + S_COND_CCOND +} sdf_cond_kind_t; + +typedef enum { + S_TCHECK_SETUP, + S_TCHECK_HOLD, + S_TCHECK_SETUPHOLD, + S_TCHECK_RECOVERY, + S_TCHECK_REMOVAL, + S_TCHECK_RECREM, + S_TCHECK_SKEW, + S_TCHECK_BIDIRSKEW, + S_TCHECK_WIDTH, + S_TCHECK_PERIOD, + S_TCHECK_NOCHANGE +} sdf_tcheck_kind_t; + +typedef enum { + S_UNARY_EXPR_PLUS, + S_UNARY_EXPR_MINUS, + S_UNARY_EXPR_LOGNOT, + S_UNARY_EXPR_BITNOT, + S_UNARY_EXPR_AND, + S_UNARY_EXPR_NAND, + S_UNARY_EXPR_OR, + S_UNARY_EXPR_NOR, + S_UNARY_EXPR_XOR, + S_UNARY_EXPR_XNOR, + + S_UNARY_EXPR_NONE, +} sdf_unary_expr_kind_t; + +typedef enum { + S_BINARY_EXPR_PLUS, + S_BINARY_EXPR_MINUS, + S_BINARY_EXPR_MULT, + S_BINARY_EXPR_DIV, + S_BINARY_EXPR_MOD, + S_BINARY_EXPR_LOGEQ, + S_BINARY_EXPR_LOGNEQ, + S_BINARY_EXPR_CASEEQ, + S_BINARY_EXPR_CASENEQ, + S_BINARY_EXPR_LOGAND, + S_BINARY_EXPR_LOGOR, + S_BINARY_EXPR_LT, + S_BINARY_EXPR_LTEQ, + S_BINARY_EXPR_GT, + S_BINARY_EXPR_GTEQ, + S_BINARY_EXPR_BITAND, + S_BINARY_EXPR_BITOR, + S_BINARY_EXPR_BITXOR, + S_BINARY_EXPR_BITXNOR, + S_BINARY_EXPR_SHRIGHT, + S_BINARY_EXPR_SHLEFT, + + S_BINARY_EXPR_NONE +} sdf_binary_expr_kind_t; + +struct _sdf_file { + // SDF standard + sdf_std_t std; + + // Multiplier for each time unit to convert into fs. + double unit_mult; + + // Hierarchy separator + char hchar; + char hchar_other; + + // hier_map: Hierarchy -> Cell map + // name_map: Cell name -> Cell map (for wildcards) + // Each cell is placed in only one of these two hash tables. + // Duplicities are prevented by looking up cells in the hash tables before + // adding the cell. Thus cells with equal: + // - hierarchy (INSTANCE [hierarchy]) + // - cell name and wildcard (CELLNAME, INSTANCE *) + // are merged into single S_CELL object. + // TODO: Resolve location tracking. SDF standard says that information from + // SDF file should be annotated in order it is present in the SDF file. + // If there is a cell annotated due to cell_name and instance wildcard, + // and also annotated by cell with hierarchy entry, then it is currently + // not possible to figure out which one to apply earlier. This is because + // loc_t is invalid since it counts only until 1M lines! Maybe somehow + // tracking only line number ? + hash_t *hier_map; + hash_t *name_map; + + // Mask of delays that are parsed: + // S_F_MIN_DELAYS, S_F_TYP_DELAYS, S_F_MAX_DELAYS + sdf_flags_t min_max_spec; +}; + +sdf_file_t *sdf_file_new(int exp_hier_cells, int exp_wild_cells); +void sdf_file_free(sdf_file_t *sdf); + +#endif // _SDF_UTIL_H diff --git a/test/Makemodule.am b/test/Makemodule.am index 2228b014e..a080bb432 100644 --- a/test/Makemodule.am +++ b/test/Makemodule.am @@ -27,6 +27,7 @@ bin_unit_test_SOURCES = \ test/test_jit.c \ test/test_model.c \ test/test_psl.c \ + test/test_sdf.c \ test/test_charset.c \ test/test_dump.c \ test/test_driver.c \ diff --git a/test/sdf/parse1.sdf b/test/sdf/parse1.sdf new file mode 100644 index 000000000..08514d5c0 --- /dev/null +++ b/test/sdf/parse1.sdf @@ -0,0 +1,7 @@ +(DELAYFILE + (SDFVERSION "4.0") + (CELL + (CELLTYPE "AND2") + (INSTANCE I1) + ) +) \ No newline at end of file diff --git a/test/sdf/parse10.sdf b/test/sdf/parse10.sdf new file mode 100644 index 000000000..c328f9bcf --- /dev/null +++ b/test/sdf/parse10.sdf @@ -0,0 +1,14 @@ +(DELAYFILE + (SDFVERSION "4.0") + (CELL + (CELLTYPE "AND2") + (INSTANCE B2.C2) + (DELAY + (ABSOLUTE + (IOPATH (posedge clk_a) x_q (0)) + (IOPATH (negedge clk_b[17]) y_q (0)) + (IOPATH (negedge clk_c[2:3]) z_q[1:2] (0)) + ) + ) + ) +) \ No newline at end of file diff --git a/test/sdf/parse11.sdf b/test/sdf/parse11.sdf new file mode 100644 index 000000000..7b04cf109 --- /dev/null +++ b/test/sdf/parse11.sdf @@ -0,0 +1,14 @@ +(DELAYFILE + (SDFVERSION "4.0") + (CELL + (CELLTYPE "AND2") + (INSTANCE B2.C2) + (DELAY + (ABSOLUTE + (IOPATH a b (17) ) + (IOPATH c d ((2.3) (1.2)) ) + (IOPATH e f ((0) (1) (2.5)) ) + ) + ) + ) +) \ No newline at end of file diff --git a/test/sdf/parse12.sdf b/test/sdf/parse12.sdf new file mode 100644 index 000000000..1dd222ba0 --- /dev/null +++ b/test/sdf/parse12.sdf @@ -0,0 +1,13 @@ +(DELAYFILE + (SDFVERSION "4.0") + (CELL + (CELLTYPE "AND2") + (INSTANCE B2.C2) + (DELAY + (ABSOLUTE + (IOPATH g h (1:2.4:3) ((:4.7:5) (6.1::7) (8.9:9:)) ((10.00007::) (:11.500:) (::12.99)) ) + (IOPATH i j () () () () () () () () () () () () ()) + ) + ) + ) +) \ No newline at end of file diff --git a/test/sdf/parse13.sdf b/test/sdf/parse13.sdf new file mode 100644 index 000000000..a8355f9ef --- /dev/null +++ b/test/sdf/parse13.sdf @@ -0,0 +1,13 @@ +(DELAYFILE + (SDFVERSION "Fancy SDF super version 1.0 followed by 2.1 that shall be ignored") + (DATE "Dec 17, 1991 14:49:48") + (DESIGN "testchip") + (VENDOR "Big Chips Inc.") + (PROGRAM "New VHDL Compiler") + (PROGRAM "Duplicit program entry") + (VERSION "1.10.2") + (CELL + (CELLTYPE "AND2") + (INSTANCE I1) + ) +) \ No newline at end of file diff --git a/test/sdf/parse14.sdf b/test/sdf/parse14.sdf new file mode 100644 index 000000000..fcd67f367 --- /dev/null +++ b/test/sdf/parse14.sdf @@ -0,0 +1,17 @@ +(DELAYFILE + (SDFVERSION "4.0") + (CELL + (CELLTYPE "DFF") + (INSTANCE cache.mem3.bndrx.I103) + (LABEL + (ABSOLUTE + (TCLK_Q (7.54:12.14:19.78) (6.97:13.66:18.47)) + (TSETUP_D_CLK (3:4:5.6)) + ) + (INCREMENT + (TCLK_QB (8.16:13.74:20.25) (7.63:14.83:21.42)) + (THOLD_D_CLK (4:5.6:7)) + ) + ) + ) +) \ No newline at end of file diff --git a/test/sdf/parse15.sdf b/test/sdf/parse15.sdf new file mode 100644 index 000000000..e84f13bb3 --- /dev/null +++ b/test/sdf/parse15.sdf @@ -0,0 +1,13 @@ +(DELAYFILE + (SDFVERSION "4.0") + (CELL + (CELLTYPE "DFF") + (INSTANCE hierarchy_example) + (DELAY + (PATHPULSE (0.4)) + (PATHPULSE (0.1) (0.5)) + (PATHPULSE my_inst.my_sub_inst.a my_inst_my_sub_inst.b (5.4)) + (PATHPULSEPERCENT top.in top.out (10.2) (11.9)) + ) + ) +) \ No newline at end of file diff --git a/test/sdf/parse16.sdf b/test/sdf/parse16.sdf new file mode 100644 index 000000000..6926201b6 --- /dev/null +++ b/test/sdf/parse16.sdf @@ -0,0 +1,13 @@ +(DELAYFILE + (SDFVERSION "4.0") + (CELL + (CELLTYPE "AND2") + (INSTANCE i_and) + (DELAY + (ABSOLUTE + (COND i_and.b==1'b0 (IOPATH i_and.a i_and.y (0.5) )) + (COND "My condition name" i_and.b=='b1 (IOPATH i_and.a i_and.y (0.4) )) + ) + ) + ) +) \ No newline at end of file diff --git a/test/sdf/parse17.sdf b/test/sdf/parse17.sdf new file mode 100644 index 000000000..98883ba30 --- /dev/null +++ b/test/sdf/parse17.sdf @@ -0,0 +1,26 @@ +(DELAYFILE + (SDFVERSION "4.0") + (DIVIDER / ) + (CELL + (CELLTYPE "AND2") + (INSTANCE i_and) + (DELAY + (ABSOLUTE + (COND a (IOPATH x y ())) + (COND a/b/c/d (IOPATH x y ())) + (COND (((((e/f/g/h))))) (IOPATH x y ())) + (COND +(x/y/z) (IOPATH x y ())) + (COND -(x/y/z) (IOPATH x y ())) + (COND !(x/y/z) (IOPATH x y ())) + (COND ~(x/y/z) (IOPATH x y ())) + (COND &(x/y/z) (IOPATH x y ())) + (COND ~&(x/y/z) (IOPATH x y ())) + (COND |(x/y/z) (IOPATH x y ())) + (COND ~|(x/y/z) (IOPATH x y ())) + (COND ^(x/y/z) (IOPATH x y ())) + (COND ^~(x/y/z) (IOPATH x y ())) + (COND ~^(x/y/z) (IOPATH x y ())) + ) + ) + ) +) \ No newline at end of file diff --git a/test/sdf/parse18.sdf b/test/sdf/parse18.sdf new file mode 100644 index 000000000..5859cb118 --- /dev/null +++ b/test/sdf/parse18.sdf @@ -0,0 +1,33 @@ +(DELAYFILE + (SDFVERSION "4.0") + (CELL + (CELLTYPE "AND2") + (INSTANCE i_and) + (DELAY + (ABSOLUTE + (COND a.b.c[5]+x.y.z[1] (IOPATH x y ())) + (COND (a.b.c[5])-x.y.z[2] (IOPATH x y ())) + (COND a.b.c[5]*(x.y.z[2]) (IOPATH x y ())) + (COND (a.b.c[5])/(x.y.z[2]) (IOPATH x y ())) + (COND (a.b.c%x.y.z) (IOPATH x y ())) + (COND (a.b.c==(x.y.z)) (IOPATH x y ())) + (COND ((a.b.c)!=x.y.z) (IOPATH x y ())) + (COND ((a.b.c)===(x.y.z)) (IOPATH x y ())) + (COND (((a.b.c))!==x.y.z) (IOPATH x y ())) + (COND a.b.c&&x.y.z (IOPATH x y ())) + (COND a.b.c||x.y.z (IOPATH x y ())) + (COND a.b.cx.y.z (IOPATH x y ())) + (COND a.b.c>=x.y.z (IOPATH x y ())) + (COND a.b.c&x.y.z (IOPATH x y ())) + (COND a.b.c|x.y.z (IOPATH x y ())) + (COND a.b.c^x.y.z (IOPATH x y ())) + (COND a.b.c^~x.y.z (IOPATH x y ())) + (COND a.b.c~^x.y.z (IOPATH x y ())) + (COND a.b.c>>x.y.z (IOPATH x y ())) + (COND a.b.c<. +// + +#include "test_util.h" +#include "common.h" +#include "lib.h" +#include "option.h" +#include "phase.h" +#include "sdf/sdf-phase.h" +#include "scan.h" +#include "type.h" + +#include + +#define fail_unless_floats_equal(a, b) fail_unless(fabs(a - b) < 0.00001); + +START_TEST(test_parse1) +{ + input_from_file(TESTDIR "/sdf/parse1.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse2) +{ + input_from_file(TESTDIR "/sdf/parse2.sdf"); + + const error_t expect[] = { + { 2, "Invalid SDF version: \"FOR_SURE_INVALID_SDF_VERSION\". " + "SDF version shall contain one of: \"1.0\", \"2.0\", \"2.1\", \"3.0\" or \"4.0\"" }, + { -1, NULL } + }; + expect_errors(expect); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + check_expected_errors(); +} +END_TEST + +START_TEST(test_parse3) +{ + input_from_file(TESTDIR "/sdf/parse3.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse4) +{ + input_from_file(TESTDIR "/sdf/parse4.sdf"); + + const error_t expect[] = { + { 6, "Used hierarchy separator: / but hierarchy separator defined in SDF header is: ." }, + { -1, NULL } + }; + expect_errors(expect); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + check_expected_errors(); +} +END_TEST + +START_TEST(test_parse5) +{ + input_from_file(TESTDIR "/sdf/parse5.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse6) +{ + input_from_file(TESTDIR "/sdf/parse6.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse7) +{ + input_from_file(TESTDIR "/sdf/parse7.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse8) +{ + input_from_file(TESTDIR "/sdf/parse8.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse9) +{ + input_from_file(TESTDIR "/sdf/parse9.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse10) +{ + input_from_file(TESTDIR "/sdf/parse10.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse11) +{ + input_from_file(TESTDIR "/sdf/parse11.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse12) +{ + input_from_file(TESTDIR "/sdf/parse12.sdf"); + + const error_t expect[] = { + // { 9, "'delval_list' shall have at most 12 'delval' entries" }, + { -1, NULL } + }; + expect_errors(expect); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + check_expected_errors(); +} +END_TEST + +START_TEST(test_parse13) +{ + input_from_file(TESTDIR "/sdf/parse13.sdf"); + + const error_t expect[] = { + { 4, "unexpected ( while parsing delay file, expecting )" }, + // { 7, "Duplicit header item" }, + { -1, NULL } + }; + + expect_errors(expect); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + check_expected_errors(); +} +END_TEST + +START_TEST(test_parse14) +{ + input_from_file(TESTDIR "/sdf/parse14.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse15) +{ + input_from_file(TESTDIR "/sdf/parse15.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse16) +{ + input_from_file(TESTDIR "/sdf/parse16.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse17) +{ + input_from_file(TESTDIR "/sdf/parse17.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse18) +{ + input_from_file(TESTDIR "/sdf/parse18.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse19) +{ + input_from_file(TESTDIR "/sdf/parse19.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse20) +{ + input_from_file(TESTDIR "/sdf/parse20.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse21) +{ + input_from_file(TESTDIR "/sdf/parse21.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse22) +{ + input_from_file(TESTDIR "/sdf/parse22.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse23) +{ + input_from_file(TESTDIR "/sdf/parse23.sdf"); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + fail_if_errors(); +} +END_TEST + +START_TEST(test_parse24) +{ + input_from_file(TESTDIR "/sdf/parse24.sdf"); + + const error_t expect[] = { + { 8, "'rtripple' shall have at least one number specified"}, + { 12, "'tripple' shall have at least one number specified"}, + { -1, NULL } + }; + expect_errors(expect); + + sdf_file_t *file = sdf_parse("dummy.sdf", S_F_MIN_MAX_SPEC_ALL); + ck_assert_ptr_nonnull(file); + + check_expected_errors(); +} +END_TEST + +Suite *get_sdf_tests(void) +{ + Suite *s = suite_create("sdf"); + + TCase *tc_core = nvc_unit_test(); + tcase_add_test(tc_core, test_parse1); + tcase_add_test(tc_core, test_parse2); + tcase_add_test(tc_core, test_parse3); + tcase_add_test(tc_core, test_parse4); + tcase_add_test(tc_core, test_parse5); + tcase_add_test(tc_core, test_parse6); + tcase_add_test(tc_core, test_parse7); + tcase_add_test(tc_core, test_parse8); + tcase_add_test(tc_core, test_parse9); + tcase_add_test(tc_core, test_parse10); + tcase_add_test(tc_core, test_parse11); + tcase_add_test(tc_core, test_parse12); + tcase_add_test(tc_core, test_parse13); + tcase_add_test(tc_core, test_parse14); + tcase_add_test(tc_core, test_parse15); + tcase_add_test(tc_core, test_parse16); + tcase_add_test(tc_core, test_parse17); + tcase_add_test(tc_core, test_parse18); + tcase_add_test(tc_core, test_parse19); + tcase_add_test(tc_core, test_parse20); + tcase_add_test(tc_core, test_parse21); + tcase_add_test(tc_core, test_parse22); + tcase_add_test(tc_core, test_parse23); + tcase_add_test(tc_core, test_parse24); + suite_add_tcase(s, tc_core); + + return s; +} diff --git a/test/test_util.c b/test/test_util.c index 34c910e97..ed4638e2f 100644 --- a/test/test_util.c +++ b/test/test_util.c @@ -156,7 +156,7 @@ tree_t run_elab(void) last_ent = t; } - tree_t top = elab(tree_to_object(last_ent), j, ur, NULL); + tree_t top = elab(tree_to_object(last_ent), j, ur, NULL, NULL); jit_free(j); diff --git a/test/unit_test.c b/test/unit_test.c index 0d8406384..50d36f54e 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -111,6 +111,7 @@ int main(int argc, char **argv) #endif nfail += RUN_TESTS(psl); nfail += RUN_TESTS(vlog); + nfail += RUN_TESTS(sdf); #ifdef ENABLE_TCL nfail += RUN_TESTS(shell); #endif