From 0833d9e37ae647a150d69fb3a0e5678cbd6af463 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Mon, 30 Apr 2012 21:12:59 -0700 Subject: [PATCH 1/9] Basic support for program blocks. --- Module.cc | 1 + Module.h | 5 +++++ parse.y | 5 +---- pform.cc | 9 +++++---- pform.h | 8 ++++++-- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Module.cc b/Module.cc index 66efe26b0..589e3e7e3 100644 --- a/Module.cc +++ b/Module.cc @@ -32,6 +32,7 @@ Module::Module(perm_string n) { library_flag = false; is_cell = false; + program_block = false; uc_drive = UCD_NONE; timescale_warn_done = false; time_unit = 0; diff --git a/Module.h b/Module.h index 1f9c32349..29da46ae1 100644 --- a/Module.h +++ b/Module.h @@ -76,6 +76,11 @@ class Module : public PScopeExtra, public LineInfo { bool is_cell; + /* This is true if the module represents a program block + instead of a module/cell. Program blocks have content + restrictions and slightly modify scheduling semantics. */ + bool program_block; + enum UCDriveType { UCD_NONE, UCD_PULL0, UCD_PULL1 }; UCDriveType uc_drive; diff --git a/parse.y b/parse.y index cc5fe859e..56443e206 100644 --- a/parse.y +++ b/parse.y @@ -3754,7 +3754,7 @@ local_timeunit_prec_decl module : attribute_list_opt module_start IDENTIFIER - { pform_startmodule($3, @2.text, @2.first_line, $1); } + { pform_startmodule(@2, $3, $2==K_program, $1); } module_parameter_port_list_opt module_port_list_opt module_attribute_foreign ';' @@ -3796,9 +3796,6 @@ module break; } } - if ($2 == K_program) { - yyerror(@2, "sorry: Program blocks not supported yet."); - } pform_endmodule($3, in_celldefine, ucd); delete[]$3; have_timeunit_decl = false; // We will allow decls again. diff --git a/pform.cc b/pform.cc index aa51b4468..4ff15138c 100644 --- a/pform.cc +++ b/pform.cc @@ -796,13 +796,14 @@ verinum* pform_verinum_with_size(verinum*siz, verinum*val, return res; } -void pform_startmodule(const char*name, const char*file, unsigned lineno, - list*attr) +void pform_startmodule(const struct vlltype&loc, const char*name, + bool program_block, list*attr) { assert( pform_cur_module == 0 ); perm_string lex_name = lex_strings.make(name); pform_cur_module = new Module(lex_name); + pform_cur_module->program_block = program_block; /* Set the local time unit/precision to the global value. */ pform_cur_module->time_unit = pform_time_unit; pform_cur_module->time_precision = pform_time_prec; @@ -813,7 +814,7 @@ void pform_startmodule(const char*name, const char*file, unsigned lineno, * a timescale directive. */ pform_cur_module->time_from_timescale = pform_timescale_file != 0; - FILE_NAME(pform_cur_module, file, lineno); + FILE_NAME(pform_cur_module, loc); pform_cur_module->library_flag = pform_library_flag; ivl_assert(*pform_cur_module, lexical_scope == 0); @@ -824,7 +825,7 @@ void pform_startmodule(const char*name, const char*file, unsigned lineno, scope_generate_counter = 1; if (warn_timescale && pform_timescale_file - && (strcmp(pform_timescale_file,file) != 0)) { + && (strcmp(pform_timescale_file,loc.text) != 0)) { cerr << pform_cur_module->get_fileline() << ": warning: " << "timescale for " << name diff --git a/pform.h b/pform.h index 838fad779..d1a5d8df5 100644 --- a/pform.h +++ b/pform.h @@ -145,9 +145,13 @@ extern PWire* pform_get_make_wire_in_scope(perm_string name, NetNet::Type net_ty * module has been noticed in the source file and the following events * are to apply to the scope of that module. The endmodule causes the * pform to close up and finish the named module. + * + * The program_flag indicates that the module is actually a program + * block. This has implications during parse and during + * elaboration/code generation. */ -extern void pform_startmodule(const char*, const char*file, unsigned lineno, - list*attr); +extern void pform_startmodule(const struct vlltype&loc, const char*name, + bool program_block, list*attr); extern void pform_check_timeunit_prec(); extern void pform_module_set_ports(vector*); From 580c44c015a01e19134f9760427517b78b17be0b Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Sun, 6 May 2012 10:21:51 -0700 Subject: [PATCH 2/9] Prevent non-blocking assignment in program blocks. --- parse.y | 110 ++++++++++++++++++++++++++++++------------------------- pform.cc | 17 +++++++++ pform.h | 4 ++ 3 files changed, 81 insertions(+), 50 deletions(-) diff --git a/parse.y b/parse.y index 56443e206..c4a21fa3d 100644 --- a/parse.y +++ b/parse.y @@ -5484,56 +5484,66 @@ statement_item /* This is roughly statement_item in the LRM */ $$ = tmp; } - | error '=' expression ';' - { yyerror(@2, "Syntax in assignment statement l-value."); - yyerrok; - $$ = new PNoop; - } - | lpvalue K_LE expression ';' - { PAssignNB*tmp = new PAssignNB($1,$3); - FILE_NAME(tmp, @1); - $$ = tmp; - } - | error K_LE expression ';' - { yyerror(@2, "Syntax in assignment statement l-value."); - yyerrok; - $$ = new PNoop; - } - | lpvalue '=' delay1 expression ';' - { PExpr*del = $3->front(); $3->pop_front(); - assert($3->empty()); - PAssign*tmp = new PAssign($1,del,$4); - FILE_NAME(tmp, @1); - $$ = tmp; - } - | lpvalue K_LE delay1 expression ';' - { PExpr*del = $3->front(); $3->pop_front(); - assert($3->empty()); - PAssignNB*tmp = new PAssignNB($1,del,$4); - FILE_NAME(tmp, @1); - $$ = tmp; - } - | lpvalue '=' event_control expression ';' - { PAssign*tmp = new PAssign($1,0,$3,$4); - FILE_NAME(tmp, @1); - $$ = tmp; - } - | lpvalue '=' K_repeat '(' expression ')' event_control expression ';' - { PAssign*tmp = new PAssign($1,$5,$7,$8); - FILE_NAME(tmp,@1); - tmp->set_lineno(@1.first_line); - $$ = tmp; - } - | lpvalue K_LE event_control expression ';' - { PAssignNB*tmp = new PAssignNB($1,0,$3,$4); - FILE_NAME(tmp, @1); - $$ = tmp; - } - | lpvalue K_LE K_repeat '(' expression ')' event_control expression ';' - { PAssignNB*tmp = new PAssignNB($1,$5,$7,$8); - FILE_NAME(tmp, @1); - $$ = tmp; - } + | error '=' expression ';' + { yyerror(@2, "Syntax in assignment statement l-value."); + yyerrok; + $$ = new PNoop; + } + | lpvalue K_LE expression ';' + { PAssignNB*tmp = new PAssignNB($1,$3); + FILE_NAME(tmp, @1); + $$ = tmp; + if (pform_in_program_block()) + yyerror(@2, "Non-blocking assignments not permitted in program blocks."); + } + | error K_LE expression ';' + { yyerror(@2, "Syntax in assignment statement l-value."); + yyerrok; + $$ = new PNoop; + if (pform_in_program_block()) + yyerror(@2, "Non-blocking assignments not permitted in program blocks."); + } + | lpvalue '=' delay1 expression ';' + { PExpr*del = $3->front(); $3->pop_front(); + assert($3->empty()); + PAssign*tmp = new PAssign($1,del,$4); + FILE_NAME(tmp, @1); + $$ = tmp; + } + | lpvalue K_LE delay1 expression ';' + { PExpr*del = $3->front(); $3->pop_front(); + assert($3->empty()); + PAssignNB*tmp = new PAssignNB($1,del,$4); + FILE_NAME(tmp, @1); + $$ = tmp; + if (pform_in_program_block()) + yyerror(@2, "Non-blocking assignments not permitted in program blocks."); + } + | lpvalue '=' event_control expression ';' + { PAssign*tmp = new PAssign($1,0,$3,$4); + FILE_NAME(tmp, @1); + $$ = tmp; + } + | lpvalue '=' K_repeat '(' expression ')' event_control expression ';' + { PAssign*tmp = new PAssign($1,$5,$7,$8); + FILE_NAME(tmp,@1); + tmp->set_lineno(@1.first_line); + $$ = tmp; + } + | lpvalue K_LE event_control expression ';' + { PAssignNB*tmp = new PAssignNB($1,0,$3,$4); + FILE_NAME(tmp, @1); + $$ = tmp; + if (pform_in_program_block()) + yyerror(@2, "Non-blocking assignments not permitted in program blocks."); + } + | lpvalue K_LE K_repeat '(' expression ')' event_control expression ';' + { PAssignNB*tmp = new PAssignNB($1,$5,$7,$8); + FILE_NAME(tmp, @1); + $$ = tmp; + if (pform_in_program_block()) + yyerror(@2, "Non-blocking assignments not permitted in program blocks."); + } /* The IEEE1800 standard defines dynamic_array_new assignment as a different rule from regular assignment. That implies that the diff --git a/pform.cc b/pform.cc index 4ff15138c..d8acf08ce 100644 --- a/pform.cc +++ b/pform.cc @@ -400,6 +400,15 @@ void pform_bind_attributes(map&attributes, delete attr; } +bool pform_in_program_block() +{ + if (pform_cur_module == 0) + return false; + if (pform_cur_module->program_block) + return true; + return false; +} + static bool pform_at_module_level() { return (lexical_scope == pform_cur_module) @@ -2850,6 +2859,14 @@ PProcess* pform_make_behavior(ivl_process_type_t type, Statement*st, pform_bind_attributes(pp->attributes, attr); pform_put_behavior_in_scope(pp); + + ivl_assert(*st, pform_cur_module); + if (pform_cur_module->program_block && type == IVL_PR_ALWAYS) { + cerr << st->get_fileline() << ": error: Always statements not allowed" + << " in program blocks." << endl; + error_count += 1; + } + return pp; } diff --git a/pform.h b/pform.h index d1a5d8df5..c3b292871 100644 --- a/pform.h +++ b/pform.h @@ -130,6 +130,10 @@ extern void pform_set_default_nettype(NetNet::Type net, const char*file, unsigned lineno); + /* Return true if currently processing a program block. This can be + used to reject statements that cannot exist in program blocks. */ +extern bool pform_in_program_block(void); + /* * Look for the given wire in the current lexical scope. If the wire * (including variables of any type) cannot be found in the current From dfe7beec31aafcee19ed95ea6b7d10114e91e8a9 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Wed, 9 May 2012 19:35:11 -0700 Subject: [PATCH 3/9] Allow modules (and program blocks in particular) to nest. An important advantage of program blocks is its ability to nest within a module. This winds up also allowing modules to nest, which is legal but presumably less used feature. --- Module.cc | 4 +- Module.h | 6 +- PGate.cc | 10 ++- PGate.h | 4 ++ compiler.h | 5 ++ elab_net.cc | 2 +- elab_scope.cc | 21 +++++- elab_sig.cc | 6 +- elaborate.cc | 20 +++++- net_scope.cc | 13 ++-- netlist.h | 3 +- nodangle.cc | 1 + parse.y | 68 +++++++++--------- parse_misc.cc | 2 + pform.cc | 189 +++++++++++++++++++++++++++----------------------- pform.h | 6 -- pform_dump.cc | 5 ++ t-dll.cc | 1 + 18 files changed, 218 insertions(+), 148 deletions(-) diff --git a/Module.cc b/Module.cc index 589e3e7e3..d448f6dfc 100644 --- a/Module.cc +++ b/Module.cc @@ -27,8 +27,8 @@ list Module::user_defparms; /* n is a permallocated string. */ -Module::Module(perm_string n) -: PScopeExtra(n) +Module::Module(LexicalScope*parent, perm_string n) +: PScopeExtra(n, parent) { library_flag = false; is_cell = false; diff --git a/Module.h b/Module.h index 29da46ae1..ad6604b37 100644 --- a/Module.h +++ b/Module.h @@ -65,7 +65,7 @@ class Module : public PScopeExtra, public LineInfo { public: /* The name passed here is the module name, not the instance name. This make must be a permallocated string. */ - explicit Module(perm_string name); + explicit Module(LexicalScope*parent, perm_string name); ~Module(); /* Initially false. This is set to true if the module has been @@ -121,6 +121,10 @@ class Module : public PScopeExtra, public LineInfo { the module definition. These are used at elaboration time. */ list generate_schemes; + /* Nested modules are placed here, and are not elaborated + unless they are instantiated, implicitly or explicitly. */ + std::map nested_modules; + list specify_paths; // The mod_name() is the name of the module type. diff --git a/PGate.cc b/PGate.cc index 9ec25d33f..05a1dc572 100644 --- a/PGate.cc +++ b/PGate.cc @@ -260,7 +260,7 @@ const char* PGBuiltin::gate_name() const } PGModule::PGModule(perm_string type, perm_string name, list*pins) -: PGate(name, pins), overrides_(0), pins_(0), +: PGate(name, pins), bound_type_(0), overrides_(0), pins_(0), npins_(0), parms_(0), nparms_(0), msb_(0), lsb_(0) { type_ = type; @@ -268,12 +268,18 @@ PGModule::PGModule(perm_string type, perm_string name, list*pins) PGModule::PGModule(perm_string type, perm_string name, named*pins, unsigned npins) -: PGate(name, 0), overrides_(0), pins_(pins), +: PGate(name, 0), bound_type_(0), overrides_(0), pins_(pins), npins_(npins), parms_(0), nparms_(0), msb_(0), lsb_(0) { type_ = type; } +PGModule::PGModule(Module*type, perm_string name) +: PGate(name, 0), bound_type_(type), overrides_(0), pins_(0), + npins_(0), parms_(0), nparms_(0), msb_(0), lsb_(0) +{ +} + PGModule::~PGModule() { } diff --git a/PGate.h b/PGate.h index 84fb26b9d..97e8261c9 100644 --- a/PGate.h +++ b/PGate.h @@ -201,6 +201,9 @@ class PGModule : public PGate { explicit PGModule(perm_string type, perm_string name, named*pins, unsigned npins); + // If the module type is known by design, then use this + // constructor. + explicit PGModule(Module*type, perm_string name); ~PGModule(); @@ -223,6 +226,7 @@ class PGModule : public PGate { perm_string get_type() const; private: + Module*bound_type_; perm_string type_; list*overrides_; named*pins_; diff --git a/compiler.h b/compiler.h index 976cbc831..8fb7b009e 100644 --- a/compiler.h +++ b/compiler.h @@ -177,6 +177,11 @@ static inline bool gn_system_verilog(void) return false; } +static inline bool gn_modules_nest(void) +{ + return gn_system_verilog(); +} + /* The bits of these GN_KEYWORDS_* constants define non-intersecting sets of keywords. The compiler enables groups of keywords by setting lexor_keyword_mask with the OR of the bits for the keywords to be diff --git a/elab_net.cc b/elab_net.cc index 030e22544..4d70820b7 100644 --- a/elab_net.cc +++ b/elab_net.cc @@ -693,7 +693,7 @@ NetNet* PEIdent::elaborate_bi_net(Design*des, NetScope*scope) const */ NetNet* PEIdent::elaborate_port(Design*des, NetScope*scope) const { - assert(scope->type() == NetScope::MODULE); + assert(scope->type_is_module()); NetNet*sig = des->find_signal(scope, path_); if (sig == 0) { cerr << get_fileline() << ": error: no wire/reg " << path_ diff --git a/elab_scope.cc b/elab_scope.cc index 234c8c983..eb17273ed 100644 --- a/elab_scope.cc +++ b/elab_scope.cc @@ -541,6 +541,19 @@ bool Module::elaborate_scope(Design*des, NetScope*scope, elaborate_scope_funcs(des, scope, funcs); + // Look for implicit modules and implicit gates for them. + + for (map::iterator cur = nested_modules.begin() + ; cur != nested_modules.end() ; ++cur) { + // Skip modules that must be explicitly instantiated. + if (cur->second->port_count() > 0) + continue; + + PGModule*nested_gate = new PGModule(cur->second, cur->second->mod_name()); + nested_gate->set_line(*cur->second); + gates_.push_back(nested_gate); + } + // Gates include modules, which might introduce new scopes, so // scan all of them to create those scopes. @@ -1212,7 +1225,7 @@ void PGModule::elaborate_scope_mod_(Design*des, Module*mod, NetScope*sc) const continue; } - if (scn->type() != NetScope::MODULE) continue; + if (! scn->type_is_module()) continue; if (strcmp(mod->mod_name(), scn->module_name()) != 0) continue; @@ -1329,8 +1342,10 @@ void PGModule::elaborate_scope_mod_instances_(Design*des, Module*mod, NetScope*s << "." << endl; } - // Create the new scope as a MODULE with my name. - NetScope*my_scope = new NetScope(sc, use_name, NetScope::MODULE); + // Create the new scope as a MODULE with my name. Note + // that if this is a nested module, mark it thus so that + // scope searches will continue into the parent scope. + NetScope*my_scope = new NetScope(sc, use_name, bound_type_? NetScope::NESTED_MODULE : NetScope::MODULE); my_scope->set_line(get_file(), mod->get_file(), get_lineno(), mod->get_lineno()); my_scope->set_module_name(mod->mod_name()); diff --git a/elab_sig.cc b/elab_sig.cc index 9f2e6b60e..90969c2fa 100644 --- a/elab_sig.cc +++ b/elab_sig.cc @@ -98,7 +98,7 @@ bool PScope::elaborate_sig_wires_(Design*des, NetScope*scope) const reg, then report an error. */ if (sig && (sig->scope() == scope) - && (scope->type() == NetScope::MODULE) + && (scope->type_is_module()) && (sig->port_type() == NetNet::PINPUT) && (sig->type() == NetNet::REG)) { @@ -110,7 +110,7 @@ bool PScope::elaborate_sig_wires_(Design*des, NetScope*scope) const } if (sig && (sig->scope() == scope) - && (scope->type() == NetScope::MODULE) + && (scope->type_is_module()) && (sig->port_type() == NetNet::PINOUT) && (sig->type() == NetNet::REG)) { @@ -122,7 +122,7 @@ bool PScope::elaborate_sig_wires_(Design*des, NetScope*scope) const } if (sig && (sig->scope() == scope) - && (scope->type() == NetScope::MODULE) + && scope->type_is_module() && (sig->port_type() == NetNet::PINOUT) && (sig->data_type() == IVL_VT_REAL)) { diff --git a/elaborate.cc b/elaborate.cc index c684b0de1..fa309110d 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -2068,6 +2068,10 @@ void PGModule::elaborate_udp_(Design*des, PUdp*udp, NetScope*scope) const bool PGModule::elaborate_sig(Design*des, NetScope*scope) const { + if (bound_type_) { + return elaborate_sig_mod_(des, scope, bound_type_); + } + // Look for the module type map::const_iterator mod = pform_modules.find(type_); if (mod != pform_modules.end()) @@ -2087,6 +2091,11 @@ bool PGModule::elaborate_sig(Design*des, NetScope*scope) const void PGModule::elaborate(Design*des, NetScope*scope) const { + if (bound_type_) { + elaborate_mod_(des, bound_type_, scope); + return; + } + // Look for the module type map::const_iterator mod = pform_modules.find(type_); if (mod != pform_modules.end()) { @@ -2108,10 +2117,16 @@ void PGModule::elaborate(Design*des, NetScope*scope) const void PGModule::elaborate_scope(Design*des, NetScope*sc) const { + // If the module type is known by design, then go right to it. + if (bound_type_) { + elaborate_scope_mod_(des, bound_type_, sc); + return; + } + // Look for the module type map::const_iterator mod = pform_modules.find(type_); if (mod != pform_modules.end()) { - elaborate_scope_mod_(des, (*mod).second, sc); + elaborate_scope_mod_(des, mod->second, sc); return; } @@ -2128,7 +2143,7 @@ void PGModule::elaborate_scope(Design*des, NetScope*sc) const // Try again to find the module type mod = pform_modules.find(type_); if (mod != pform_modules.end()) { - elaborate_scope_mod_(des, (*mod).second, sc); + elaborate_scope_mod_(des, mod->second, sc); return; } @@ -3241,6 +3256,7 @@ NetProc* PDisable::elaborate(Design*des, NetScope*scope) const return 0; case NetScope::MODULE: + case NetScope::NESTED_MODULE: cerr << get_fileline() << ": error: Cannot disable modules." << endl; des->errors += 1; return 0; diff --git a/net_scope.cc b/net_scope.cc index d51ade0b4..22a67e6fc 100644 --- a/net_scope.cc +++ b/net_scope.cc @@ -302,6 +302,9 @@ void NetScope::print_type(ostream&stream) const case FUNC: stream << "function"; break; + case NESTED_MODULE: + stream << "nested_module <" << (module_name_ ? module_name_.str() : "") + << "> instance"; case MODULE: stream << "module <" << (module_name_ ? module_name_.str() : "") << "> instance"; @@ -360,31 +363,31 @@ const NetFuncDef* NetScope::func_def() const void NetScope::set_module_name(perm_string n) { - assert(type_ == MODULE); + assert(type_ == MODULE || type_ == NESTED_MODULE); module_name_ = n; /* NOTE: n must have been permallocated. */ } perm_string NetScope::module_name() const { - assert(type_ == MODULE); + assert(type_ == MODULE || type_ == NESTED_MODULE); return module_name_; } void NetScope::add_module_port(NetNet*port) { - assert(type_ == MODULE); + assert(type_ == MODULE || type_ == NESTED_MODULE); ports_.push_back(port); } unsigned NetScope::module_ports() const { - assert(type_ == MODULE); + assert(type_ == MODULE || type_ == NESTED_MODULE); return ports_.size(); } NetNet* NetScope::module_port(unsigned idx) const { - assert(type_ == MODULE); + assert(type_ == MODULE || type_ == NESTED_MODULE); assert(idx < ports_.size()); return ports_[idx]; } diff --git a/netlist.h b/netlist.h index 06684d804..ddfc70e3d 100644 --- a/netlist.h +++ b/netlist.h @@ -724,7 +724,7 @@ extern std::ostream&operator << (std::ostream&out, const std::list&r class NetScope : public Attrib { public: - enum TYPE { MODULE, TASK, FUNC, BEGIN_END, FORK_JOIN, GENBLOCK }; + enum TYPE { MODULE, NESTED_MODULE, TASK, FUNC, BEGIN_END, FORK_JOIN, GENBLOCK }; /* Create a new scope, and attach it to the given parent. The name is expected to have been permallocated. */ @@ -806,6 +806,7 @@ class NetScope : public Attrib { const NetScope* child(const hname_t&name) const; TYPE type() const; + bool type_is_module() const { return type()==MODULE || type()==NESTED_MODULE; } void print_type(ostream&) const; void set_task_def(NetTaskDef*); diff --git a/nodangle.cc b/nodangle.cc index 6e01d4aba..229d21214 100644 --- a/nodangle.cc +++ b/nodangle.cc @@ -136,6 +136,7 @@ void nodangle_f::signal(Design*, NetNet*sig) if ((sig->port_type() != NetNet::NOT_A_PORT) && ((sig->scope()->type() == NetScope::TASK) || (sig->scope()->type() == NetScope::FUNC) || + (sig->scope()->type() == NetScope::NESTED_MODULE) || (sig->scope()->type() == NetScope::MODULE))) return; diff --git a/parse.y b/parse.y index c4a21fa3d..185038531 100644 --- a/parse.y +++ b/parse.y @@ -3866,43 +3866,46 @@ module_parameter_port_list module_item + /* Modules can contain further sub-module definitions. */ + : module + /* This rule detects net declarations that possibly include a primitive type, an optional vector range and signed flag. This also includes an optional delay set. The values are then applied to a list of names. If the primitive type is not specified, then resort to the default type LOGIC. */ - : attribute_list_opt net_type - primitive_type_opt unsigned_signed_opt range_opt - delay3_opt - net_variable_list ';' + | attribute_list_opt net_type + primitive_type_opt unsigned_signed_opt range_opt + delay3_opt + net_variable_list ';' - { ivl_variable_type_t dtype = $3; - if (dtype == IVL_VT_NO_TYPE) - dtype = IVL_VT_LOGIC; - pform_makewire(@2, $5, $4, $7, $2, - NetNet::NOT_A_PORT, dtype, $1); - if ($6 != 0) { - yyerror(@6, "sorry: net delays not supported."); - delete $6; - } - delete $1; - } + { ivl_variable_type_t dtype = $3; + if (dtype == IVL_VT_NO_TYPE) + dtype = IVL_VT_LOGIC; + pform_makewire(@2, $5, $4, $7, $2, NetNet::NOT_A_PORT, dtype, $1); + if ($6 != 0) { + yyerror(@6, "sorry: net delays not supported."); + delete $6; + } + delete $1; + } - | attribute_list_opt K_wreal delay3 net_variable_list ';' - { pform_makewire(@2, 0, true, $4, NetNet::WIRE, - NetNet::NOT_A_PORT, IVL_VT_REAL, $1); - if ($3 != 0) { - yyerror(@3, "sorry: net delays not supported."); - delete $3; - } - delete $1; - } - | attribute_list_opt K_wreal net_variable_list ';' - { pform_makewire(@2, 0, true, $3, NetNet::WIRE, - NetNet::NOT_A_PORT, IVL_VT_REAL, $1); - delete $1; - } + | attribute_list_opt K_wreal delay3 net_variable_list ';' + { pform_makewire(@2, 0, true, $4, NetNet::WIRE, + NetNet::NOT_A_PORT, IVL_VT_REAL, $1); + if ($3 != 0) { + yyerror(@3, "sorry: net delays not supported."); + delete $3; + } + delete $1; + } + + | attribute_list_opt K_wreal net_variable_list ';' + { pform_makewire(@2, 0, true, $3, NetNet::WIRE, + NetNet::NOT_A_PORT, IVL_VT_REAL, $1); + delete $1; + } /* Very similar to the rule above, but this takes a list of net_decl_assigns, which are = assignment @@ -4248,13 +4251,6 @@ module_item module items. These rules try to catch them at a point where a reasonable error message can be produced. */ - | K_module error ';' - { yyerror(@1, "error: missing endmodule or attempt to " - "nest modules."); - pform_error_nested_modules(); - yyerrok; - } - | error ';' { yyerror(@2, "error: invalid module item."); yyerrok; diff --git a/parse_misc.cc b/parse_misc.cc index 241341564..32f1bafc6 100644 --- a/parse_misc.cc +++ b/parse_misc.cc @@ -33,6 +33,8 @@ std::ostream& operator << (std::ostream&o, const YYLTYPE&loc) { if (loc.text) o << loc.text << ":"; + else + o << "<>:"; o << loc.first_line; return o; } diff --git a/pform.cc b/pform.cc index d8acf08ce..726cead0d 100644 --- a/pform.cc +++ b/pform.cc @@ -41,7 +41,13 @@ # include "ivl_assert.h" # include "ivl_alloc.h" +/* + * The pform_modules is a map of the modules that have been defined in + * the top level. This should not contain nested modules/programs. + */ map pform_modules; +/* + */ map pform_primitives; @@ -217,7 +223,7 @@ extern int VLparse(); /* This tracks the current module being processed. There can only be exactly one module currently being parsed, since Verilog does not allow nested module definitions. */ -static Module*pform_cur_module = 0; +static listpform_cur_module; bool pform_library_flag = false; @@ -307,7 +313,7 @@ PTask* pform_push_task_scope(const struct vlltype&loc, char*name, bool is_auto) pform_cur_generate->tasks.end()) { cerr << task->get_fileline() << ": error: duplicate " "definition for task '" << name << "' in '" - << pform_cur_module->mod_name() << "' (generate)." + << pform_cur_module.front()->mod_name() << "' (generate)." << endl; error_count += 1; } @@ -350,7 +356,7 @@ PFunction* pform_push_function_scope(const struct vlltype&loc, char*name, pform_cur_generate->funcs.end()) { cerr << func->get_fileline() << ": error: duplicate " "definition for function '" << name << "' in '" - << pform_cur_module->mod_name() << "' (generate)." + << pform_cur_module.front()->mod_name() << "' (generate)." << endl; error_count += 1; } @@ -402,16 +408,16 @@ void pform_bind_attributes(map&attributes, bool pform_in_program_block() { - if (pform_cur_module == 0) + if (pform_cur_module.size() == 0) return false; - if (pform_cur_module->program_block) + if (pform_cur_module.front()->program_block) return true; return false; } static bool pform_at_module_level() { - return (lexical_scope == pform_cur_module) + return (lexical_scope == pform_cur_module.front()) || (lexical_scope == pform_cur_generate); } @@ -488,15 +494,15 @@ void pform_set_default_nettype(NetNet::Type type, { pform_default_nettype = type; - if (pform_cur_module) { + if (pform_cur_module.size() > 0) { cerr << file<<":"<mod_name() + << "module " << pform_cur_module.back()->mod_name() << " starts on line " - << pform_cur_module->get_fileline() << "." << endl; + << pform_cur_module.back()->get_fileline() << "." << endl; error_count += 1; } } @@ -699,14 +705,14 @@ void pform_set_timeunit(const char*txt, bool in_module, bool only_check) if (in_module) { if (!only_check) { - pform_cur_module->time_unit = val; + pform_cur_module.front()->time_unit = val; tu_decl_flag = true; tu_local_flag = true; } else if (!tu_decl_flag) { VLerror(yylloc, "error: repeat timeunit found and the " "initial module timeunit is missing."); return; - } else if (pform_cur_module->time_unit != val) { + } else if (pform_cur_module.front()->time_unit != val) { VLerror(yylloc, "error: repeat timeunit does not match " "the initial module timeunit " "declaration."); @@ -723,7 +729,7 @@ void pform_set_timeunit(const char*txt, bool in_module, bool only_check) int pform_get_timeunit() { - return pform_cur_module->time_unit; + return pform_cur_module.front()->time_unit; } void pform_set_timeprecision(const char*txt, bool in_module, bool only_check) @@ -734,14 +740,14 @@ void pform_set_timeprecision(const char*txt, bool in_module, bool only_check) if (in_module) { if (!only_check) { - pform_cur_module->time_precision = val; + pform_cur_module.front()->time_precision = val; tp_decl_flag = true; tp_local_flag = true; } else if (!tp_decl_flag) { VLerror(yylloc, "error: repeat timeprecision found and the " "initial module timeprecision is missing."); return; - } else if (pform_cur_module->time_precision != val) { + } else if (pform_cur_module.front()->time_precision != val) { VLerror(yylloc, "error: repeat timeprecision does not match " "the initial module timeprecision " "declaration."); @@ -808,26 +814,32 @@ verinum* pform_verinum_with_size(verinum*siz, verinum*val, void pform_startmodule(const struct vlltype&loc, const char*name, bool program_block, list*attr) { - assert( pform_cur_module == 0 ); + if (pform_cur_module.size() > 0 && !gn_system_verilog()) { + cerr << loc << ": error: Module definition " << name + << " cannot nest into module " << pform_cur_module.front()->mod_name() << "." << endl; + error_count += 1; + } + perm_string lex_name = lex_strings.make(name); - pform_cur_module = new Module(lex_name); - pform_cur_module->program_block = program_block; + Module*cur_module = new Module(lexical_scope, lex_name); + cur_module->program_block = program_block; /* Set the local time unit/precision to the global value. */ - pform_cur_module->time_unit = pform_time_unit; - pform_cur_module->time_precision = pform_time_prec; + cur_module->time_unit = pform_time_unit; + cur_module->time_precision = pform_time_prec; tu_local_flag = tu_global_flag; tp_local_flag = tp_global_flag; /* If we have a timescale file then the time information is from * a timescale directive. */ - pform_cur_module->time_from_timescale = pform_timescale_file != 0; + cur_module->time_from_timescale = pform_timescale_file != 0; - FILE_NAME(pform_cur_module, loc); - pform_cur_module->library_flag = pform_library_flag; + FILE_NAME(cur_module, loc); + cur_module->library_flag = pform_library_flag; - ivl_assert(*pform_cur_module, lexical_scope == 0); - lexical_scope = pform_cur_module; + pform_cur_module.push_front(cur_module); + + lexical_scope = cur_module; /* The generate scheme numbering starts with *1*, not zero. That's just the way it is, thanks to the standard. */ @@ -836,13 +848,13 @@ void pform_startmodule(const struct vlltype&loc, const char*name, if (warn_timescale && pform_timescale_file && (strcmp(pform_timescale_file,loc.text) != 0)) { - cerr << pform_cur_module->get_fileline() << ": warning: " + cerr << cur_module->get_fileline() << ": warning: " << "timescale for " << name << " inherited from another file." << endl; cerr << pform_timescale_file << ":" << pform_timescale_line << ": ...: The inherited timescale is here." << endl; } - pform_bind_attributes(pform_cur_module->attributes, attr); + pform_bind_attributes(cur_module->attributes, attr); } /* @@ -852,13 +864,12 @@ void pform_startmodule(const struct vlltype&loc, const char*name, */ void pform_check_timeunit_prec() { - assert(pform_cur_module); + assert(pform_cur_module.size() > 0); if ((generation_flag & (GN_VER2005_SV | GN_VER2009)) && - (pform_cur_module->time_unit < pform_cur_module->time_precision)) { - VLerror("error: a timeprecision is missing or is too " - "large!"); - } else assert(pform_cur_module->time_unit >= - pform_cur_module->time_precision); + (pform_cur_module.front()->time_unit < pform_cur_module.front()->time_precision)) { + VLerror("error: a timeprecision is missing or is too large!"); + } else assert(pform_cur_module.front()->time_unit >= + pform_cur_module.front()->time_precision); } /* @@ -881,7 +892,7 @@ Module::port_t* pform_module_port_reference(perm_string name, void pform_module_set_ports(vector*ports) { - assert(pform_cur_module); + assert(pform_cur_module.size() > 0); /* The parser parses ``module foo()'' as having one unconnected port, but it is really a module with no @@ -892,7 +903,7 @@ void pform_module_set_ports(vector*ports) } if (ports != 0) { - pform_cur_module->ports = *ports; + pform_cur_module.front()->ports = *ports; delete ports; } } @@ -900,34 +911,44 @@ void pform_module_set_ports(vector*ports) void pform_endmodule(const char*name, bool inside_celldefine, Module::UCDriveType uc_drive_def) { - assert(pform_cur_module); - pform_cur_module->time_from_timescale = (tu_local_flag && - tp_local_flag) || - (pform_timescale_file != 0); - perm_string mod_name = pform_cur_module->mod_name(); + assert(pform_cur_module.size() > 0); + Module*cur_module = pform_cur_module.front(); + pform_cur_module.pop_front(); + + cur_module->time_from_timescale = (tu_local_flag && tp_local_flag) + || (pform_timescale_file != 0); + perm_string mod_name = cur_module->mod_name(); assert(strcmp(name, mod_name) == 0); - pform_cur_module->is_cell = inside_celldefine; - pform_cur_module->uc_drive = uc_drive_def; + cur_module->is_cell = inside_celldefine; + cur_module->uc_drive = uc_drive_def; + + // If this is a root module, then there is no parent module + // and we try to put this newly defined module into the global + // root list of modules. Otherwise, this is a nested module + // and we put it into the parent module scope to be elaborated + // if needed. + map&use_module_map = (pform_cur_module.size() == 0) + ? pform_modules + : pform_cur_module.front()->nested_modules; map::const_iterator test = - pform_modules.find(mod_name); + use_module_map.find(mod_name); - if (test != pform_modules.end()) { + if (test != use_module_map.end()) { ostringstream msg; msg << "Module " << name << " was already declared here: " - << (*test).second->get_fileline() << endl; + << test->second->get_fileline() << endl; VLerror(msg.str().c_str()); } else { - pform_modules[mod_name] = pform_cur_module; + use_module_map[mod_name] = cur_module; } // The current lexical scope should be this module by now, and // this module should not have a parent lexical scope. - ivl_assert(*pform_cur_module, lexical_scope == pform_cur_module); + ivl_assert(*cur_module, lexical_scope == cur_module); pform_pop_scope(); - ivl_assert(*pform_cur_module, lexical_scope == 0); + ivl_assert(*cur_module, pform_cur_module.size()>0 || lexical_scope == 0); - pform_cur_module = 0; tp_decl_flag = false; tu_decl_flag = false; tu_local_flag = false; @@ -958,7 +979,7 @@ void pform_genvars(const struct vlltype&li, list*names) if (pform_cur_generate) pform_add_genvar(li, *cur, pform_cur_generate->genvars); else - pform_add_genvar(li, *cur, pform_cur_module->genvars); + pform_add_genvar(li, *cur, pform_cur_module.front()->genvars); } delete names; @@ -1113,7 +1134,7 @@ void pform_generate_block_name(char*name) void pform_endgenerate() { assert(pform_cur_generate != 0); - assert(pform_cur_module); + assert(pform_cur_module.size() > 0); // If there is no explicit block name then generate a temporary // name. This will be replaced by the correct name later, once @@ -1137,7 +1158,7 @@ void pform_endgenerate() parent_generate->generate_schemes.push_back(pform_cur_generate); } else { assert(pform_cur_generate->scheme_type != PGenerate::GS_CASE_ITEM); - pform_cur_module->generate_schemes.push_back(pform_cur_generate); + pform_cur_module.front()->generate_schemes.push_back(pform_cur_generate); } pform_cur_generate = parent_generate; } @@ -1593,7 +1614,7 @@ static void pform_make_event(perm_string name, const char*fn, unsigned ln) FILE_NAME(&tloc, fn, ln); cerr << tloc.get_fileline() << ": error: duplicate definition " "for named event '" << name << "' in '" - << pform_cur_module->mod_name() << "'." << endl; + << pform_cur_module.front()->mod_name() << "'." << endl; error_count += 1; } @@ -1653,7 +1674,7 @@ static void pform_makegate(PGBuiltin::Type type, if (pform_cur_generate) pform_cur_generate->add_gate(cur); else - pform_cur_module->add_gate(cur); + pform_cur_module.front()->add_gate(cur); } void pform_makegates(PGBuiltin::Type type, @@ -1719,7 +1740,7 @@ static void pform_make_modgate(perm_string type, if (pform_cur_generate) pform_cur_generate->add_gate(cur); else - pform_cur_module->add_gate(cur); + pform_cur_module.front()->add_gate(cur); } static void pform_make_modgate(perm_string type, @@ -1763,7 +1784,7 @@ static void pform_make_modgate(perm_string type, if (pform_cur_generate) pform_cur_generate->add_gate(cur); else - pform_cur_module->add_gate(cur); + pform_cur_module.front()->add_gate(cur); } void pform_make_modgates(perm_string type, @@ -1833,7 +1854,7 @@ static PGAssign* pform_make_pgassign(PExpr*lval, PExpr*rval, if (pform_cur_generate) pform_cur_generate->add_gate(cur); else - pform_cur_module->add_gate(cur); + pform_cur_module.front()->add_gate(cur); return cur; } @@ -2305,7 +2326,7 @@ void pform_set_attrib(perm_string name, perm_string key, char*value) if (PWire*cur = lexical_scope->wires_find(name)) { cur->attributes[key] = new PEString(value); - } else if (PGate*curg = pform_cur_module->get_gate(name)) { + } else if (PGate*curg = pform_cur_module.front()->get_gate(name)) { curg->attributes[key] = new PEString(value); } else { @@ -2384,23 +2405,23 @@ void pform_set_parameter(const struct vlltype&loc, FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: duplicate definition " "for parameter '" << name << "' in '" - << pform_cur_module->mod_name() << "'." << endl; + << pform_cur_module.front()->mod_name() << "'." << endl; error_count += 1; } if (scope->localparams.find(name) != scope->localparams.end()) { LineInfo tloc; FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: localparam and " - "parameter in '" << pform_cur_module->mod_name() + << "parameter in '" << pform_cur_module.front()->mod_name() << "' have the same name '" << name << "'." << endl; error_count += 1; } - if ((scope == pform_cur_module) && - (pform_cur_module->specparams.find(name) != pform_cur_module->specparams.end())) { + if ((scope == pform_cur_module.front()) && + (pform_cur_module.front()->specparams.find(name) != pform_cur_module.front()->specparams.end())) { LineInfo tloc; FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: specparam and " - "parameter in '" << pform_cur_module->mod_name() + "parameter in '" << pform_cur_module.front()->mod_name() << "' have the same name '" << name << "'." << endl; error_count += 1; } @@ -2426,8 +2447,8 @@ void pform_set_parameter(const struct vlltype&loc, parm.signed_flag = signed_flag; parm.range = value_range; - if (scope == pform_cur_module) - pform_cur_module->param_names.push_back(name); + if (scope == pform_cur_module.front()) + pform_cur_module.front()->param_names.push_back(name); } void pform_set_localparam(const struct vlltype&loc, @@ -2442,23 +2463,23 @@ void pform_set_localparam(const struct vlltype&loc, FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: duplicate definition " "for localparam '" << name << "' in '" - << pform_cur_module->mod_name() << "'." << endl; + << pform_cur_module.front()->mod_name() << "'." << endl; error_count += 1; } if (scope->parameters.find(name) != scope->parameters.end()) { LineInfo tloc; FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: parameter and " - "localparam in '" << pform_cur_module->mod_name() + << "localparam in '" << pform_cur_module.front()->mod_name() << "' have the same name '" << name << "'." << endl; error_count += 1; } - if ((scope == pform_cur_module) && - (pform_cur_module->specparams.find(name) != pform_cur_module->specparams.end())) { + if ((scope == pform_cur_module.front()) && + (pform_cur_module.front()->specparams.find(name) != pform_cur_module.front()->specparams.end())) { LineInfo tloc; FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: specparam and " - "localparam in '" << pform_cur_module->mod_name() + "localparam in '" << pform_cur_module.front()->mod_name() << "' have the same name '" << name << "'." << endl; error_count += 1; } @@ -2488,23 +2509,25 @@ void pform_set_localparam(const struct vlltype&loc, void pform_set_specparam(const struct vlltype&loc, perm_string name, list*range, PExpr*expr) { - Module*scope = pform_cur_module; + assert(pform_cur_module.size() > 0); + Module*scope = pform_cur_module.front(); assert(scope == lexical_scope); // Check if the specparam name is already in the dictionary. - if (scope->specparams.find(name) != scope->specparams.end()) { + if (pform_cur_module.front()->specparams.find(name) != + pform_cur_module.front()->specparams.end()) { LineInfo tloc; FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: duplicate definition " "for specparam '" << name << "' in '" - << pform_cur_module->mod_name() << "'." << endl; + << pform_cur_module.front()->mod_name() << "'." << endl; error_count += 1; } if (scope->parameters.find(name) != scope->parameters.end()) { LineInfo tloc; FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: parameter and " - "specparam in '" << pform_cur_module->mod_name() + "specparam in '" << pform_cur_module.front()->mod_name() << "' have the same name '" << name << "'." << endl; error_count += 1; } @@ -2512,13 +2535,14 @@ void pform_set_specparam(const struct vlltype&loc, perm_string name, LineInfo tloc; FILE_NAME(&tloc, loc); cerr << tloc.get_fileline() << ": error: localparam and " - "specparam in '" << pform_cur_module->mod_name() + "specparam in '" << pform_cur_module.front()->mod_name() << "' have the same name '" << name << "'." << endl; error_count += 1; } assert(expr); - Module::param_expr_t&parm = scope->specparams[name]; + + Module::param_expr_t&parm = pform_cur_module.front()->specparams[name]; FILE_NAME(&parm, loc); parm.expr = expr; @@ -2542,7 +2566,7 @@ void pform_set_specparam(const struct vlltype&loc, perm_string name, void pform_set_defparam(const pform_name_t&name, PExpr*expr) { assert(expr); - pform_cur_module->defparms.push_back(make_pair(name,expr)); + pform_cur_module.front()->defparms.push_back(make_pair(name,expr)); } /* @@ -2609,7 +2633,7 @@ extern void pform_module_specify_path(PSpecPath*obj) { if (obj == 0) return; - pform_cur_module->specify_paths.push_back(obj); + pform_cur_module.front()->specify_paths.push_back(obj); } @@ -2860,8 +2884,8 @@ PProcess* pform_make_behavior(ivl_process_type_t type, Statement*st, pform_put_behavior_in_scope(pp); - ivl_assert(*st, pform_cur_module); - if (pform_cur_module->program_block && type == IVL_PR_ALWAYS) { + ivl_assert(*st, pform_cur_module.size() > 0); + if (pform_cur_module.front()->program_block && type == IVL_PR_ALWAYS) { cerr << st->get_fileline() << ": error: Always statements not allowed" << " in program blocks." << endl; error_count += 1; @@ -2908,10 +2932,3 @@ int pform_parse(const char*path, FILE*file) destroy_lexor(); return error_count; } - -void pform_error_nested_modules() -{ - assert( pform_cur_module != 0 ); - cerr << pform_cur_module->get_fileline() << ": error: original module " - "(" << pform_cur_module->mod_name() << ") defined here." << endl; -} diff --git a/pform.h b/pform.h index c3b292871..a1892cbe9 100644 --- a/pform.h +++ b/pform.h @@ -424,12 +424,6 @@ extern PAssign* pform_compressed_assign_from_inc_dec(const struct vlltype&loc, */ extern void pform_dump(ostream&out, Module*mod); -/* - * Used to report the original module location when a nested module - * (missing endmodule) is found by the parser. - */ -extern void pform_error_nested_modules(); - /* ** pform_discipline.cc * Functions for handling the parse of natures and disciplines. These * functions are in pform_disciplines.cc diff --git a/pform_dump.cc b/pform_dump.cc index 364a26b67..12f2db915 100644 --- a/pform_dump.cc +++ b/pform_dump.cc @@ -1249,6 +1249,11 @@ void Module::dump(ostream&out) const out << ")" << endl; } + for (map::const_iterator cur = nested_modules.begin() + ; cur != nested_modules.end() ; ++cur) { + out << setw(4) << "" << "Nested module " << cur->first << ";" << endl; + } + dump_typedefs_(out, 4); dump_parameters_(out, 4); diff --git a/t-dll.cc b/t-dll.cc index 0bee8e88d..ed644be40 100644 --- a/t-dll.cc +++ b/t-dll.cc @@ -2297,6 +2297,7 @@ void dll_target::scope(const NetScope*net) switch (net->type()) { case NetScope::MODULE: + case NetScope::NESTED_MODULE: scop->type_ = IVL_SCT_MODULE; scop->tname_ = net->module_name(); scop->ports = net->module_ports(); From 8154ce2a4a797fd29bc5e4dfa3fb555247c3e220 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Sun, 13 May 2012 17:48:47 -0700 Subject: [PATCH 4/9] Reword how we enforce program block constraints Making the scope type NESTED_MODULE was just plain wrong, because it didn't really encapsulate the meaning of program blocks OR nested modules. So instead create nested_module() and program_block() flags and use those to test scope constraints. --- design_dump.cc | 3 +++ elab_net.cc | 2 +- elab_scope.cc | 6 ++++-- elab_sig.cc | 6 +++--- elaborate.cc | 45 +++++++++++++++++++++++++++++++++++++++++++-- net_design.cc | 5 +++-- net_scope.cc | 17 +++++++---------- netlist.h | 16 ++++++++++++---- nodangle.cc | 1 - parse.y | 10 ---------- symbol_search.cc | 2 +- t-dll.cc | 1 - 12 files changed, 77 insertions(+), 37 deletions(-) diff --git a/design_dump.cc b/design_dump.cc index 1518f1f21..ce16ec862 100644 --- a/design_dump.cc +++ b/design_dump.cc @@ -1136,6 +1136,9 @@ void NetScope::dump(ostream&o) const print_type(o); if (is_auto()) o << " (automatic)"; if (is_cell()) o << " (cell)"; + if (nested_module()) o << " (nested)"; + if (program_block()) o << " (program)"; + o << endl; for (unsigned idx = 0 ; idx < attr_cnt() ; idx += 1) diff --git a/elab_net.cc b/elab_net.cc index 4d70820b7..fa4a3fd2a 100644 --- a/elab_net.cc +++ b/elab_net.cc @@ -693,7 +693,7 @@ NetNet* PEIdent::elaborate_bi_net(Design*des, NetScope*scope) const */ NetNet* PEIdent::elaborate_port(Design*des, NetScope*scope) const { - assert(scope->type_is_module()); + ivl_assert(*this, scope->type() == NetScope::MODULE); NetNet*sig = des->find_signal(scope, path_); if (sig == 0) { cerr << get_fileline() << ": error: no wire/reg " << path_ diff --git a/elab_scope.cc b/elab_scope.cc index eb17273ed..685dfac2c 100644 --- a/elab_scope.cc +++ b/elab_scope.cc @@ -1225,7 +1225,7 @@ void PGModule::elaborate_scope_mod_(Design*des, Module*mod, NetScope*sc) const continue; } - if (! scn->type_is_module()) continue; + if (scn->type() != NetScope::MODULE) continue; if (strcmp(mod->mod_name(), scn->module_name()) != 0) continue; @@ -1345,7 +1345,9 @@ void PGModule::elaborate_scope_mod_instances_(Design*des, Module*mod, NetScope*s // Create the new scope as a MODULE with my name. Note // that if this is a nested module, mark it thus so that // scope searches will continue into the parent scope. - NetScope*my_scope = new NetScope(sc, use_name, bound_type_? NetScope::NESTED_MODULE : NetScope::MODULE); + NetScope*my_scope = new NetScope(sc, use_name, NetScope::MODULE, + bound_type_? true : false, + mod->program_block); my_scope->set_line(get_file(), mod->get_file(), get_lineno(), mod->get_lineno()); my_scope->set_module_name(mod->mod_name()); diff --git a/elab_sig.cc b/elab_sig.cc index 90969c2fa..9f2e6b60e 100644 --- a/elab_sig.cc +++ b/elab_sig.cc @@ -98,7 +98,7 @@ bool PScope::elaborate_sig_wires_(Design*des, NetScope*scope) const reg, then report an error. */ if (sig && (sig->scope() == scope) - && (scope->type_is_module()) + && (scope->type() == NetScope::MODULE) && (sig->port_type() == NetNet::PINPUT) && (sig->type() == NetNet::REG)) { @@ -110,7 +110,7 @@ bool PScope::elaborate_sig_wires_(Design*des, NetScope*scope) const } if (sig && (sig->scope() == scope) - && (scope->type_is_module()) + && (scope->type() == NetScope::MODULE) && (sig->port_type() == NetNet::PINOUT) && (sig->type() == NetNet::REG)) { @@ -122,7 +122,7 @@ bool PScope::elaborate_sig_wires_(Design*des, NetScope*scope) const } if (sig && (sig->scope() == scope) - && scope->type_is_module() + && (scope->type() == NetScope::MODULE) && (sig->port_type() == NetNet::PINOUT) && (sig->data_type() == IVL_VT_REAL)) { diff --git a/elaborate.cc b/elaborate.cc index fa309110d..25767e5c7 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -2312,6 +2312,18 @@ NetProc* PAssign::elaborate_compressed_(Design*des, NetScope*scope) const return cur; } +static bool lval_not_program_variable(const NetAssign_*lv) +{ + while (lv) { + NetScope*sig_scope = lv->sig()->scope(); + if (! sig_scope->program_block()) + return true; + + lv = lv->more; + } + return false; +} + NetProc* PAssign::elaborate(Design*des, NetScope*scope) const { assert(scope); @@ -2326,6 +2338,12 @@ NetProc* PAssign::elaborate(Design*des, NetScope*scope) const NetAssign_*lv = elaborate_lval(des, scope); if (lv == 0) return 0; + if (scope->program_block() && lval_not_program_variable(lv)) { + cerr << get_fileline() << ": error: Blocking assignments to " + << "non-program variables are not allowed." << endl; + des->errors += 1; + } + /* If there is an internal delay expression, elaborate it. */ NetExpr*delay = 0; if (delay_ != 0) @@ -2469,6 +2487,22 @@ NetProc* PAssign::elaborate(Design*des, NetScope*scope) const return cur; } +/* + * Return true if any lvalue parts are in a program block scope. + */ +static bool lval_is_program_variable(const NetAssign_*lv) +{ + while (lv) { + NetScope*sig_scope = lv->sig()->scope(); + if (sig_scope->program_block()) + return true; + + lv = lv->more; + } + + return false; +} + /* * Elaborate non-blocking assignments. The statement is of the general * form: @@ -2505,6 +2539,14 @@ NetProc* PAssignNB::elaborate(Design*des, NetScope*scope) const NetAssign_*lv = elaborate_lval(des, scope); if (lv == 0) return 0; + if (scope->program_block() && lval_is_program_variable(lv)) { + cerr << get_fileline() << ": error: Non-blocking assignments to " + << "program variables are not allowed." << endl; + des->errors += 1; + // This is an error, but we can let elaboration continue + // because it would necessarily trigger other errors. + } + NetExpr*rv = elaborate_rval_(des, scope, count_lval_width(lv), lv->expr_type()); if (rv == 0) return 0; @@ -3256,7 +3298,6 @@ NetProc* PDisable::elaborate(Design*des, NetScope*scope) const return 0; case NetScope::MODULE: - case NetScope::NESTED_MODULE: cerr << get_fileline() << ": error: Cannot disable modules." << endl; des->errors += 1; return 0; @@ -4849,7 +4890,7 @@ Design* elaborate(listroots) // Make the root scope. This makes a NetScope object and // pushes it into the list of root scopes in the Design. - NetScope*scope = des->make_root_scope(*root); + NetScope*scope = des->make_root_scope(*root, rmod->program_block); // Collect some basic properties of this scope from the // Module definition. diff --git a/net_design.cc b/net_design.cc index 0ea857cf4..216a7fc20 100644 --- a/net_design.cc +++ b/net_design.cc @@ -101,10 +101,11 @@ uint64_t Design::scale_to_precision(uint64_t val, return val; } -NetScope* Design::make_root_scope(perm_string root) +NetScope* Design::make_root_scope(perm_string root, bool program_block) { NetScope *root_scope_; - root_scope_ = new NetScope(0, hname_t(root), NetScope::MODULE); + root_scope_ = new NetScope(0, hname_t(root), NetScope::MODULE, + false, program_block); /* This relies on the fact that the basename return value is permallocated. */ root_scope_->set_module_name(root_scope_->basename()); diff --git a/net_scope.cc b/net_scope.cc index 22a67e6fc..47d4751ce 100644 --- a/net_scope.cc +++ b/net_scope.cc @@ -38,8 +38,8 @@ class PExpr; * in question. */ -NetScope::NetScope(NetScope*up, const hname_t&n, NetScope::TYPE t) -: type_(t), name_(n), up_(up) +NetScope::NetScope(NetScope*up, const hname_t&n, NetScope::TYPE t, bool nest, bool prog) +: type_(t), name_(n), nested_module_(nest), program_block_(prog), up_(up) { events_ = 0; lcounter_ = 0; @@ -302,9 +302,6 @@ void NetScope::print_type(ostream&stream) const case FUNC: stream << "function"; break; - case NESTED_MODULE: - stream << "nested_module <" << (module_name_ ? module_name_.str() : "") - << "> instance"; case MODULE: stream << "module <" << (module_name_ ? module_name_.str() : "") << "> instance"; @@ -363,31 +360,31 @@ const NetFuncDef* NetScope::func_def() const void NetScope::set_module_name(perm_string n) { - assert(type_ == MODULE || type_ == NESTED_MODULE); + assert(type_ == MODULE); module_name_ = n; /* NOTE: n must have been permallocated. */ } perm_string NetScope::module_name() const { - assert(type_ == MODULE || type_ == NESTED_MODULE); + assert(type_ == MODULE); return module_name_; } void NetScope::add_module_port(NetNet*port) { - assert(type_ == MODULE || type_ == NESTED_MODULE); + assert(type_ == MODULE); ports_.push_back(port); } unsigned NetScope::module_ports() const { - assert(type_ == MODULE || type_ == NESTED_MODULE); + assert(type_ == MODULE); return ports_.size(); } NetNet* NetScope::module_port(unsigned idx) const { - assert(type_ == MODULE || type_ == NESTED_MODULE); + assert(type_ == MODULE); assert(idx < ports_.size()); return ports_[idx]; } diff --git a/netlist.h b/netlist.h index ddfc70e3d..284d28896 100644 --- a/netlist.h +++ b/netlist.h @@ -724,11 +724,11 @@ extern std::ostream&operator << (std::ostream&out, const std::list&r class NetScope : public Attrib { public: - enum TYPE { MODULE, NESTED_MODULE, TASK, FUNC, BEGIN_END, FORK_JOIN, GENBLOCK }; + enum TYPE { MODULE, TASK, FUNC, BEGIN_END, FORK_JOIN, GENBLOCK }; /* Create a new scope, and attach it to the given parent. The name is expected to have been permallocated. */ - NetScope(NetScope*up, const hname_t&name, TYPE t); + NetScope(NetScope*up, const hname_t&name, TYPE t, bool nest=false, bool prog=false); ~NetScope(); /* Rename the scope using the name generated by inserting as @@ -805,8 +805,11 @@ class NetScope : public Attrib { const NetScope* parent() const { return up_; } const NetScope* child(const hname_t&name) const; + // Nested modules have slightly different scope search rules. + inline bool nested_module() const { return nested_module_; } + // Program blocks have elaboration constraints. + inline bool program_block() const { return program_block_; } TYPE type() const; - bool type_is_module() const { return type()==MODULE || type()==NESTED_MODULE; } void print_type(ostream&) const; void set_task_def(NetTaskDef*); @@ -989,6 +992,11 @@ class NetScope : public Attrib { TYPE type_; hname_t name_; + // True if the scope is a nested module/program block + bool nested_module_; + // True if the scope is a program block + bool program_block_; + perm_string file_; perm_string def_file_; unsigned lineno_; @@ -4012,7 +4020,7 @@ class Design { const char* get_flag(const string&key) const; - NetScope* make_root_scope(perm_string name); + NetScope* make_root_scope(perm_string name, bool program_block); NetScope* find_root_scope(); list find_root_scopes(); diff --git a/nodangle.cc b/nodangle.cc index 229d21214..6e01d4aba 100644 --- a/nodangle.cc +++ b/nodangle.cc @@ -136,7 +136,6 @@ void nodangle_f::signal(Design*, NetNet*sig) if ((sig->port_type() != NetNet::NOT_A_PORT) && ((sig->scope()->type() == NetScope::TASK) || (sig->scope()->type() == NetScope::FUNC) || - (sig->scope()->type() == NetScope::NESTED_MODULE) || (sig->scope()->type() == NetScope::MODULE))) return; diff --git a/parse.y b/parse.y index 185038531..736dacf77 100644 --- a/parse.y +++ b/parse.y @@ -5489,15 +5489,11 @@ statement_item /* This is roughly statement_item in the LRM */ { PAssignNB*tmp = new PAssignNB($1,$3); FILE_NAME(tmp, @1); $$ = tmp; - if (pform_in_program_block()) - yyerror(@2, "Non-blocking assignments not permitted in program blocks."); } | error K_LE expression ';' { yyerror(@2, "Syntax in assignment statement l-value."); yyerrok; $$ = new PNoop; - if (pform_in_program_block()) - yyerror(@2, "Non-blocking assignments not permitted in program blocks."); } | lpvalue '=' delay1 expression ';' { PExpr*del = $3->front(); $3->pop_front(); @@ -5512,8 +5508,6 @@ statement_item /* This is roughly statement_item in the LRM */ PAssignNB*tmp = new PAssignNB($1,del,$4); FILE_NAME(tmp, @1); $$ = tmp; - if (pform_in_program_block()) - yyerror(@2, "Non-blocking assignments not permitted in program blocks."); } | lpvalue '=' event_control expression ';' { PAssign*tmp = new PAssign($1,0,$3,$4); @@ -5530,15 +5524,11 @@ statement_item /* This is roughly statement_item in the LRM */ { PAssignNB*tmp = new PAssignNB($1,0,$3,$4); FILE_NAME(tmp, @1); $$ = tmp; - if (pform_in_program_block()) - yyerror(@2, "Non-blocking assignments not permitted in program blocks."); } | lpvalue K_LE K_repeat '(' expression ')' event_control expression ';' { PAssignNB*tmp = new PAssignNB($1,$5,$7,$8); FILE_NAME(tmp, @1); $$ = tmp; - if (pform_in_program_block()) - yyerror(@2, "Non-blocking assignments not permitted in program blocks."); } /* The IEEE1800 standard defines dynamic_array_new assignment as a diff --git a/symbol_search.cc b/symbol_search.cc index d2d6b118c..a4a7a9d25 100644 --- a/symbol_search.cc +++ b/symbol_search.cc @@ -79,7 +79,7 @@ NetScope*symbol_search(const LineInfo*li, Design*des, NetScope*scope, /* We can't look up if we are at the enclosing module scope * or if a hierarchical path was given. */ - if ((scope->type() == NetScope::MODULE) || hier_path) + if ((scope->type()==NetScope::MODULE && !scope->nested_module()) || hier_path) scope = 0; else scope = scope->parent(); diff --git a/t-dll.cc b/t-dll.cc index ed644be40..0bee8e88d 100644 --- a/t-dll.cc +++ b/t-dll.cc @@ -2297,7 +2297,6 @@ void dll_target::scope(const NetScope*net) switch (net->type()) { case NetScope::MODULE: - case NetScope::NESTED_MODULE: scop->type_ = IVL_SCT_MODULE; scop->tname_ = net->module_name(); scop->ports = net->module_ports(); From c0f35cbe622c98917e80c1e2e8f346b18d888901 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Mon, 14 May 2012 19:13:57 -0700 Subject: [PATCH 5/9] Disallow modules/gates in program blocks. --- parse.y | 88 ++++++++++++++++++++++++++------------------------------ pform.cc | 23 +++++++++++++-- pform.h | 6 ++-- 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/parse.y b/parse.y index 736dacf77..219a1960b 100644 --- a/parse.y +++ b/parse.y @@ -1082,6 +1082,14 @@ integer_vector_type /* IEEE1800-2005: A.2.2.1 */ | K_bool { $$ = IVL_VT_BOOL; } /* Icarus Verilog xtypes extension */ ; +join_keyword /* IEEE1800-2005: A.6.3 */ + : K_join + | K_join_none + { yyerror(@1, "sorry: join_none not supported."); } + | K_join_any + { yyerror(@1, "sorry: join_any not supported."); } + ; + jump_statement /* IEEE1800-2005: A.6.5 */ : K_break ';' { yyerror(@1, "sorry: break statements not supported."); @@ -4056,66 +4064,50 @@ module_item two/three-value delay. These rules handle the different cases. We check that the actual number of delays is correct later. */ - | attribute_list_opt gatetype gate_instance_list ';' - { pform_makegates($2, str_strength, 0, $3, $1); - } + | attribute_list_opt gatetype gate_instance_list ';' + { pform_makegates(@2, $2, str_strength, 0, $3, $1); } - | attribute_list_opt gatetype delay3 gate_instance_list ';' - { pform_makegates($2, str_strength, $3, $4, $1); - } + | attribute_list_opt gatetype delay3 gate_instance_list ';' + { pform_makegates(@2, $2, str_strength, $3, $4, $1); } - | attribute_list_opt gatetype drive_strength gate_instance_list ';' - { pform_makegates($2, $3, 0, $4, $1); - } + | attribute_list_opt gatetype drive_strength gate_instance_list ';' + { pform_makegates(@2, $2, $3, 0, $4, $1); } - | attribute_list_opt gatetype drive_strength delay3 gate_instance_list ';' - { pform_makegates($2, $3, $4, $5, $1); - } + | attribute_list_opt gatetype drive_strength delay3 gate_instance_list ';' + { pform_makegates(@2, $2, $3, $4, $5, $1); } /* The switch type gates do not support a strength. */ - | attribute_list_opt switchtype gate_instance_list ';' - { pform_makegates($2, str_strength, 0, $3, $1); - } + | attribute_list_opt switchtype gate_instance_list ';' + { pform_makegates(@2, $2, str_strength, 0, $3, $1); } - | attribute_list_opt switchtype delay3 gate_instance_list ';' - { pform_makegates($2, str_strength, $3, $4, $1); - } + | attribute_list_opt switchtype delay3 gate_instance_list ';' + { pform_makegates(@2, $2, str_strength, $3, $4, $1); } /* Pullup and pulldown devices cannot have delays, and their strengths are limited. */ - | K_pullup gate_instance_list ';' - { pform_makegates(PGBuiltin::PULLUP, pull_strength, 0, - $2, 0); - } - | K_pulldown gate_instance_list ';' - { pform_makegates(PGBuiltin::PULLDOWN, pull_strength, - 0, $2, 0); - } + | K_pullup gate_instance_list ';' + { pform_makegates(@1, PGBuiltin::PULLUP, pull_strength, 0, $2, 0); } + | K_pulldown gate_instance_list ';' + { pform_makegates(@1, PGBuiltin::PULLDOWN, pull_strength, 0, $2, 0); } - | K_pullup '(' dr_strength1 ')' gate_instance_list ';' - { pform_makegates(PGBuiltin::PULLUP, $3, 0, $5, 0); - } + | K_pullup '(' dr_strength1 ')' gate_instance_list ';' + { pform_makegates(@1, PGBuiltin::PULLUP, $3, 0, $5, 0); } - | K_pullup '(' dr_strength1 ',' dr_strength0 ')' gate_instance_list ';' - { pform_makegates(PGBuiltin::PULLUP, $3, 0, $7, 0); - } + | K_pullup '(' dr_strength1 ',' dr_strength0 ')' gate_instance_list ';' + { pform_makegates(@1, PGBuiltin::PULLUP, $3, 0, $7, 0); } - | K_pullup '(' dr_strength0 ',' dr_strength1 ')' gate_instance_list ';' - { pform_makegates(PGBuiltin::PULLUP, $5, 0, $7, 0); - } + | K_pullup '(' dr_strength0 ',' dr_strength1 ')' gate_instance_list ';' + { pform_makegates(@1, PGBuiltin::PULLUP, $5, 0, $7, 0); } - | K_pulldown '(' dr_strength0 ')' gate_instance_list ';' - { pform_makegates(PGBuiltin::PULLDOWN, $3, 0, $5, 0); - } + | K_pulldown '(' dr_strength0 ')' gate_instance_list ';' + { pform_makegates(@1, PGBuiltin::PULLDOWN, $3, 0, $5, 0); } - | K_pulldown '(' dr_strength1 ',' dr_strength0 ')' gate_instance_list ';' - { pform_makegates(PGBuiltin::PULLDOWN, $5, 0, $7, 0); - } + | K_pulldown '(' dr_strength1 ',' dr_strength0 ')' gate_instance_list ';' + { pform_makegates(@1, PGBuiltin::PULLDOWN, $5, 0, $7, 0); } - | K_pulldown '(' dr_strength0 ',' dr_strength1 ')' gate_instance_list ';' - { pform_makegates(PGBuiltin::PULLDOWN, $3, 0, $7, 0); - } + | K_pulldown '(' dr_strength0 ',' dr_strength1 ')' gate_instance_list ';' + { pform_makegates(@1, PGBuiltin::PULLDOWN, $3, 0, $7, 0); } /* This rule handles instantiations of modules and user defined primitives. These devices to not have delay lists or strengths, @@ -4124,7 +4116,7 @@ module_item | attribute_list_opt IDENTIFIER parameter_value_opt gate_instance_list ';' { perm_string tmp1 = lex_strings.make($2); - pform_make_modgates(tmp1, $3, $4); + pform_make_modgates(@2, tmp1, $3, $4); delete[]$2; if ($1) delete $1; } @@ -5342,12 +5334,12 @@ statement_item /* This is roughly statement_item in the LRM */ need to do is remember that this is a parallel block so that the code generator can do the right thing. */ - | K_fork K_join + | K_fork join_keyword { PBlock*tmp = new PBlock(PBlock::BL_PAR); FILE_NAME(tmp, @1); $$ = tmp; } - | K_fork statement_or_null_list K_join + | K_fork statement_or_null_list join_keyword { PBlock*tmp = new PBlock(PBlock::BL_PAR); FILE_NAME(tmp, @1); tmp->set_statement(*$2); @@ -5360,7 +5352,7 @@ statement_item /* This is roughly statement_item in the LRM */ current_block_stack.push(tmp); } block_item_decls_opt - statement_or_null_list_opt K_join + statement_or_null_list_opt join_keyword { pform_pop_scope(); assert(! current_block_stack.empty()); PBlock*tmp = current_block_stack.top(); diff --git a/pform.cc b/pform.cc index 726cead0d..ee878a868 100644 --- a/pform.cc +++ b/pform.cc @@ -820,6 +820,10 @@ void pform_startmodule(const struct vlltype&loc, const char*name, error_count += 1; } + if (gn_system_verilog() && pform_cur_module.size() > 0 && pform_cur_module.front()->program_block) { + cerr << loc << ": error: Program blocks cannot contain nested modules/program blocks." << endl; + error_count += 1; + } perm_string lex_name = lex_strings.make(name); Module*cur_module = new Module(lexical_scope, lex_name); @@ -1677,12 +1681,20 @@ static void pform_makegate(PGBuiltin::Type type, pform_cur_module.front()->add_gate(cur); } -void pform_makegates(PGBuiltin::Type type, +void pform_makegates(const struct vlltype&loc, + PGBuiltin::Type type, struct str_pair_t str, list*delay, svector*gates, list*attr) { + assert(pform_cur_module.size() > 0); + if (pform_cur_module.front()->program_block) { + cerr << loc << ": error: Gates and switches may not be instantiated" + << " in program blocks." << endl; + error_count += 1; + } + for (unsigned idx = 0 ; idx < gates->count() ; idx += 1) { pform_makegate(type, str, delay, (*gates)[idx], attr); } @@ -1787,10 +1799,17 @@ static void pform_make_modgate(perm_string type, pform_cur_module.front()->add_gate(cur); } -void pform_make_modgates(perm_string type, +void pform_make_modgates(const struct vlltype&loc, + perm_string type, struct parmvalue_t*overrides, svector*gates) { + assert(pform_cur_module.size() > 0); + if (pform_cur_module.front()->program_block) { + cerr << loc << ": error: Module instantiations are not allowed" + << " in program blocks." << endl; + error_count += 1; + } for (unsigned idx = 0 ; idx < gates->count() ; idx += 1) { lgate cur = (*gates)[idx]; diff --git a/pform.h b/pform.h index a1892cbe9..23d45c558 100644 --- a/pform.h +++ b/pform.h @@ -377,13 +377,15 @@ extern void pform_make_reals(list*names, * The makegate function creates a new gate (which need not have a * name) and connects it to the specified wires. */ -extern void pform_makegates(PGBuiltin::Type type, +extern void pform_makegates(const struct vlltype&loc, + PGBuiltin::Type type, struct str_pair_t str, list*delay, svector*gates, list*attr); -extern void pform_make_modgates(perm_string type, +extern void pform_make_modgates(const struct vlltype&loc, + perm_string type, struct parmvalue_t*overrides, svector*gates); From 6a57764e0ec48cc89f10afd4e762dae9d17d5e87 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Sat, 19 May 2012 17:34:13 -0700 Subject: [PATCH 6/9] Elaborate fork-join_none and fork-join_any statements. --- Statement.cc | 9 ++++++++- Statement.h | 10 +++++++--- design_dump.cc | 6 ++++++ elab_scope.cc | 4 +++- elaborate.cc | 18 +++++++++++++++--- ivl_target.h | 10 ++++++---- net_nex_input.cc | 4 ++-- netlist.h | 2 +- parse.y | 13 +++++++++---- t-dll-api.cc | 8 +++++++- t-dll-proc.cc | 19 +++++++++++++++---- tgt-stub/statement.c | 24 +++++++++++++++++++++++- 12 files changed, 102 insertions(+), 25 deletions(-) diff --git a/Statement.cc b/Statement.cc index a99e8b37e..64df85ebd 100644 --- a/Statement.cc +++ b/Statement.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2008,2010 Stephen Williams (steve@icarus.com) + * Copyright (c) 1998-2008,2010,2012 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -114,6 +114,13 @@ PBlock::~PBlock() delete list_[idx]; } +void PBlock::set_join_type(PBlock::BL_TYPE type) +{ + assert(bl_type_ == BL_PAR); + assert(type == BL_JOIN_NONE || type==BL_JOIN_ANY); + bl_type_ = type; +} + void PBlock::set_statement(const vector&st) { list_ = st; diff --git a/Statement.h b/Statement.h index 049aa118e..48c58a800 100644 --- a/Statement.h +++ b/Statement.h @@ -1,7 +1,7 @@ #ifndef __Statement_H #define __Statement_H /* - * Copyright (c) 1998-2008 Stephen Williams (steve@icarus.com) + * Copyright (c) 1998-2008,2012 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -165,7 +165,7 @@ class PAssignNB : public PAssign_ { class PBlock : public PScope, public Statement { public: - enum BL_TYPE { BL_SEQ, BL_PAR }; + enum BL_TYPE { BL_SEQ, BL_PAR, BL_JOIN_NONE, BL_JOIN_ANY }; // If the block has a name, it is a scope and also has a parent. explicit PBlock(perm_string n, LexicalScope*parent, BL_TYPE t); @@ -175,6 +175,10 @@ class PBlock : public PScope, public Statement { BL_TYPE bl_type() const { return bl_type_; } + // If the bl_type() is BL_PAR, it is possible to replace it + // with JOIN_NONE or JOIN_ANY. This is to help the parser. + void set_join_type(BL_TYPE); + void set_statement(const std::vector&st); virtual void dump(ostream&out, unsigned ind) const; @@ -183,7 +187,7 @@ class PBlock : public PScope, public Statement { virtual void elaborate_sig(Design*des, NetScope*scope) const; private: - const BL_TYPE bl_type_; + BL_TYPE bl_type_; std::vectorlist_; }; diff --git a/design_dump.cc b/design_dump.cc index ce16ec862..9d5b3b678 100644 --- a/design_dump.cc +++ b/design_dump.cc @@ -40,6 +40,12 @@ static ostream& operator<< (ostream&o, NetBlock::Type t) case NetBlock::PARA: o << "fork"; break; + case NetBlock::PARA_JOIN_NONE: + o << "fork-join_none"; + break; + case NetBlock::PARA_JOIN_ANY: + o << "fork-join_any"; + break; } return o; } diff --git a/elab_scope.cc b/elab_scope.cc index 685dfac2c..796f60633 100644 --- a/elab_scope.cc +++ b/elab_scope.cc @@ -1564,7 +1564,9 @@ void PBlock::elaborate_scope(Design*des, NetScope*scope) const << "Elaborate block scope " << use_name << " within " << scope_path(scope) << endl; - my_scope = new NetScope(scope, use_name, bl_type_==BL_PAR + // The scope type is begin-end or fork-join. The + // sub-types of fork-join are not interesting to the scope. + my_scope = new NetScope(scope, use_name, bl_type_!=BL_SEQ ? NetScope::FORK_JOIN : NetScope::BEGIN_END); my_scope->set_line(get_file(), get_lineno()); diff --git a/elaborate.cc b/elaborate.cc index 25767e5c7..319051b18 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -2633,9 +2633,21 @@ NetProc* PBlock::elaborate(Design*des, NetScope*scope) const { assert(scope); - NetBlock::Type type = (bl_type_==PBlock::BL_PAR) - ? NetBlock::PARA - : NetBlock::SEQU; + NetBlock::Type type; + switch (bl_type_) { + case PBlock::BL_SEQ: + type = NetBlock::SEQU; + break; + case PBlock::BL_PAR: + type = NetBlock::PARA; + break; + case PBlock::BL_JOIN_NONE: + type = NetBlock::PARA_JOIN_NONE; + break; + case PBlock::BL_JOIN_ANY: + type = NetBlock::PARA_JOIN_ANY; + break; + } NetScope*nscope = 0; if (pscope_name() != 0) { diff --git a/ivl_target.h b/ivl_target.h index fddcf86f5..2933ac0e6 100644 --- a/ivl_target.h +++ b/ivl_target.h @@ -1,7 +1,7 @@ #ifndef __ivl_target_H #define __ivl_target_H /* - * Copyright (c) 2000-2011 Stephen Williams (steve@icarus.com) + * Copyright (c) 2000-2012 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -406,6 +406,8 @@ typedef enum ivl_statement_type_e { IVL_ST_FORCE = 14, IVL_ST_FOREVER = 15, IVL_ST_FORK = 16, + IVL_ST_FORK_JOIN_ANY = 28, + IVL_ST_FORK_JOIN_NONE = 29, IVL_ST_FREE = 26, IVL_ST_RELEASE = 17, IVL_ST_REPEAT = 18, @@ -2069,11 +2071,11 @@ extern unsigned ivl_stmt_lineno(ivl_statement_t net); * triggers. The statement waits even if the sub-statement is nul. */ - /* IVL_ST_BLOCK, IVL_ST_FORK */ + /* IVL_ST_BLOCK, IVL_ST_FORK, IVL_ST_FORK_JOIN_ANY, IVL_ST_FORK_JOIN_NONE */ extern unsigned ivl_stmt_block_count(ivl_statement_t net); - /* IVL_ST_BLOCK, IVL_ST_FORK */ + /* IVL_ST_BLOCK, IVL_ST_FORK, IVL_ST_FORK_JOIN_ANY, IVL_ST_FORK_JOIN_NONE */ extern ivl_scope_t ivl_stmt_block_scope(ivl_statement_t net); - /* IVL_ST_BLOCK, IVL_ST_FORK */ + /* IVL_ST_BLOCK, IVL_ST_FORK, IVL_ST_FORK_JOIN_ANY, IVL_ST_FORK_JOIN_NONE */ extern ivl_statement_t ivl_stmt_block_stmt(ivl_statement_t net, unsigned i); /* IVL_ST_UTASK IVL_ST_DISABLE */ extern ivl_scope_t ivl_stmt_call(ivl_statement_t net); diff --git a/net_nex_input.cc b/net_nex_input.cc index 5642e6164..784ff9d01 100644 --- a/net_nex_input.cc +++ b/net_nex_input.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2011 Stephen Williams (steve@icarus.com) + * Copyright (c) 2002-2012 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -257,7 +257,7 @@ NexusSet* NetBlock::nex_input(bool rem_out) if (last_ == 0) return new NexusSet; - if (type_ == PARA) { + if (type_ != SEQU) { cerr << get_fileline() << ": internal error: Sorry, " << "I don't know how to synthesize fork/join blocks." << endl; diff --git a/netlist.h b/netlist.h index 284d28896..3dc0a628c 100644 --- a/netlist.h +++ b/netlist.h @@ -2491,7 +2491,7 @@ class NetAssignNB : public NetAssignBase { class NetBlock : public NetProc { public: - enum Type { SEQU, PARA }; + enum Type { SEQU, PARA, PARA_JOIN_ANY, PARA_JOIN_NONE }; NetBlock(Type t, NetScope*subscope); ~NetBlock(); diff --git a/parse.y b/parse.y index 219a1960b..27c8d7e5a 100644 --- a/parse.y +++ b/parse.y @@ -356,6 +356,7 @@ static void current_function_set_statement(const YYLTYPE&loc, vector PGBuiltin::Type gatetype; NetNet::PortType porttype; ivl_variable_type_t vartype; + PBlock::BL_TYPE join_keyword; PWire*wire; svector*wires; @@ -574,6 +575,8 @@ static void current_function_set_statement(const YYLTYPE&loc, vector %type analog_statement +%type join_keyword + %type spec_polarity %type specify_path_identifiers @@ -1084,10 +1087,11 @@ integer_vector_type /* IEEE1800-2005: A.2.2.1 */ join_keyword /* IEEE1800-2005: A.6.3 */ : K_join + { $$ = PBlock::BL_PAR; } | K_join_none - { yyerror(@1, "sorry: join_none not supported."); } + { $$ = PBlock::BL_JOIN_NONE; } | K_join_any - { yyerror(@1, "sorry: join_any not supported."); } + { $$ = PBlock::BL_JOIN_ANY; } ; jump_statement /* IEEE1800-2005: A.6.5 */ @@ -5335,12 +5339,12 @@ statement_item /* This is roughly statement_item in the LRM */ code generator can do the right thing. */ | K_fork join_keyword - { PBlock*tmp = new PBlock(PBlock::BL_PAR); + { PBlock*tmp = new PBlock($2); FILE_NAME(tmp, @1); $$ = tmp; } | K_fork statement_or_null_list join_keyword - { PBlock*tmp = new PBlock(PBlock::BL_PAR); + { PBlock*tmp = new PBlock($3); FILE_NAME(tmp, @1); tmp->set_statement(*$2); delete $2; @@ -5357,6 +5361,7 @@ statement_item /* This is roughly statement_item in the LRM */ assert(! current_block_stack.empty()); PBlock*tmp = current_block_stack.top(); current_block_stack.pop(); + tmp->set_join_type($7); if ($6) tmp->set_statement(*$6); delete[]$3; delete $6; diff --git a/t-dll-api.cc b/t-dll-api.cc index 008847127..150258cac 100644 --- a/t-dll-api.cc +++ b/t-dll-api.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2011 Stephen Williams (steve@icarus.com) + * Copyright (c) 2000-2012 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -2271,6 +2271,8 @@ extern "C" ivl_scope_t ivl_stmt_block_scope(ivl_statement_t net) switch (net->type_) { case IVL_ST_BLOCK: case IVL_ST_FORK: + case IVL_ST_FORK_JOIN_ANY: + case IVL_ST_FORK_JOIN_NONE: return net->u_.block_.scope; default: assert(0); @@ -2283,6 +2285,8 @@ extern "C" unsigned ivl_stmt_block_count(ivl_statement_t net) switch (net->type_) { case IVL_ST_BLOCK: case IVL_ST_FORK: + case IVL_ST_FORK_JOIN_ANY: + case IVL_ST_FORK_JOIN_NONE: return net->u_.block_.nstmt_; default: assert(0); @@ -2296,6 +2300,8 @@ extern "C" ivl_statement_t ivl_stmt_block_stmt(ivl_statement_t net, switch (net->type_) { case IVL_ST_BLOCK: case IVL_ST_FORK: + case IVL_ST_FORK_JOIN_ANY: + case IVL_ST_FORK_JOIN_NONE: return net->u_.block_.stmt_ + i; default: assert(0); diff --git a/t-dll-proc.cc b/t-dll-proc.cc index fbc55464e..8874dbb5d 100644 --- a/t-dll-proc.cc +++ b/t-dll-proc.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2011 Stephen Williams (steve@icarus.com) + * Copyright (c) 2000-2012 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -386,9 +386,20 @@ bool dll_target::proc_block(const NetBlock*net) it, so fill in the block fields of the existing statement, and generate the contents for the statement array. */ - stmt_cur_->type_ = (net->type() == NetBlock::SEQU) - ? IVL_ST_BLOCK - : IVL_ST_FORK; + switch (net->type()) { + case NetBlock::SEQU: + stmt_cur_->type_ = IVL_ST_BLOCK; + break; + case NetBlock::PARA: + stmt_cur_->type_ = IVL_ST_FORK; + break; + case NetBlock::PARA_JOIN_ANY: + stmt_cur_->type_ = IVL_ST_FORK_JOIN_ANY; + break; + case NetBlock::PARA_JOIN_NONE: + stmt_cur_->type_ = IVL_ST_FORK_JOIN_NONE; + break; + } stmt_cur_->u_.block_.nstmt_ = count; stmt_cur_->u_.block_.stmt_ = (struct ivl_statement_s*) calloc(count, sizeof(struct ivl_statement_s)); diff --git a/tgt-stub/statement.c b/tgt-stub/statement.c index 401b049e7..1b89e43ec 100644 --- a/tgt-stub/statement.c +++ b/tgt-stub/statement.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2007 Stephen Williams (steve@icarus.com) + * Copyright (c) 2004-2007,2012 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -329,6 +329,28 @@ void show_statement(ivl_statement_t net, unsigned ind) break; } + case IVL_ST_FORK_JOIN_ANY: { + unsigned cnt = ivl_stmt_block_count(net); + fprintf(out, "%*sfork\n", ind, ""); + for (idx = 0 ; idx < cnt ; idx += 1) { + ivl_statement_t cur = ivl_stmt_block_stmt(net, idx); + show_statement(cur, ind+4); + } + fprintf(out, "%*sjoin_any\n", ind, ""); + break; + } + + case IVL_ST_FORK_JOIN_NONE: { + unsigned cnt = ivl_stmt_block_count(net); + fprintf(out, "%*sfork\n", ind, ""); + for (idx = 0 ; idx < cnt ; idx += 1) { + ivl_statement_t cur = ivl_stmt_block_stmt(net, idx); + show_statement(cur, ind+4); + } + fprintf(out, "%*sjoin_none\n", ind, ""); + break; + } + case IVL_ST_FREE: fprintf(out, "%*sfree automatic storage ...\n", ind, ""); break; From 47ddf5220c07567d7e536a84040d778a709829a0 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Sun, 20 May 2012 11:39:05 -0700 Subject: [PATCH 7/9] Fix assertion settin join type is PBlock statements. --- Statement.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Statement.cc b/Statement.cc index 64df85ebd..4fcedc777 100644 --- a/Statement.cc +++ b/Statement.cc @@ -117,7 +117,7 @@ PBlock::~PBlock() void PBlock::set_join_type(PBlock::BL_TYPE type) { assert(bl_type_ == BL_PAR); - assert(type == BL_JOIN_NONE || type==BL_JOIN_ANY); + assert(type==BL_PAR || type==BL_JOIN_NONE || type==BL_JOIN_ANY); bl_type_ = type; } From 3b7619b46cbcfd309f33c4b9f59f282768786f44 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Sun, 20 May 2012 11:39:52 -0700 Subject: [PATCH 8/9] Implement fork-join_none in vvp. --- tgt-vvp/vvp_process.c | 58 +++++++++++++++++++++++++++++++++---------- vvp/codes.h | 1 + vvp/compile.cc | 1 + vvp/vthread.cc | 38 ++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/tgt-vvp/vvp_process.c b/tgt-vvp/vvp_process.c index 7a0e9c1e7..c30fab792 100644 --- a/tgt-vvp/vvp_process.c +++ b/tgt-vvp/vvp_process.c @@ -1314,55 +1314,85 @@ static int show_stmt_fork(ivl_statement_t net, ivl_scope_t sscope) { unsigned idx; int rc = 0; - unsigned cnt = ivl_stmt_block_count(net); + unsigned join_count = ivl_stmt_block_count(net); + unsigned join_detach_count = 0; ivl_scope_t scope = ivl_stmt_block_scope(net); - unsigned is_named = (scope != 0); + int is_named = (scope != 0); + /* This is TRUE if it is allowed to embed one of the threads + into this thread. */ + int is_embeddable = 1; unsigned out = transient_id++; unsigned id_base = transient_id; + /* Children are certainly not embeddable if they are going + into a new scope. */ + if (is_named) + is_embeddable = 0; + + switch (ivl_statement_type(net)) { + case IVL_ST_FORK: + break; + case IVL_ST_FORK_JOIN_ANY: + if (join_count < 2) + break; + is_embeddable = 0; + join_detach_count = join_count - 1; + join_count = 1; + break; + case IVL_ST_FORK_JOIN_NONE: + is_embeddable = 0; + join_detach_count = join_count; + join_count = 0; + break; + default: + assert(0); + } + /* cnt is the number of sub-threads. If the fork-join has no name, then we can put one of the sub-threads in the current thread, so decrement the count by one and use the current scope for all the threads. */ - if (! is_named) { - cnt -= 1; + if (is_embeddable) + join_count -= 1; + if (scope==0) scope = sscope; - } - transient_id += cnt; + transient_id += join_count; /* Draw a fork statement for all but one of the threads of the fork/join. Send the threads off to a bit of code where they are implemented. */ - for (idx = 0 ; idx < cnt ; idx += 1) { + for (idx = 0 ; idx < (join_count+join_detach_count) ; idx += 1) { fprintf(vvp_out, " %%fork t_%u, S_%p;\n", id_base+idx, scope); } /* If we are putting one sub-thread into the current thread, then draw its code here. */ - if (! is_named) - rc += show_statement(ivl_stmt_block_stmt(net, cnt), scope); + if (is_embeddable) + rc += show_statement(ivl_stmt_block_stmt(net, join_count), scope); /* Generate enough joins to collect all the sub-threads. */ - for (idx = 0 ; idx < cnt ; idx += 1) { + for (idx = 0 ; idx < join_count ; idx += 1) fprintf(vvp_out, " %%join;\n"); - } + if (join_detach_count > 0) + fprintf(vvp_out, " %%join/detach %u;\n", join_detach_count); + /* Jump around all the threads that I'm creating. */ fprintf(vvp_out, " %%jmp t_%u;\n", out); /* Change the compiling scope to be the named forks scope. */ if (is_named) fprintf(vvp_out, " .scope S_%p;\n", scope); /* Generate the sub-threads themselves. */ - for (idx = 0 ; idx < cnt ; idx += 1) { + for (idx = 0 ; idx < (join_count + join_detach_count) ; idx += 1) { fprintf(vvp_out, "t_%u ;\n", id_base+idx); clear_expression_lookaside(); rc += show_statement(ivl_stmt_block_stmt(net, idx), scope); fprintf(vvp_out, " %%end;\n"); } /* Return to the previous scope. */ - if (is_named) fprintf(vvp_out, " .scope S_%p;\n", sscope); + if (sscope) fprintf(vvp_out, " .scope S_%p;\n", sscope); /* This is the label for the out. Use this to branch around the implementations of all the child threads. */ @@ -2073,6 +2103,8 @@ static int show_statement(ivl_statement_t net, ivl_scope_t sscope) break; case IVL_ST_FORK: + case IVL_ST_FORK_JOIN_ANY: + case IVL_ST_FORK_JOIN_NONE: rc += show_stmt_fork(net, sscope); break; diff --git a/vvp/codes.h b/vvp/codes.h index e7632d5b2..77a69a70a 100644 --- a/vvp/codes.h +++ b/vvp/codes.h @@ -115,6 +115,7 @@ extern bool of_JMP0(vthread_t thr, vvp_code_t code); extern bool of_JMP0XZ(vthread_t thr, vvp_code_t code); extern bool of_JMP1(vthread_t thr, vvp_code_t code); extern bool of_JOIN(vthread_t thr, vvp_code_t code); +extern bool of_JOIN_DETACH(vthread_t thr, vvp_code_t code); extern bool of_LOAD_AR(vthread_t thr, vvp_code_t code); extern bool of_LOAD_AV(vthread_t thr, vvp_code_t code); extern bool of_LOAD_AVP0(vthread_t thr, vvp_code_t code); diff --git a/vvp/compile.cc b/vvp/compile.cc index 955dc5425..9edb2e97f 100644 --- a/vvp/compile.cc +++ b/vvp/compile.cc @@ -157,6 +157,7 @@ static const struct opcode_table_s opcode_table[] = { { "%jmp/0xz",of_JMP0XZ, 2, {OA_CODE_PTR, OA_BIT1, OA_NONE} }, { "%jmp/1", of_JMP1, 2, {OA_CODE_PTR, OA_BIT1, OA_NONE} }, { "%join", of_JOIN, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%join/detach",of_JOIN_DETACH,1,{OA_NUMBER,OA_NONE, OA_NONE} }, { "%load/ar",of_LOAD_AR,3, {OA_BIT1, OA_ARR_PTR, OA_BIT2} }, { "%load/av",of_LOAD_AV,3, {OA_BIT1, OA_ARR_PTR, OA_BIT2} }, { "%load/avp0",of_LOAD_AVP0,3, {OA_BIT1, OA_ARR_PTR, OA_BIT2} }, diff --git a/vvp/vthread.cc b/vvp/vthread.cc index 86e1d44ff..563f5e465 100644 --- a/vvp/vthread.cc +++ b/vvp/vthread.cc @@ -2892,6 +2892,44 @@ bool of_JOIN(vthread_t thr, vvp_code_t) return false; } +/* + * This %join/detach instruction causes the thread to detach + * threads that were created by an earlier %fork. + */ +bool of_JOIN_DETACH(vthread_t thr, vvp_code_t cp) +{ + unsigned long count = cp->number; + + while (count > 0) { + assert(thr->child); + assert(thr->child->parent == thr); + assert(thr->fork_count > 0); + assert(thr->child->wt_context == 0); + + thr->fork_count -= 1; + + if (thr->child->i_have_ended) { + /* If the child has already ended, then reap it + instead of pulling it from the list. */ + vthread_reap(thr->child); + } else { + /* Pull the child from the parent->child list. */ + struct vthread_s*tmp = thr->child; + assert(tmp->fork_count == 0); + thr->child = tmp->child; + if (thr->child) + thr->child->parent = thr; + /* set up detach. */ + tmp->parent = 0; + tmp->child = 0; + } + + count -= 1; + } + + return true; +} + /* * %load/ar , , ; */ From 25f72e31d4cb4ac2c8278f8fb41785b900142fb6 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Sun, 27 May 2012 18:03:55 -0700 Subject: [PATCH 9/9] Re-implement fork/join in vvp The fork/join list did not adequately support the tree of processes that can happen in Verilog, so this patch reworks that support to make it all more natural. --- vvp/opcodes.txt | 8 ++ vvp/vthread.cc | 241 ++++++++++++++++++++++++++---------------------- 2 files changed, 138 insertions(+), 111 deletions(-) diff --git a/vvp/opcodes.txt b/vvp/opcodes.txt index b8ce9332e..369aa04b2 100644 --- a/vvp/opcodes.txt +++ b/vvp/opcodes.txt @@ -526,6 +526,14 @@ If the matching child instruction is still running, a %join suspends the calling thread until the child ends. If the child is already ended, then the %join does not block or yield the thread. +* %join/detach + +This is also a partner to the %ork. This instruction causes the thread +to detach threads from the current thread. The should be ALL +the children, and none of those children may be automatic. This +instruction is used to implement join_none and join_any from the +Verilog source. + * %load/av , , This instruction loads a word from the specified array. The word diff --git a/vvp/vthread.cc b/vvp/vthread.cc index 563f5e465..ccef7946b 100644 --- a/vvp/vthread.cc +++ b/vvp/vthread.cc @@ -28,7 +28,9 @@ #ifdef CHECK_WITH_VALGRIND # include "vvp_cleanup.h" #endif +# include # include +# include # include # include # include @@ -38,6 +40,8 @@ # include # include +using namespace std; + /* This is the size of an unsigned long in bits. This is just a convenience macro. */ # define CPU_WORD_BITS (8*sizeof(unsigned long)) @@ -51,35 +55,26 @@ * * ** Notes On The Interactions of %fork/%join/%end: * - * The %fork instruction creates a new thread and pushes that onto the - * stack of children for the thread. This new thread, then, becomes - * the new direct descendant of the thread. This new thread is - * therefore also the first thread to be reaped when the parent does a - * %join. + * The %fork instruction creates a new thread and pushes that into a + * set of children for the thread. This new thread, then, becomes a + * child of the current thread, and the current thread a parent of the + * new thread. Any child can be reaped by a %join. + * + * Children placed into an automatic scope are given special + * treatment, which is required to make function/tasks calls that they + * represent work correctly. These automatic children are copied into + * an automatic_children set to mark them for this handling. %join + * operations will guarantee that automatic threads are joined first, + * before any non-automatic threads. * * It is a programming error for a thread that created threads to not - * %join as many as it created before it %ends. The linear stack for - * tracking thread relationships will create a mess otherwise. For - * example, if A creates B then C, the stack is: + * %join (or %join/detach) as many as it created before it %ends. The + * children set will get messed up otherwise. * - * A --> C --> B - * - * If C then %forks X, the stack is: - * - * A --> C --> X --> B - * - * If C %ends without a join, then the stack is: - * - * A --> C(zombie) --> X --> B - * - * If A then executes 2 %joins, it will reap C and X (when it ends) - * leaving B in purgatory. What's worse, A will block on the schedules - * of X and C instead of C and B, possibly creating incorrect timing. - * - * The schedule_parent_on_end flag is used by threads to tell their - * children that they are waiting for it to end. It is set by a %join - * instruction if the child is not already done. The thread that - * executes a %join instruction sets the flag in its child. + * the i_am_joining flag is a clue to children that the parent is + * blocked in a %join and may need to be scheduled. The %end + * instruction will check this flag in the parent to see if it should + * notify the parent that something is interesting. * * The i_have_ended flag, on the other hand, is used by threads to * tell their parents that they are already dead. A thread that @@ -105,14 +100,15 @@ struct vthread_s { } words[16]; /* My parent sets this when it wants me to wake it up. */ - unsigned schedule_parent_on_end :1; + unsigned i_am_joining :1; unsigned i_have_ended :1; unsigned waiting_for_event :1; unsigned is_scheduled :1; unsigned delay_delete :1; - unsigned fork_count :8; - /* This points to the sole child of the thread. */ - struct vthread_s*child; + /* This points to the children of the thread. */ + setchildren; + /* No more then 1 of the children is automatic. */ + setautomatic_children; /* This points to my parent, if I have one. */ struct vthread_s*parent; /* This points to the containing scope. */ @@ -126,6 +122,9 @@ struct vthread_s { uint64_t ecount; }; +static bool test_joinable(vthread_t thr, vthread_t child); +static void do_join(vthread_t thr, vthread_t child); + struct __vpiScope* vthread_scope(struct vthread_s*thr) { return thr->parent_scope; @@ -400,19 +399,17 @@ vthread_t vthread_new(vvp_code_t pc, struct __vpiScope*scope) vthread_t thr = new struct vthread_s; thr->pc = pc; thr->bits4 = vvp_vector4_t(32); - thr->child = 0; thr->parent = 0; thr->parent_scope = scope; thr->wait_next = 0; thr->wt_context = 0; thr->rd_context = 0; - thr->schedule_parent_on_end = 0; + thr->i_am_joining = 0; thr->is_scheduled = 0; thr->i_have_ended = 0; thr->delay_delete = 0; thr->waiting_for_event = 0; - thr->fork_count = 0; thr->event = 0; thr->ecount = 0; @@ -469,16 +466,19 @@ void vthreads_delete(struct __vpiScope*scope) */ static void vthread_reap(vthread_t thr) { - if (thr->child) { - assert(thr->child->parent == thr); - thr->child->parent = thr->parent; + if (thr->children.size() > 0) { + for (set::iterator cur = thr->children.begin() + ; cur != thr->children.end() ; ++cur) { + vthread_t curp = *cur; + assert(curp->parent == thr); + curp->parent = thr->parent; + } } if (thr->parent) { - assert(thr->parent->child == thr); - thr->parent->child = thr->child; + //assert(thr->parent->child == thr); + thr->parent->children.erase(thr); } - thr->child = 0; thr->parent = 0; // Remove myself from the containing scope. @@ -490,7 +490,7 @@ static void vthread_reap(vthread_t thr) it now. Otherwise, let the schedule event (which will execute the thread at of_ZOMBIE) delete the object. */ if ((thr->is_scheduled == 0) && (thr->waiting_for_event == 0)) { - assert(thr->fork_count == 0); + assert(thr->children.size() == 0); assert(thr->wait_next == 0); if (thr->delay_delete) schedule_del_thr(thr); @@ -1956,28 +1956,30 @@ static bool do_disable(vthread_t thr, vthread_t match) /* Turn off all the children of the thread. Simulate a %join for as many times as needed to clear the results of all the %forks that this thread has done. */ - while (thr->fork_count > 0) { + while (!thr->children.empty()) { - vthread_t tmp = thr->child; + vthread_t tmp = *(thr->children.begin()); assert(tmp); assert(tmp->parent == thr); - tmp->schedule_parent_on_end = 0; + thr->i_am_joining = 0; if (do_disable(tmp, match)) flag = true; - thr->fork_count -= 1; - vthread_reap(tmp); } + if (thr->parent && thr->parent->i_am_joining) { + // If a parent is waiting in a %join, wake it up. Note + // that it is possible to be waiting in a %join yet + // already scheduled if multiple child threads are + // ending. So check if the thread is already scheduled + // before scheduling it again. + vthread_t parent = thr->parent; + parent->i_am_joining = 0; + if (! parent->i_have_ended) + schedule_vthread(parent, 0, true); - if (thr->schedule_parent_on_end) { - /* If a parent is waiting in a %join, wake it up. */ - assert(thr->parent); - assert(thr->parent->fork_count > 0); - - thr->parent->fork_count -= 1; - schedule_vthread(thr->parent, 0, true); + // Let the parent do the reaping. vthread_reap(thr); } else if (thr->parent) { @@ -2007,7 +2009,7 @@ bool of_DISABLE(vthread_t thr, vvp_code_t cp) while (! scope->threads.empty()) { set::iterator cur = scope->threads.begin(); - /* If I am disabling myself, that remember that fact so + /* If I am disabling myself, then remember that fact so that I can finish this statement differently. */ if (*cur == thr) disabled_myself_flag = true; @@ -2350,20 +2352,25 @@ bool of_DIV_WR(vthread_t thr, vvp_code_t cp) bool of_END(vthread_t thr, vvp_code_t) { assert(! thr->waiting_for_event); - assert( thr->fork_count == 0 ); thr->i_have_ended = 1; thr->pc = codespace_null(); /* If I have a parent who is waiting for me, then mark that I have ended, and schedule that parent. Also, finish the %join for the parent. */ - if (thr->schedule_parent_on_end) { - assert(thr->parent); - assert(thr->parent->fork_count > 0); + if (thr->parent && thr->parent->i_am_joining) { + vthread_t tmp = thr->parent; - thr->parent->fork_count -= 1; - schedule_vthread(thr->parent, 0, true); - vthread_reap(thr); + // Detect that the parent is waiting on an automatic + // thread. Automatic threads must be reaped first. If + // the parent is waiting on an auto (other then me) then + // go into zomple state to be picked up later. + if (!test_joinable(tmp, thr)) + return false; + + tmp->i_am_joining = 0; + schedule_vthread(tmp, 0, true); + do_join(tmp, thr); return false; } @@ -2375,7 +2382,7 @@ bool of_END(vthread_t thr, vvp_code_t) main thread (there is no other parent) and an error (not enough %joins) has been detected. */ if (thr->parent == 0) { - assert(thr->child == 0); + assert(thr->children.empty()); vthread_reap(thr); return false; } @@ -2523,29 +2530,25 @@ bool of_FORCE_X0(vthread_t thr, vvp_code_t cp) /* * The %fork instruction causes a new child to be created and pushed - * in front of any existing child. This causes the new child to be the - * parent of any previous children, and for me to be the parent of the + * in front of any existing child. This causes the new child to be + * added to the list of children, and for me to be the parent of the * new child. */ bool of_FORK(vthread_t thr, vvp_code_t cp) { vthread_t child = vthread_new(cp->cptr2, cp->scope); + if (cp->scope->is_automatic) { /* The context allocated for this child is the top entry on the write context stack. */ child->wt_context = thr->wt_context; child->rd_context = thr->wt_context; + + thr->automatic_children.insert(child); } - child->child = thr->child; child->parent = thr; - thr->child = child; - if (child->child) { - assert(child->child->parent == thr); - child->child->parent = child; - } - - thr->fork_count += 1; + thr->children.insert(child); /* If the new child was created to evaluate a function, run it immediately, then return to this thread. */ @@ -2854,20 +2857,27 @@ bool of_JMP1(vthread_t thr, vvp_code_t cp) } /* - * The %join instruction causes the thread to wait for the one and - * only child to die. If it is already dead (and a zombie) then I - * reap it and go on. Otherwise, I tell the child that I am ready for - * it to die, and it will reschedule me when it does. + * The %join instruction causes the thread to wait for one child + * to die. If a child is already dead (and a zombie) then I reap + * it and go on. Otherwise, I mark myself as waiting in a join so that + * children know to wake me when they finish. */ -bool of_JOIN(vthread_t thr, vvp_code_t) + +static bool test_joinable(vthread_t thr, vthread_t child) { - assert(thr->child); - assert(thr->child->parent == thr); + set::iterator auto_cur = thr->automatic_children.find(child); + if (!thr->automatic_children.empty() && auto_cur == thr->automatic_children.end()) + return false; - assert(thr->fork_count > 0); + return true; +} - /* If the child thread is in an automatic scope... */ - if (thr->child->wt_context) { +static void do_join(vthread_t thr, vthread_t child) +{ + assert(child->parent == thr); + + /* If the immediate child thread is in an automatic scope... */ + if (thr->automatic_children.erase(child) != 0) { /* and is the top level task/function thread... */ if (thr->wt_context != thr->rd_context) { /* Pop the child context from the write context stack. */ @@ -2880,15 +2890,33 @@ bool of_JOIN(vthread_t thr, vvp_code_t) } } - /* If the child has already ended, reap it now. */ - if (thr->child->i_have_ended) { - thr->fork_count -= 1; - vthread_reap(thr->child); + vthread_reap(child); +} + +bool of_JOIN(vthread_t thr, vvp_code_t) +{ + assert( !thr->i_am_joining ); + assert( !thr->children.empty()); + + // Are there any children that have already ended? If so, then + // join with that one. + for (set::iterator cur = thr->children.begin() + ; cur != thr->children.end() ; ++cur) { + vthread_t curp = *cur; + if (!curp->i_have_ended) + continue; + + if (!test_joinable(thr, curp)) + continue; + + // found somenting! + do_join(thr, curp); return true; } - /* Otherwise, I get to start waiting. */ - thr->child->schedule_parent_on_end = 1; + // Otherwise, tell my children to awaken me when they end, + // then pause. + thr->i_am_joining = 1; return false; } @@ -2900,31 +2928,23 @@ bool of_JOIN_DETACH(vthread_t thr, vvp_code_t cp) { unsigned long count = cp->number; - while (count > 0) { - assert(thr->child); - assert(thr->child->parent == thr); - assert(thr->fork_count > 0); - assert(thr->child->wt_context == 0); + assert(thr->automatic_children.empty()); + assert(count == thr->children.size()); - thr->fork_count -= 1; + while (!thr->children.empty()) { + vthread_t child = *thr->children.begin(); + assert(child->parent == thr); + + // We cannot detach automatic tasks/functions + assert(child->wt_context == 0); + if (child->i_have_ended) { + // If the child has already ended, then reap it. + vthread_reap(child); - if (thr->child->i_have_ended) { - /* If the child has already ended, then reap it - instead of pulling it from the list. */ - vthread_reap(thr->child); } else { - /* Pull the child from the parent->child list. */ - struct vthread_s*tmp = thr->child; - assert(tmp->fork_count == 0); - thr->child = tmp->child; - if (thr->child) - thr->child->parent = thr; - /* set up detach. */ - tmp->parent = 0; - tmp->child = 0; + thr->children.erase(child); + child->parent = 0; } - - count -= 1; } return true; @@ -4646,7 +4666,7 @@ bool of_XOR(vthread_t thr, vvp_code_t cp) bool of_ZOMBIE(vthread_t thr, vvp_code_t) { thr->pc = codespace_null(); - if ((thr->parent == 0) && (thr->child == 0)) { + if ((thr->parent == 0) && (thr->children.empty())) { if (thr->delay_delete) schedule_del_thr(thr); else @@ -4667,8 +4687,7 @@ bool of_EXEC_UFUNC(vthread_t thr, vvp_code_t cp) struct __vpiScope*child_scope = cp->ufunc_core_ptr->func_scope(); assert(child_scope); - assert(thr->child == 0); - assert(thr->fork_count == 0); + assert(thr->children.empty()); /* We can take a number of shortcuts because we know that a continuous assignment can only occur in a static scope. */