1 // Copyright 2017-2019 VMware, Inc.
2 // SPDX-License-Identifier: BSD-2-Clause
3 //
4 // The BSD-2 license (the License) set forth below applies to all parts of the
5 // Cascade project.  You may not use this file except in compliance with the
6 // License.
7 //
8 // BSD-2 License
9 //
10 // Redistribution and use in source and binary forms, with or without
11 // modification, are permitted provided that the following conditions are met:
12 //
13 // 1. Redistributions of source code must retain the above copyright notice, this
14 // list of conditions and the following disclaimer.
15 //
16 // 2. Redistributions in binary form must reproduce the above copyright notice,
17 // this list of conditions and the following disclaimer in the documentation
18 // and/or other materials provided with the distribution.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND
21 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 #ifndef CASCADE_SRC_TARGET_CORE_AVMM_AVMM_COMPILER_H
32 #define CASCADE_SRC_TARGET_CORE_AVMM_AVMM_COMPILER_H
33 
34 #include <cmath>
35 #include <condition_variable>
36 #include <limits>
37 #include <map>
38 #include <mutex>
39 #include <sstream>
40 #include <stdint.h>
41 #include <string>
42 #include <vector>
43 #include "common/indstream.h"
44 #include "target/compiler.h"
45 #include "target/core_compiler.h"
46 #include "target/core/avmm/avmm_logic.h"
47 #include "target/core/avmm/rewrite.h"
48 #include "verilog/analyze/evaluate.h"
49 #include "verilog/analyze/module_info.h"
50 #include "verilog/ast/ast.h"
51 
52 namespace cascade::avmm {
53 
54 template <size_t M, size_t V, typename A, typename T>
55 class AvmmCompiler : public CoreCompiler {
56   public:
57     AvmmCompiler();
58     ~AvmmCompiler() override = default;
59 
60     // Core Compiler Interface:
61     void stop_compile(Engine::Id id) override;
62 
63   protected:
64     // Avalon Memory Mapped Compiler Interface
65     //
66     // This method should perform whatever target-specific logic is necessary
67     // to return an instance of an AvmmLogic.
68     virtual AvmmLogic<V,A,T>* build(Interface* interface, ModuleDeclaration* md, size_t slot) = 0;
69     // This method should perform whatever target-specific logic is necessary
70     // to stop any previous compilations and compile text to a device. This
71     // method is called in a context where it holds the global lock on this
72     // compiler. Implementations for which this may take a long time should
73     // release this lock, but reaquire it before returning.  This method should
74     // return true on success, and false on failure, say if stop_compile
75     // interrupted a compilation.
76     virtual bool compile(const std::string& text, std::mutex& lock) = 0;
77     // This method should perform whatever target-specific logic is necessary
78     // to stop the execution of any invocations of compile().
79     virtual void stop_compile() = 0;
80 
81   private:
82     // Compilation States:
83     enum class State : uint8_t {
84       FREE = 0,
85       COMPILING,
86       WAITING,
87       STOPPED,
88       CURRENT
89     };
90     // Slot Information:
91     struct Slot {
92       Engine::Id id;
93       State state;
94       std::string text;
95     };
96 
97     // Program Management:
98     std::mutex lock_;
99     std::condition_variable cv_;
100     std::vector<Slot> slots_;
101 
102     // Core Compiler Interface:
103     AvmmLogic<V,A,T>* compile_logic(Engine::Id id, ModuleDeclaration* md, Interface* interface) override;
104 
105     // Slot Management Helpers:
106     int get_free() const;
107     void release(size_t slot);
108     void update();
109 
110     // Codegen Helpers:
111     std::string get_text();
112 };
113 
114 template <size_t M, size_t V, typename A, typename T>
AvmmCompiler()115 inline AvmmCompiler<M,V,A,T>::AvmmCompiler() : CoreCompiler() {
116   const auto num_slots = T(1) << M;
117   slots_.resize(num_slots, {0, State::FREE, ""});
118 }
119 
120 template <size_t M, size_t V, typename A, typename T>
stop_compile(Engine::Id id)121 inline void AvmmCompiler<M,V,A,T>::stop_compile(Engine::Id id) {
122   std::lock_guard<std::mutex> lg(lock_);
123 
124   // Free any slot with this id which is in the compiling or waiting state.
125   auto stopped = false;
126   auto need_new_lead = false;
127   for (auto& s : slots_) {
128     if (s.id == id) {
129       switch (s.state) {
130         case State::COMPILING:
131           need_new_lead = true;
132           // fallthrough
133         case State::WAITING:
134           stopped = true;
135           s.state = State::STOPPED;
136           break;
137         default:
138           break;
139       }
140     }
141   }
142   // If nothing was stopped, we can return immediately.
143   if (!stopped) {
144     return;
145   }
146   // If we need a new compilation lead, find a waiting slot and promote it.
147   if (need_new_lead) {
148     for (auto& s : slots_) {
149       if (s.state == State::WAITING) {
150         s.state = State::COMPILING;
151         break;
152       }
153     }
154   }
155   // Target-specific implementation of stop logic
156   stop_compile();
157 
158   // Notify any waiting threads that the slot table has changed.
159   cv_.notify_all();
160 }
161 
162 template <size_t M, size_t V, typename A, typename T>
compile_logic(Engine::Id id,ModuleDeclaration * md,Interface * interface)163 inline AvmmLogic<V,A,T>* AvmmCompiler<M,V,A,T>::compile_logic(Engine::Id id, ModuleDeclaration* md, Interface* interface) {
164   std::unique_lock<std::mutex> lg(lock_);
165   ModuleInfo info(md);
166 
167   // Check for unsupported language features
168   auto unsupported = false;
169   if (info.uses_mixed_triggers()) {
170     get_compiler()->error("Avmm backends do not currently support code with mixed triggers!");
171     unsupported = true;
172   } else if (!info.implied_latches().empty()) {
173     get_compiler()->error("Avmm backends do not currently support the use of implied latches!");
174     unsupported = true;
175   } else if (info.uses_multiple_clocks()) {
176     get_compiler()->error("Avmm backends do not currently support the use of multiple clocks!");
177     unsupported = true;
178   }
179   if (unsupported) {
180     delete md;
181     return nullptr;
182   }
183 
184   // Find a free slot
185   const auto slot = get_free();
186   if (slot == -1) {
187     get_compiler()->error("No remaining slots available on Avmm device");
188     delete md;
189     return nullptr;
190   }
191 
192   // Register inputs, state, and outputs. Invoke these methods
193   // lexicographically to ensure a deterministic variable table ordering. The
194   // final invocation of index_tasks is lexicographic by construction, as it's
195   // based on a recursive descent of the AST.
196   auto* al = build(interface, md, slot);
197   std::map<VId, const Identifier*> is;
198   for (auto* i : info.inputs()) {
199     is.insert(std::make_pair(to_vid(i), i));
200   }
201   for (const auto& i : is) {
202     al->set_input(i.second, i.first);
203   }
204   std::map<VId, const Identifier*> ss;
205   for (auto* s : info.stateful()) {
206     ss.insert(std::make_pair(to_vid(s), s));
207   }
208   for (const auto& s : ss) {
209     al->set_state(s.second, s.first);
210   }
211   std::map<VId, const Identifier*> os;
212   for (auto* o : info.outputs()) {
213     os.insert(std::make_pair(to_vid(o), o));
214   }
215   for (const auto& o : os) {
216     al->set_output(o.second, o.first);
217   }
218   al->index_tasks();
219   // Check table and index sizes. If this program uses too much state, we won't
220   // be able to uniquely name its elements using our current addressing scheme.
221 
222   const auto max_vars = T(1) << V;
223   if (al->get_table()->size() >= max_vars) {
224     std::stringstream ss;
225     ss << "Avmm backends do not currently support more than " << max_vars << " entries in variable table";
226     get_compiler()->error(ss.str());
227     delete al;
228     return nullptr;
229   }
230 
231   // Downgrade any compilation slots to waiting slots, and stop any slots that
232   // are working on this id.
233   for (auto& s : slots_) {
234     if (s.state == State::COMPILING) {
235       s.state = State::WAITING;
236     }
237     if ((s.id == id) && (s.state == State::WAITING)) {
238       s.state = State::STOPPED;
239     }
240   }
241   // This slot is now the compile lead
242   slots_[slot].id = id;
243   slots_[slot].state = State::COMPILING;
244   slots_[slot].text = Rewrite<M,V,A,T>().run(md, slot, al->get_table(), al->open_loop_clock());
245   // Enter into compilation state machine. Control will exit from this loop
246   // either when compilation succeeds or is aborted.
247   while (true) {
248     switch (slots_[slot].state) {
249       case State::COMPILING:
250         if (compile(get_text(), lock_)) {
251           update();
252         }
253         break;
254       case State::WAITING:
255         cv_.wait(lg);
256         break;
257       case State::STOPPED:
258         slots_[slot].state = State::FREE;
259         delete al;
260         return nullptr;
261       case State::CURRENT:
262         al->set_callback([this, slot]{release(slot);});
263         return al;
264       default:
265         // Control should never reach here
266         assert(false);
267         break;
268     }
269   }
270 }
271 
272 template <size_t M, size_t V, typename A, typename T>
get_free()273 inline int AvmmCompiler<M,V,A,T>::get_free() const {
274   for (size_t i = 0, ie = slots_.size(); i < ie; ++i) {
275     if (slots_[i].state == State::FREE) {
276       return i;
277     }
278   }
279   return -1;
280 }
281 
282 template <size_t M, size_t V, typename A, typename T>
release(size_t slot)283 inline void AvmmCompiler<M,V,A,T>::release(size_t slot) {
284   // Return this slot to the pool if necessary. This method is only invoked on
285   // successfully compiled cores, which means we don't have to worry about
286   // transfering compilation ownership or invoking stop_compile.
287   std::lock_guard<std::mutex> lg(lock_);
288   assert(slots_[slot].state == State::CURRENT);
289   slots_[slot].state = State::FREE;
290   cv_.notify_all();
291 }
292 
293 template <size_t M, size_t V, typename A, typename T>
update()294 inline void AvmmCompiler<M,V,A,T>::update() {
295   for (auto& s : slots_) {
296     if ((s.state == State::COMPILING) || (s.state == State::WAITING)) {
297       s.state = State::CURRENT;
298     }
299   }
300   cv_.notify_all();
301 }
302 
303 template <size_t M, size_t V, typename A, typename T>
get_text()304 inline std::string AvmmCompiler<M,V,A,T>::get_text() {
305   std::stringstream ss;
306   indstream os(ss);
307 
308   // Generate code for modules
309   std::map<MId, std::string> text;
310   for (size_t i = 0, ie = slots_.size(); i < ie; ++i) {
311     if (slots_[i].state != State::FREE) {
312       text.insert(std::make_pair(i, slots_[i].text));
313     }
314   }
315 
316   // Module Declarations
317   for (const auto& s : text) {
318     os << s.second << std::endl;
319     os << std::endl;
320   }
321 
322   // Top-level Module
323   os << "module program_logic(" << std::endl;
324   os.tab();
325   os << "input wire clk," << std::endl;
326   os << "input wire reset," << std::endl;
327   os << std::endl;
328   os << "input wire[" << (std::numeric_limits<A>::digits-1) << ":0]  s0_address," << std::endl;
329   os << "input wire s0_read," << std::endl;
330   os << "input wire s0_write," << std::endl;
331   os << std::endl;
332   os << "output wire[" << (std::numeric_limits<T>::digits-1) << ":0] s0_readdata," << std::endl;
333   os << "input  wire[" << (std::numeric_limits<T>::digits-1) << ":0] s0_writedata," << std::endl;
334   os << std::endl;
335   os << "output wire s0_waitrequest" << std::endl;
336   os.untab();
337   os << ");" << std::endl;
338   os.tab();
339 
340   os << "// Unpack address into module id and variable id" << std::endl;
341   os << "wire[" << (M-1) << ":0] __mid = s0_address[" << (V+M-1) << ":" << V << "];" << std::endl;
342   os << "wire[" << (V-1) << ":0] __vid = s0_address[" << (V-1) << ":0];" << std::endl;
343 
344   os << "// Module Instantiations:" << std::endl;
345   for (const auto& s : text) {
346     os << "wire[" << (std::numeric_limits<T>::digits-1) << ":0] m" << s.first << "_out;" << std::endl;
347     os << "wire m" << s.first << "_wait;" << std::endl;
348     os << "M" << s.first << " m" << s.first << "(" << std::endl;
349     os.tab();
350     os << ".__clk(clk)," << std::endl;
351     os << ".__read((__mid == " << s.first << ") & s0_write)," << std::endl;
352     os << ".__write((__mid == " << s.first << ") & s0_read)," << std::endl;
353     os << ".__vid(__vid)," << std::endl;
354     os << ".__in(s0_writedata)," << std::endl;
355     os << ".__out(m" << s.first << "_out)," << std::endl;
356     os << ".__wait(m" << s.first << "_wait)" << std::endl;
357     os.untab();
358     os << ");" << std::endl;
359   }
360 
361   os << "// Output Demuxing:" << std::endl;
362   os << "reg[" << (std::numeric_limits<T>::digits-1) << ":0] rd;" << std::endl;
363   os << "reg wr;" << std::endl;
364   os << "always @(*) begin" << std::endl;
365   os.tab();
366   os << "case (__mid)" << std::endl;
367   os.tab();
368   for (const auto& s : text) {
369     os << s.first << ": begin rd = m" << s.first << "_out; wr = m" << s.first << "_wait; end" << std::endl;
370   }
371   os << "default: begin rd = 0; wr = 0; end" << std::endl;
372   os.untab();
373   os << "endcase" << std::endl;
374   os.untab();
375   os << "end" << std::endl;
376 
377   os << "// Output Logic:" << std::endl;
378   os << "assign s0_waitrequest = (s0_read | s0_write) ? wr : 1'b1;" << std::endl;
379   os << "assign s0_readdata = rd;" << std::endl;
380 
381   os.untab();
382   os << "endmodule";
383 
384   return ss.str();
385 }
386 
387 } // namespace cascade::avmm
388 
389 #endif
390