From f9da800cf5af6c104370fa85b31f472ca67ffe24 Mon Sep 17 00:00:00 2001 From: Nick Gasson Date: Tue, 17 Aug 2010 22:42:49 +0100 Subject: [PATCH] Reduce number of 0 ns waits in generated VHDL Previous we generated a "wait for 0 ns" statement after every blocking assignment that wasn't the last statement in the process. While this implements the Verilog semantics, it generates excessive waits, and cannot usually be synthesised. This patch only generates "wait for 0 ns" statements when it cannot be avoid (e.g. when the target of a blocking assignment is read in the same process). An example: begin x = 5; if (x == 2) y = 7; end Becomes: x <= 5; wait for 0 ns; -- Required to implement assignment semantics if x = 2 then y <= 7; -- No need for wait here, not read -- wait for 0 ns (previously) end if; --- tgt-vhdl/process.cc | 15 ++-- tgt-vhdl/stmt.cc | 162 ++++++++++++++++++++++++++-------------- tgt-vhdl/vhdl_syntax.cc | 28 ++++++- tgt-vhdl/vhdl_syntax.hh | 14 +++- tgt-vhdl/vhdl_target.h | 1 - 5 files changed, 148 insertions(+), 72 deletions(-) diff --git a/tgt-vhdl/process.cc b/tgt-vhdl/process.cc index 9440b25cf..93e2fba8f 100644 --- a/tgt-vhdl/process.cc +++ b/tgt-vhdl/process.cc @@ -57,15 +57,12 @@ static int generate_vhdl_process(vhdl_entity *ent, ivl_process_t proc) // However, if no statements were added to the container // by draw_stmt, don't bother adding a wait as `emit' // will optimise the process out of the output - if (ivl_process_type(proc) == IVL_PR_INITIAL) { - // Get rid of any useless `wait for 0 ns's at the end of the process - prune_wait_for_0(vhdl_proc->get_container()); - - // The above pruning might have removed all logic from the process - if (!vhdl_proc->get_container()->empty()) { - vhdl_wait_stmt *wait = new vhdl_wait_stmt(); - vhdl_proc->get_container()->add_stmt(wait); - } + bool is_initial = ivl_process_type(proc) == IVL_PR_INITIAL; + bool is_empty = vhdl_proc->get_container()->empty(); + + if (is_initial && !is_empty) { + vhdl_wait_stmt *wait = new vhdl_wait_stmt(); + vhdl_proc->get_container()->add_stmt(wait); } // Add a comment indicating where it came from diff --git a/tgt-vhdl/stmt.cc b/tgt-vhdl/stmt.cc index 774ce9d06..7ac6e6fc7 100644 --- a/tgt-vhdl/stmt.cc +++ b/tgt-vhdl/stmt.cc @@ -1,7 +1,7 @@ /* * VHDL code generation for statements. * - * Copyright (C) 2008-2009 Nick Gasson (nick@nickg.me.uk) + * Copyright (C) 2008-2010 Nick Gasson (nick@nickg.me.uk) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -131,27 +131,6 @@ static int draw_noop(vhdl_procedural *proc, stmt_container *container, return 0; } -/* - * The VHDL code generator inserts `wait for 0 ns' after each - * not-last-in-block blocking assignment. - * If this is immediately followed by another `wait for ...' then - * we might as well not emit the first zero-time wait. - */ -void prune_wait_for_0(stmt_container *container) -{ - vhdl_wait_stmt *wait0; - stmt_container::stmt_list_t &stmts = container->get_stmts(); - while (stmts.size() > 0 - && (wait0 = dynamic_cast(stmts.back()))) { - if (wait0->get_type() == VHDL_WAIT_FOR0) { - delete wait0; - stmts.pop_back(); - } - else - break; - } -} - static vhdl_var_ref *make_assign_lhs(ivl_lval_t lval, vhdl_scope *scope) { ivl_signal_t sig = ivl_lval_sig(lval); @@ -263,12 +242,68 @@ bool check_valid_assignment(vhdl_decl::assign_type_t atype, vhdl_procedural *pro return true; } +// Generate a "wait for 0 ns" statement to emulate the behaviour of +// Verilog blocking assignment using VHDL signals. This is only generated +// if we read from the target of a blocking assignment in the same +// process (i.e. it is only generated when required, not for every +// blocking assignment). An example: +// +// begin +// x = 5; +// if (x == 2) +// y = 7; +// end +// +// Becomes: +// +// x <= 5; +// wait for 0 ns; -- Required to implement assignment semantics +// if x = 2 then +// y <= 7; -- No need for wait here, not read +// end if; +// +static void emit_wait_for_0(vhdl_procedural *proc, + stmt_container *container, + ivl_statement_t stmt, + vhdl_expr *expr) +{ + vhdl_var_set_t read; + expr->find_vars(read); + + bool need_wait_for_0 = false; + for (vhdl_var_set_t::const_iterator it = read.begin(); + it != read.end(); ++it) { + if (proc->is_blocking_target(*it)) + need_wait_for_0 = true; + } + + stmt_container::stmt_list_t &stmts = container->get_stmts(); + bool last_was_wait = + !stmts.empty() && dynamic_cast(stmts.back()); + + if (need_wait_for_0 && !last_was_wait) { + debug_msg("Generated wait-for-0 for %s:%d", + ivl_stmt_file(stmt), ivl_stmt_lineno(stmt)); + + vhdl_seq_stmt *wait = new vhdl_wait_stmt(VHDL_WAIT_FOR0); + + ostringstream ss; + ss << "Read target of blocking assignment (" + << ivl_stmt_file(stmt) + << ":" << ivl_stmt_lineno(stmt) << ")"; + wait->set_comment(ss.str()); + + container->add_stmt(wait); + proc->added_wait_stmt(); + } +} + // Generate an assignment of type T for the Verilog statement stmt. // If a statement was generated then `assign_type' will contain the // type of assignment that was generated; this should be initialised // to some sensible default. void make_assignment(vhdl_procedural *proc, stmt_container *container, - ivl_statement_t stmt, bool blocking, + ivl_statement_t stmt, bool emul_blocking, vhdl_decl::assign_type_t& assign_type) { list lvals; @@ -286,21 +321,33 @@ void make_assignment(vhdl_procedural *proc, stmt_container *container, if (rhs == NULL) return; + emit_wait_for_0(proc, container, stmt, rhs); + if (rhs2) + emit_wait_for_0(proc, container, stmt, rhs2); + if (lvals.size() == 1) { vhdl_var_ref *lhs = lvals.front(); rhs = rhs->cast(lhs->get_type()); ivl_expr_t i_delay; vhdl_expr *after = NULL; - if ((i_delay = ivl_stmt_delay_expr(stmt)) != NULL) + if ((i_delay = ivl_stmt_delay_expr(stmt)) != NULL) { after = translate_time_expr(i_delay); + if (after == NULL) + return; + + emit_wait_for_0(proc, container, stmt, after); + } // Find the declaration of the LHS so we know what type // of assignment statement to generate (is it a signal, // a variable, etc?) vhdl_decl *decl = proc->get_scope()->get_decl(lhs->get_name()); assign_type = decl->assignment_type(); - + + if (assign_type == vhdl_decl::ASSIGN_NONBLOCK && emul_blocking) + proc->add_blocking_target(lhs); + // A small optimisation is to expand ternary RHSs into an // if statement (eliminates a function call and produces // more idiomatic code) @@ -313,6 +360,8 @@ void make_assignment(vhdl_procedural *proc, stmt_container *container, if (NULL == test) return; + emit_wait_for_0(proc, container, stmt, test); + if (!check_valid_assignment(decl->assignment_type(), proc, stmt)) return; @@ -368,6 +417,7 @@ void make_assignment(vhdl_procedural *proc, stmt_container *container, decl->set_initial(NULL); // Default initial value else { decl->set_initial(rhs); + proc->get_scope()->hoisted_initialiser(true); delete lhs; return; } @@ -380,7 +430,7 @@ void make_assignment(vhdl_procedural *proc, stmt_container *container, assign_for(decl->assignment_type(), lhs, rhs); container->add_stmt(a); - if (after != NULL) + if (after != NULL) a->set_after(after); } else { @@ -408,8 +458,13 @@ void make_assignment(vhdl_procedural *proc, stmt_container *container, ivl_expr_t i_delay; vhdl_expr *after = NULL; - if ((i_delay = ivl_stmt_delay_expr(stmt)) != NULL) + if ((i_delay = ivl_stmt_delay_expr(stmt)) != NULL) { after = translate_time_expr(i_delay); + if (after == NULL) + return; + + emit_wait_for_0(proc, container, stmt, after); + } // Find the declaration of the LHS so we know what type // of assignment statement to generate (is it a signal, @@ -428,10 +483,11 @@ void make_assignment(vhdl_procedural *proc, stmt_container *container, container->add_stmt(a); width_so_far += lval_width; + + if (assign_type == vhdl_decl::ASSIGN_NONBLOCK && emul_blocking) + proc->add_blocking_target(*it); } } - - return; } /* @@ -454,26 +510,10 @@ static int draw_assign(vhdl_procedural *proc, stmt_container *container, ivl_statement_t stmt, bool is_last) { vhdl_decl::assign_type_t assign_type = vhdl_decl::ASSIGN_NONBLOCK; - if (proc->get_scope()->allow_signal_assignment()) { - // Blocking assignment is implemented as non-blocking assignment - // followed by a zero-time wait - // This follows the Verilog semantics fairly closely. - - make_assignment(proc, container, stmt, false, assign_type); - - // Don't generate a zero-wait if either: - // a) this is the last statement in the process - // c) a blocking assignment was generated - if (!is_last && assign_type == vhdl_decl::ASSIGN_NONBLOCK) { - prune_wait_for_0(container); - container->add_stmt - (new vhdl_wait_stmt(VHDL_WAIT_FOR0)); - proc->added_wait_stmt(); - } - } - else - make_assignment(proc, container, stmt, true, assign_type); + bool emulate_blocking = proc->get_scope()->allow_signal_assignment(); + make_assignment(proc, container, stmt, emulate_blocking, assign_type); + return 0; } @@ -501,9 +541,7 @@ static int draw_delay(vhdl_procedural *proc, stmt_container *container, if (NULL == time) return 1; } - - prune_wait_for_0(container); - + ivl_statement_t sub_stmt = ivl_stmt_sub_stmt(stmt); vhdl_wait_stmt *wait = new vhdl_wait_stmt(VHDL_WAIT_FOR, time); @@ -523,7 +561,7 @@ static int draw_delay(vhdl_procedural *proc, stmt_container *container, // Any further assignments occur after simulation time 0 // so they cannot be used to initialise signal declarations // (if this scope is an initial process) - proc->get_scope()->set_initializing(false); + proc->get_scope()->set_initializing(false); return 0; } @@ -743,9 +781,12 @@ static int draw_wait(vhdl_procedural *_proc, stmt_container *container, // If this container is the top-level statement (i.e. it is the // first thing inside a process) then we can extract these - // events out into the sensitivity list - bool is_top_level = container == proc->get_container() - && container->empty(); + // events out into the sensitivity list as long as we haven't + // promoted any preceding assignments to initialisers + bool is_top_level = + container == proc->get_container() + && container->empty() + && !proc->get_scope()->hoisted_initialiser(); // See if this can be implemented in a more idomatic way before we // fall back on the generic translation @@ -875,9 +916,12 @@ static int draw_if(vhdl_procedural *proc, stmt_container *container, vhdl_expr *test = translate_expr(ivl_stmt_cond_expr(stmt)); if (NULL == test) return 1; + + emit_wait_for_0(proc, container, stmt, test); vhdl_if_stmt *vhdif = new vhdl_if_stmt(test); - + container->add_stmt(vhdif); + ivl_statement_t cond_true_stmt = ivl_stmt_cond_true(stmt); if (cond_true_stmt) draw_stmt(proc, vhdif->get_then_container(), cond_true_stmt, is_last); @@ -886,8 +930,6 @@ static int draw_if(vhdl_procedural *proc, stmt_container *container, if (cond_false_stmt) draw_stmt(proc, vhdif->get_else_container(), cond_false_stmt, is_last); - container->add_stmt(vhdif); - return 0; } @@ -1403,8 +1445,12 @@ int draw_while(vhdl_procedural *proc, stmt_container *container, vhdl_type boolean(VHDL_TYPE_BOOLEAN); test = test->cast(&boolean); + emit_wait_for_0(proc, container, stmt, test); + vhdl_while_stmt *loop = new vhdl_while_stmt(test); draw_stmt(proc, loop->get_container(), ivl_stmt_sub_stmt(stmt)); + + emit_wait_for_0(proc, loop->get_container(), stmt, test); container->add_stmt(loop); return 0; diff --git a/tgt-vhdl/vhdl_syntax.cc b/tgt-vhdl/vhdl_syntax.cc index b1d87b832..a65bdf276 100644 --- a/tgt-vhdl/vhdl_syntax.cc +++ b/tgt-vhdl/vhdl_syntax.cc @@ -1,7 +1,7 @@ /* * VHDL abstract syntax elements. * - * Copyright (C) 2008 Nick Gasson (nick@nickg.me.uk) + * Copyright (C) 2008-2010 Nick Gasson (nick@nickg.me.uk) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,8 @@ using namespace std; vhdl_scope::vhdl_scope() - : parent_(NULL), init_(false), sig_assign_(true) + : parent_(NULL), init_(false), sig_assign_(true), + hoisted_init_(false) { } @@ -100,6 +101,16 @@ vhdl_scope *vhdl_scope::get_parent() const return parent_; } +bool vhdl_scope::hoisted_initialiser() const +{ + return hoisted_init_; +} + +void vhdl_scope::hoisted_initialiser(bool h) +{ + hoisted_init_ = h; +} + vhdl_entity::vhdl_entity(const string& name, vhdl_arch *arch, int depth__) : depth(depth__), name_(name), arch_(arch), time_unit_(TIME_UNIT_NS) @@ -193,6 +204,16 @@ void vhdl_arch::emit(std::ostream &of, int level) const blank_line(of, level); // Extra blank line after architectures; } +void vhdl_procedural::add_blocking_target(vhdl_var_ref* ref) +{ + blocking_targets_.insert(ref->get_name()); +} + +bool vhdl_procedural::is_blocking_target(vhdl_var_ref* ref) const +{ + return blocking_targets_.find(ref->get_name()) != blocking_targets_.end(); +} + void vhdl_process::add_sensitivity(const std::string &name) { sens_.push_back(name); @@ -399,6 +420,7 @@ void vhdl_wait_stmt::emit(std::ostream &of, int level) const } of << ";"; + emit_comment(of, level, true); } vhdl_decl::~vhdl_decl() @@ -630,7 +652,7 @@ vhdl_abstract_assign_stmt::~vhdl_abstract_assign_stmt() void vhdl_abstract_assign_stmt::find_vars(vhdl_var_set_t& read, vhdl_var_set_t& write) { - write.insert(lhs_); + lhs_->find_vars(write); rhs_->find_vars(read); } diff --git a/tgt-vhdl/vhdl_syntax.hh b/tgt-vhdl/vhdl_syntax.hh index 586a8630c..8bab208d8 100644 --- a/tgt-vhdl/vhdl_syntax.hh +++ b/tgt-vhdl/vhdl_syntax.hh @@ -51,7 +51,7 @@ public: virtual vhdl_expr *to_integer(); virtual vhdl_expr *to_std_logic(); virtual vhdl_expr *to_vector(vhdl_type_name_t name, int w); - virtual void find_vars(vhdl_var_set_t& read) const {} + virtual void find_vars(vhdl_var_set_t& read) {} protected: static void open_parens(ostream& of); @@ -768,6 +768,8 @@ public: bool initializing() const { return init_; } void set_initializing(bool i); + bool hoisted_initialiser() const; + void hoisted_initialiser(bool h); void set_allow_signal_assignment(bool b) { sig_assign_ = b; } bool allow_signal_assignment() const { return sig_assign_; } @@ -775,6 +777,7 @@ private: decl_list_t decls_; vhdl_scope *parent_; bool init_, sig_assign_; + bool hoisted_init_; }; @@ -793,6 +796,11 @@ public: void added_wait_stmt() { contains_wait_stmt_ = true; } bool contains_wait_stmt() const { return contains_wait_stmt_; } + + // Managing set of blocking assignment targets in this block + void add_blocking_target(vhdl_var_ref* ref); + bool is_blocking_target(vhdl_var_ref* ref) const; + protected: stmt_container stmts_; vhdl_scope scope_; @@ -802,6 +810,10 @@ protected: // If this is the case then we can't use a sensitvity list for // the process bool contains_wait_stmt_; + + // The set of variable we have performed a blocking + // assignment to + set blocking_targets_; }; diff --git a/tgt-vhdl/vhdl_target.h b/tgt-vhdl/vhdl_target.h index 8d99d97a1..083274252 100644 --- a/tgt-vhdl/vhdl_target.h +++ b/tgt-vhdl/vhdl_target.h @@ -31,7 +31,6 @@ string make_safe_name(ivl_signal_t sig); int draw_stask_display(vhdl_procedural *proc, stmt_container *container, ivl_statement_t stmt, bool newline = true); -void prune_wait_for_0(stmt_container *container); void require_support_function(support_function_t f); #endif /* #ifndef INC_VHDL_TARGET_H */