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:
Jared Casper 2011-03-29 23:42:26 -07:00 committed by Stephen Williams
parent fa589badd8
commit 9b785031f5
15 changed files with 172 additions and 16 deletions

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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);

View File

@ -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); }

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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();

View File

@ -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