Implement SystemVerilog final statements.
Add a new IVL_PR_FINAL process type. Add a flag to NetScope in_final_ which is set when elaborating the statement of a final procedure. Add checks during statement elaboration for invalid statements in a final procedure, similar to checks for statements in functions. Do a final check to make sure no final blocks have delays. In the vvp runtime, use "$final" as the flag for the thread created by the final procedure. During compilation, instead of adding such a thread to the sched_list, add it to a new schedule_final_list that mirrors the schedule_init_list, but is run at the end of simulation.
This commit is contained in:
parent
fa589badd8
commit
9b785031f5
|
|
@ -41,9 +41,9 @@ class NetScope;
|
|||
|
||||
/*
|
||||
* The PProcess is the root of a behavioral process. Each process gets
|
||||
* one of these, which contains its type (initial or always) and a
|
||||
* pointer to the single statement that is the process. A module may
|
||||
* have several concurrent processes.
|
||||
* one of these, which contains its type (initial, always, or final)
|
||||
* and a pointer to the single statement that is the process. A module
|
||||
* may have several concurrent processes.
|
||||
*/
|
||||
class PProcess : public LineInfo {
|
||||
|
||||
|
|
|
|||
|
|
@ -749,6 +749,10 @@ void NetProcTop::dump(ostream&o, unsigned ind) const
|
|||
o << "always /* " << get_fileline() << " in "
|
||||
<< scope_path(scope_) << " */" << endl;
|
||||
break;
|
||||
case IVL_PR_FINAL:
|
||||
o << "final /* " << get_fileline() << " in "
|
||||
<< scope_path(scope_) << " */" << endl;
|
||||
break;
|
||||
}
|
||||
|
||||
for (unsigned idx = 0 ; idx < attr_cnt() ; idx += 1) {
|
||||
|
|
@ -771,6 +775,11 @@ void NetAnalogTop::dump(ostream&o, unsigned ind) const
|
|||
o << "analog /* " << get_fileline() << " in "
|
||||
<< scope_path(scope_) << " */" << endl;
|
||||
break;
|
||||
|
||||
case IVL_PR_FINAL:
|
||||
o << "analog final /* " << get_fileline() << " in "
|
||||
<< scope_path(scope_) << " */" << endl;
|
||||
break;
|
||||
}
|
||||
|
||||
statement_->dump(o, ind+2);
|
||||
|
|
|
|||
58
elaborate.cc
58
elaborate.cc
|
|
@ -2428,6 +2428,13 @@ NetProc* PAssignNB::elaborate(Design*des, NetScope*scope) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (scope->in_final()) {
|
||||
cerr << get_fileline() << ": error: final procedures cannot have "
|
||||
"non blocking assignment statements." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (scope->is_auto() && lval()->has_aa_term(des, scope)) {
|
||||
cerr << get_fileline() << ": error: automatically allocated "
|
||||
"variables may not be assigned values using non-blocking "
|
||||
|
|
@ -2853,6 +2860,13 @@ NetProc* PCallTask::elaborate_usr(Design*des, NetScope*scope) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (scope->in_final()) {
|
||||
cerr << get_fileline() << ": error: final procedures cannot "
|
||||
"enable/call tasks." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
NetScope*task = des->find_task(scope, path_);
|
||||
if (task == 0) {
|
||||
cerr << get_fileline() << ": error: Enable of unknown task "
|
||||
|
|
@ -3101,6 +3115,13 @@ NetProc* PDelayStatement::elaborate(Design*des, NetScope*scope) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (scope->in_final()) {
|
||||
cerr << get_fileline() << ": error: final procedures cannot "
|
||||
"have delay statements." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This call evaluates the delay expression to a NetEConst, if
|
||||
possible. This includes transforming NetECReal values to
|
||||
integers, and applying the proper scaling. */
|
||||
|
|
@ -3255,6 +3276,13 @@ NetProc* PEventStatement::elaborate_st(Design*des, NetScope*scope,
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (scope->in_final()) {
|
||||
cerr << get_fileline() << ": error: final procedures cannot "
|
||||
"have event statements." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create a single NetEvent and NetEvWait. Then, create a
|
||||
NetEvProbe for each conjunctive event in the event
|
||||
list. The NetEvProbe objects all refer back to the NetEvent
|
||||
|
|
@ -3460,6 +3488,13 @@ NetProc* PEventStatement::elaborate_wait(Design*des, NetScope*scope,
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (scope->in_final()) {
|
||||
cerr << get_fileline() << ": error: final procedures cannot "
|
||||
"have wait statements." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PExpr *pe = expr_[0]->expr();
|
||||
|
||||
/* Elaborate wait expression. Don't eval yet, we will do that
|
||||
|
|
@ -4013,7 +4048,9 @@ NetProc* PWhile::elaborate(Design*des, NetScope*scope) const
|
|||
|
||||
bool PProcess::elaborate(Design*des, NetScope*scope) const
|
||||
{
|
||||
scope->in_final(type() == IVL_PR_FINAL);
|
||||
NetProc*cur = statement_->elaborate(des, scope);
|
||||
scope->in_final(false);
|
||||
if (cur == 0) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -4597,7 +4634,7 @@ class later_defparams : public elaborator_work_item_t {
|
|||
}
|
||||
};
|
||||
|
||||
bool Design::check_always_delay() const
|
||||
bool Design::check_proc_delay() const
|
||||
{
|
||||
bool result_flag = true;
|
||||
|
||||
|
|
@ -4623,6 +4660,20 @@ bool Design::check_always_delay() const
|
|||
<< " infinite loop may be possible." << endl;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this is a final block it must not have a delay,
|
||||
but this should have been caught by the statement
|
||||
elaboration, so maybe this should be an internal
|
||||
error? */
|
||||
if (pr->type() == IVL_PR_FINAL) {
|
||||
DelayType dly_type = pr->statement()->delay_type();
|
||||
|
||||
if (dly_type != NO_DELAY && dly_type != ZERO_DELAY) {
|
||||
cerr << pr->get_fileline() << ": error: final"
|
||||
<< " statement contains a delay." << endl;
|
||||
result_flag = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result_flag;
|
||||
|
|
@ -4774,8 +4825,9 @@ Design* elaborate(list<perm_string>roots)
|
|||
}
|
||||
|
||||
// Now that everything is fully elaborated verify that we do
|
||||
// not have an always block with no delay (an infinite loop).
|
||||
if (des->check_always_delay() == false) {
|
||||
// not have an always block with no delay (an infinite loop),
|
||||
// or a final block with a delay.
|
||||
if (des->check_proc_delay() == false) {
|
||||
delete des;
|
||||
des = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,11 +324,12 @@ typedef enum ivl_path_edge_e {
|
|||
IVL_PE_COUNT
|
||||
} ivl_path_edge_t;
|
||||
|
||||
/* Processes are initial or always blocks with a statement. This is
|
||||
/* Processes are initial, always, or final blocks with a statement. This is
|
||||
the type of the ivl_process_t object. */
|
||||
typedef enum ivl_process_type_e {
|
||||
IVL_PR_INITIAL = 0,
|
||||
IVL_PR_ALWAYS = 1
|
||||
IVL_PR_ALWAYS = 1,
|
||||
IVL_PR_FINAL = 2
|
||||
} ivl_process_type_t;
|
||||
|
||||
/* These are the sorts of reasons a scope may come to be. These types
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ NetScope::NetScope(NetScope*up, const hname_t&n, NetScope::TYPE t)
|
|||
is_const_func_ = false;
|
||||
is_auto_ = false;
|
||||
is_cell_ = false;
|
||||
in_final_ = false;
|
||||
|
||||
if (up) {
|
||||
time_unit_ = up->time_unit();
|
||||
|
|
|
|||
10
netlist.h
10
netlist.h
|
|
@ -822,6 +822,10 @@ class NetScope : public Attrib {
|
|||
void is_cell(bool is_cell__) { is_cell_ = is_cell__; };
|
||||
bool is_cell() const { return is_cell_; };
|
||||
|
||||
/* Is this scope elaborating a final procedure? */
|
||||
void in_final(bool in_final__) { in_final_ = in_final__; };
|
||||
bool in_final() const { return in_final_; };
|
||||
|
||||
const NetTaskDef* task_def() const;
|
||||
const NetFuncDef* func_def() const;
|
||||
|
||||
|
|
@ -992,6 +996,10 @@ class NetScope : public Attrib {
|
|||
|
||||
unsigned lcounter_;
|
||||
bool need_const_func_, is_const_func_, is_auto_, is_cell_;
|
||||
|
||||
/* Final procedures sets this to notify statements that
|
||||
they are part of a final procedure. */
|
||||
bool in_final_;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -4033,7 +4041,7 @@ class Design {
|
|||
void add_process(NetProcTop*);
|
||||
void add_process(NetAnalogTop*);
|
||||
void delete_process(NetProcTop*);
|
||||
bool check_always_delay() const;
|
||||
bool check_proc_delay() const;
|
||||
|
||||
NetNet* find_discipline_reference(ivl_discipline_t dis, NetScope*scope);
|
||||
|
||||
|
|
|
|||
4
parse.y
4
parse.y
|
|
@ -2778,6 +2778,10 @@ module_item
|
|||
{ PProcess*tmp = pform_make_behavior(IVL_PR_INITIAL, $3, $1);
|
||||
FILE_NAME(tmp, @2);
|
||||
}
|
||||
| attribute_list_opt K_final statement
|
||||
{ PProcess*tmp = pform_make_behavior(IVL_PR_FINAL, $3, $1);
|
||||
FILE_NAME(tmp, @2);
|
||||
}
|
||||
|
||||
| attribute_list_opt K_analog analog_statement
|
||||
{ pform_make_analog_behavior(@2, IVL_PR_ALWAYS, $3); }
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ std::ostream& operator << (std::ostream&out, ivl_process_type_t pt)
|
|||
case IVL_PR_ALWAYS:
|
||||
out << "always";
|
||||
break;
|
||||
case IVL_PR_FINAL:
|
||||
out << "final";
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -907,6 +910,9 @@ void AProcess::dump(ostream&out, unsigned ind) const
|
|||
case IVL_PR_ALWAYS:
|
||||
out << setw(ind) << "" << "analog";
|
||||
break;
|
||||
case IVL_PR_FINAL:
|
||||
out << setw(ind) << "" << "analog final";
|
||||
break;
|
||||
}
|
||||
|
||||
out << " /* " << get_fileline() << " */" << endl;
|
||||
|
|
|
|||
10
synth.cc
10
synth.cc
|
|
@ -119,6 +119,7 @@ class synth_f : public functor_t {
|
|||
private:
|
||||
void proc_always_(class Design*);
|
||||
void proc_initial_(class Design*);
|
||||
void proc_final_(class Design*);
|
||||
|
||||
NetProcTop*top_;
|
||||
};
|
||||
|
|
@ -138,6 +139,9 @@ void synth_f::process(class Design*des, class NetProcTop*top)
|
|||
case IVL_PR_INITIAL:
|
||||
proc_initial_(des);
|
||||
break;
|
||||
case IVL_PR_FINAL:
|
||||
proc_final_(des);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,6 +157,12 @@ void synth_f::proc_initial_(class Design*des)
|
|||
top_->statement()->match_proc(&expr_pat);
|
||||
}
|
||||
|
||||
void synth_f::proc_final_(class Design*des)
|
||||
{
|
||||
do_expr expr_pat(des, top_->scope());
|
||||
top_->statement()->match_proc(&expr_pat);
|
||||
}
|
||||
|
||||
void synth(Design*des)
|
||||
{
|
||||
synth_f synth_obj;
|
||||
|
|
|
|||
|
|
@ -1031,6 +1031,12 @@ static int show_process(ivl_process_t net, void*x)
|
|||
else
|
||||
fprintf(out, "always\n");
|
||||
break;
|
||||
case IVL_PR_FINAL:
|
||||
if (ivl_process_analog(net))
|
||||
fprintf(out, "analog final\n");
|
||||
else
|
||||
fprintf(out, "final\n");
|
||||
break;
|
||||
}
|
||||
|
||||
for (idx = 0 ; idx < ivl_process_attr_cnt(net) ; idx += 1) {
|
||||
|
|
|
|||
|
|
@ -398,6 +398,9 @@ static int show_process(ivl_process_t net, void*x)
|
|||
case IVL_PR_ALWAYS:
|
||||
fprintf(out, " always\n");
|
||||
break;
|
||||
case IVL_PR_FINAL:
|
||||
fprintf(out, " final\n");
|
||||
break;
|
||||
}
|
||||
|
||||
show_statement(ivl_process_stmt(net), 8);
|
||||
|
|
|
|||
|
|
@ -2435,6 +2435,7 @@ int draw_process(ivl_process_t net, void*x)
|
|||
switch (ivl_process_type(net)) {
|
||||
|
||||
case IVL_PR_INITIAL:
|
||||
case IVL_PR_FINAL:
|
||||
fprintf(vvp_out, " %%end;\n");
|
||||
break;
|
||||
|
||||
|
|
@ -2443,13 +2444,22 @@ int draw_process(ivl_process_t net, void*x)
|
|||
break;
|
||||
}
|
||||
|
||||
/* Now write out the .thread directive that tells vvp where
|
||||
the thread starts. */
|
||||
/* Now write out the directive that tells vvp where the thread
|
||||
starts. */
|
||||
switch (ivl_process_type(net)) {
|
||||
|
||||
if (push_flag) {
|
||||
fprintf(vvp_out, " .thread T_%d, $push;\n", thread_count);
|
||||
} else {
|
||||
fprintf(vvp_out, " .thread T_%d;\n", thread_count);
|
||||
case IVL_PR_INITIAL:
|
||||
case IVL_PR_ALWAYS:
|
||||
if (push_flag) {
|
||||
fprintf(vvp_out, " .thread T_%d, $push;\n", thread_count);
|
||||
} else {
|
||||
fprintf(vvp_out, " .thread T_%d;\n", thread_count);
|
||||
}
|
||||
break;
|
||||
|
||||
case IVL_PR_FINAL:
|
||||
fprintf(vvp_out, " .thread T_%d, $final;\n", thread_count);
|
||||
break;
|
||||
}
|
||||
|
||||
thread_count += 1;
|
||||
|
|
|
|||
|
|
@ -1796,7 +1796,11 @@ void compile_thread(char*start_sym, char*flag)
|
|||
push_flag = true;
|
||||
|
||||
vthread_t thr = vthread_new(pc, vpip_peek_current_scope());
|
||||
schedule_vthread(thr, 0, push_flag);
|
||||
|
||||
if (flag && (strcmp(flag,"$final") == 0))
|
||||
schedule_final_vthread(thr);
|
||||
else
|
||||
schedule_vthread(thr, 0, push_flag);
|
||||
|
||||
free(start_sym);
|
||||
free(flag);
|
||||
|
|
|
|||
|
|
@ -493,6 +493,11 @@ static struct event_time_s* sched_list = 0;
|
|||
*/
|
||||
static struct event_s* schedule_init_list = 0;
|
||||
|
||||
/*
|
||||
* This is the head of the list of final events.
|
||||
*/
|
||||
static struct event_s* schedule_final_list = 0;
|
||||
|
||||
/*
|
||||
* This flag is true until a VPI task or function finishes the
|
||||
* simulation.
|
||||
|
|
@ -559,6 +564,20 @@ static void schedule_init_event(struct event_s*cur)
|
|||
schedule_init_list = cur;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function puts an event on the end of the post-simulation event queue.
|
||||
*/
|
||||
static void schedule_final_event(struct event_s*cur)
|
||||
{
|
||||
if (schedule_final_list == 0) {
|
||||
cur->next = cur;
|
||||
} else {
|
||||
cur->next = schedule_final_list->next;
|
||||
schedule_final_list->next = cur;
|
||||
}
|
||||
schedule_final_list = cur;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function does all the hard work of putting an event into the
|
||||
* event queue. The event delay is taken from the event structure
|
||||
|
|
@ -741,6 +760,16 @@ void schedule_vthread(vthread_t thr, vvp_time64_t delay, bool push_flag)
|
|||
}
|
||||
}
|
||||
|
||||
void schedule_final_vthread(vthread_t thr)
|
||||
{
|
||||
struct vthread_event_s*cur = new vthread_event_s;
|
||||
|
||||
cur->thr = thr;
|
||||
vthread_mark_scheduled(thr);
|
||||
|
||||
schedule_final_event(cur);
|
||||
}
|
||||
|
||||
void schedule_assign_vector(vvp_net_ptr_t ptr,
|
||||
unsigned base, unsigned vwid,
|
||||
const vvp_vector4_t&bit,
|
||||
|
|
@ -1096,6 +1125,17 @@ void schedule_simulate(void)
|
|||
delete (cur);
|
||||
}
|
||||
|
||||
// Execute final events.
|
||||
while (schedule_final_list) {
|
||||
struct event_s*cur = schedule_final_list->next;
|
||||
if (cur->next == cur) {
|
||||
schedule_final_list = 0;
|
||||
} else {
|
||||
schedule_final_list->next = cur->next;
|
||||
}
|
||||
cur->run_run();
|
||||
delete cur;
|
||||
}
|
||||
|
||||
signals_revert();
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@
|
|||
extern void schedule_vthread(vthread_t thr, vvp_time64_t delay,
|
||||
bool push_flag =false);
|
||||
|
||||
extern void schedule_final_vthread(vthread_t thr);
|
||||
|
||||
/*
|
||||
* Create an assignment event. The val passed here will be assigned to
|
||||
* the specified input when the delay times out. This is scheduled
|
||||
|
|
|
|||
Loading…
Reference in New Issue