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