%{ /* * Copyright (c) 2000-2021 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 * General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ # include "config.h" # include /* * This file implements synthesis based on matching threads and * converting them to equivalent devices. The trick here is that the * proc_match_t functor can be used to scan a process and generate a * string of tokens. That string of tokens can then be matched by the * rules to determine what kind of device is to be made. */ # include "netlist.h" # include "netmisc.h" # include "functor.h" # include using namespace std; struct syn_token_t { int token; NetAssignBase*assign; NetProcTop*top; NetEvWait*evwait; NetEvent*event; NetExpr*expr; syn_token_t*next_; }; #define YYSTYPE syn_token_t* static int yylex(); static void yyerror(const char*); static Design*des_; static void make_DFF_CE(Design*des, NetProcTop*top, NetEvent*eclk, NetExpr*cexp, NetAssignBase*asn); %} %token S_ALWAYS S_ASSIGN S_ASSIGN_MEM S_ASSIGN_MUX S_ELSE S_EVENT %token S_EXPR S_IF S_INITIAL %% start /* These rules match simple DFF devices. Clocked assignments are simply implemented as DFF, and a CE is easily expressed with a conditional statement. The typical Verilog that get these are: always @(posedge CLK) Q = D always @(negedge CLK) Q = D always @(posedge CLK) if (CE) Q = D; always @(negedge CLK) if (CE) Q = D; The width of Q and D cause a wide register to be created. The code generators generally implement that as an array of flip-flops. */ : S_ALWAYS '@' '(' S_EVENT ')' S_ASSIGN ';' { make_DFF_CE(des_, $1->top, $4->event, 0, $6->assign); } | S_ALWAYS '@' '(' S_EVENT ')' S_IF S_EXPR S_ASSIGN ';' ';' { make_DFF_CE(des_, $1->top, $4->event, $7->expr, $8->assign); } /* Unconditional assignments in initial blocks should be made into initializers wherever possible. */ ; %% /* Various actions. */ static void hookup_DFF_CE(NetFF*ff, NetESignal*d, NetEvProbe*pclk, NetNet*ce, NetAssign_*a, unsigned rval_pinoffset) { if (rval_pinoffset != 0) { cerr << a->get_fileline() << ": sorry: " << "unable to hook up an R-value with offset " << rval_pinoffset << " to signal " << a->name() << "." << endl; return; } // a->sig() is a *NetNet, which doesn't have the loff_ and // lwid_ context. Add the correction for loff_ ourselves. // This extra calculation allows for assignments like: // lval[7:1] <= foo; // where lval is really a "reg [7:0]". In other words, part // selects in the l-value are handled by loff and the lwidth(). connect(ff->pin_Data(), d->sig()->pin(0)); connect(ff->pin_Q(), a->sig()->pin(0)); connect(ff->pin_Clock(), pclk->pin(0)); if (ce) connect(ff->pin_Enable(), ce->pin(0)); /* This lval_ represents a reg that is a WIRE in the synthesized results. This function signals the destructor to change the REG that this l-value refers to into a WIRE. It is done then, at the last minute, so that pending synthesis can continue to work with it as a WIRE. */ a->turn_sig_to_wire_on_release(); } static void make_DFF_CE(Design*des, NetProcTop*top, NetEvent*eclk, NetExpr*cexp, NetAssignBase*asn) { assert(asn); NetEvProbe*pclk = eclk->probe(0); NetESignal*d = dynamic_cast (asn->rval()); NetNet*ce = cexp? cexp->synthesize(des, top->scope(), cexp) : 0; if (d == 0) { cerr << asn->get_fileline() << ": internal error: " << " not a simple signal? " << *asn->rval() << endl; } assert(d); NetAssign_*a; unsigned rval_pinoffset=0; for (unsigned i=0; (a=asn->l_val(i)); i++) { // asn->l_val(i) are the set of *NetAssign_'s that form the list // of lval expressions. Treat each one independently, keeping // track of which bits of rval to use for each set of DFF inputs. // For example, given: // {carry,data} <= x + y + z; // run through this loop twice, where a and rval_pinoffset are // first data and 0, then carry and 1. // FIXME: ff gets its pin names wrong when loff_ is nonzero. if (a->sig()) { // cerr << "new NetFF named " << a->name() << endl; bool negedge = pclk->edge() == NetEvProbe::NEGEDGE; NetFF*ff = new NetFF(top->scope(), a->name(), negedge, a->sig()->vector_width()); hookup_DFF_CE(ff, d, pclk, ce, a, rval_pinoffset); des->add_node(ff); } rval_pinoffset += a->lwidth(); } des->delete_process(top); } static syn_token_t*first_ = 0; static syn_token_t*last_ = 0; static syn_token_t*ptr_ = 0; /* * The match class is used to take a process and turn it into a stream * of tokens. This stream is used by the yylex function to feed tokens * to the parser. */ struct tokenize : public proc_match_t { tokenize() { } ~tokenize() { } int assign(NetAssign*dev) { syn_token_t*cur; cur = new syn_token_t; // Bit Muxes can't be synthesized (yet), but it's too much // work to detect them now. // cur->token = dev->l_val(0)->bmux() ? S_ASSIGN_MUX : S_ASSIGN; cur->token = S_ASSIGN; cur->assign = dev; cur->next_ = 0; last_->next_ = cur; last_ = cur; return 0; } int assign_nb(NetAssignNB*dev) { syn_token_t*cur; cur = new syn_token_t; // Bit Muxes can't be synthesized (yet), but it's too much // work to detect them now. // cur->token = dev->l_val(0)->bmux() ? S_ASSIGN_MUX : S_ASSIGN; cur->token = S_ASSIGN; cur->assign = dev; cur->next_ = 0; last_->next_ = cur; last_ = cur; return 0; } int condit(NetCondit*dev) { syn_token_t*cur; cur = new syn_token_t; cur->token = S_IF; cur->next_ = 0; last_->next_ = cur; last_ = cur; cur = new syn_token_t; cur->token = S_EXPR; cur->expr = dev->expr(); cur->next_ = 0; last_->next_ = cur; last_ = cur; /* Because synthesis is broken this is needed to prevent * a seg. fault. */ if (!dev->if_clause()) return 0; dev -> if_clause() -> match_proc(this); if (dev->else_clause()) { cur = new syn_token_t; cur->token = S_ELSE; cur->next_ = 0; last_->next_ = cur; last_ = cur; dev -> else_clause() -> match_proc(this); } cur = new syn_token_t; cur->token = ';'; cur->next_ = 0; last_->next_ = cur; last_ = cur; return 0; } int event_wait(NetEvWait*dev) { syn_token_t*cur; cur = new syn_token_t; cur->token = '@'; cur->evwait = dev; cur->next_ = 0; last_->next_ = cur; last_ = cur; cur = new syn_token_t; cur->token = '('; cur->next_ = 0; last_->next_ = cur; last_ = cur; for (unsigned idx = 0; idx < dev->nevents(); idx += 1) { cur = new syn_token_t; cur->token = S_EVENT; cur->event = dev->event(idx); cur->next_ = 0; last_->next_ = cur; last_ = cur; } cur = new syn_token_t; cur->token = ')'; cur->next_ = 0; last_->next_ = cur; last_ = cur; dev -> statement() -> match_proc(this); cur = new syn_token_t; cur->token = ';'; cur->next_ = 0; last_->next_ = cur; last_ = cur; return 0; } }; static void syn_start_process(NetProcTop*t) { first_ = new syn_token_t; last_ = first_; ptr_ = first_; // Can the following be converted into S_ALWAYS? if ((t->type() == IVL_PR_ALWAYS_COMB) || (t->type() == IVL_PR_ALWAYS_FF) || (t->type() == IVL_PR_ALWAYS_LATCH)) { cerr << t->get_fileline() << ": internal error: " << " Need to check if this can be synthesized." << endl; assert(0); } first_->token = (t->type() == IVL_PR_ALWAYS)? S_ALWAYS : S_INITIAL; first_->top = t; first_->next_ = 0; tokenize go; t -> statement() -> match_proc(&go); } static void syn_done_process() { while (first_) { syn_token_t*cur = first_; first_ = cur->next_; delete cur; } } static int yylex() { if (ptr_ == 0) { yylval = 0; return 0; } yylval = ptr_; ptr_ = ptr_->next_; return yylval->token; } struct syn_rules_f : public functor_t { ~syn_rules_f() { } void process(class Design*, class NetProcTop*top) { /* If the scope that contains this process as a cell attribute attached to it, then skip synthesis. */ if (top->scope()->attribute(perm_string::literal("ivl_synthesis_cell")).len() > 0) return; syn_start_process(top); yyparse(); syn_done_process(); } }; void syn_rules(Design*d) { des_ = d; syn_rules_f obj; des_->functor(&obj); } static void yyerror(const char*) { }