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 #include "target/core/avmm/de10/quartus_server.h"
32 
33 #include <cstdlib>
34 #include <fstream>
35 #include <sstream>
36 #include "common/sockserver.h"
37 #include "common/sockstream.h"
38 #include "common/system.h"
39 
40 using namespace std;
41 
42 namespace cascade::avmm {
43 
QuartusServer()44 QuartusServer::QuartusServer() : Thread() {
45   set_cache_path("/tmp/quartus_cache/");
46   set_quartus_path("");
47   set_quartus_tunnel_command("");
48   set_port(9900);
49 
50   busy_ = false;
51 }
52 
set_cache_path(const string & path)53 QuartusServer& QuartusServer::set_cache_path(const string& path) {
54   cache_path_ = path;
55   return *this;
56 }
57 
set_quartus_path(const string & path)58 QuartusServer& QuartusServer::set_quartus_path(const string& path) {
59   quartus_path_ = path;
60   return *this;
61 }
62 
set_quartus_tunnel_command(const string & tunnel_command)63 QuartusServer& QuartusServer::set_quartus_tunnel_command(const string& tunnel_command) {
64   quartus_tunnel_command_ = tunnel_command;
65   return *this;
66 }
67 
set_port(uint32_t port)68 QuartusServer& QuartusServer::set_port(uint32_t port) {
69   port_ = port;
70   return *this;
71 }
72 
error() const73 bool QuartusServer::error() const {
74   // Return true if we can't locate any of the necessary quartus components
75   if (System::execute(quartus_tunnel_command_ + " ls " + quartus_path_ + "/sopc_builder/bin/qsys-generate > /dev/null") != 0) {
76     return true;
77   }
78   if (System::execute(quartus_tunnel_command_ + " ls " + quartus_path_ + "/bin/quartus_map > /dev/null") != 0) {
79     return true;
80   }
81   if (System::execute(quartus_tunnel_command_ + " ls " + quartus_path_ + "/bin/quartus_fit > /dev/null") != 0) {
82     return true;
83   }
84   if (System::execute(quartus_tunnel_command_ + " ls " + quartus_path_ + "/bin/quartus_asm > /dev/null") != 0) {
85     return true;
86   }
87   if (System::execute(quartus_tunnel_command_ + " ls " + quartus_path_ + "/bin/quartus_pgm > /dev/null") != 0) {
88     return true;
89   }
90   return false;
91 }
92 
run_logic()93 void QuartusServer::run_logic() {
94   // Initialize thread pool and comilation cache
95   init_pool();
96   init_cache();
97 
98   // Return immediately if we can't create a sockserver
99   sockserver server(port_, 8);
100   if (server.error()) {
101     pool_.stop_now();
102     return;
103   }
104 
105   fd_set master_set;
106   FD_ZERO(&master_set);
107   FD_SET(server.descriptor(), &master_set);
108 
109   fd_set read_set;
110   FD_ZERO(&read_set);
111 
112   struct timeval timeout = {1, 0};
113 
114   while (!stop_requested()) {
115     read_set = master_set;
116     select(server.descriptor()+1, &read_set, nullptr, nullptr, &timeout);
117     if (!FD_ISSET(server.descriptor(), &read_set)) {
118       continue;
119     }
120 
121     auto* sock = server.accept();
122     const auto rpc = static_cast<QuartusServer::Rpc>(sock->get());
123 
124     // At most one compilation thread can be active at once. Issue kill-alls
125     // until this is no longer the case.
126     if (rpc == Rpc::KILL_ALL) {
127       while (busy_) {
128         kill_all();
129         this_thread::sleep_for(chrono::seconds(1));
130       }
131       sock->put(static_cast<uint8_t>(Rpc::OKAY));
132       sock->flush();
133       delete sock;
134     }
135     // Kill the one compilation thread if necessary and then fire off a new thread to
136     // attempt a recompilation. When the new thread is finished it will reset the busy
137     // flag.
138     else if (rpc == Rpc::COMPILE) {
139       while (busy_) {
140         kill_all();
141         this_thread::sleep_for(chrono::seconds(1));
142       }
143       sock->put(static_cast<uint8_t>(Rpc::OKAY));
144       sock->flush();
145       busy_ = true;
146 
147       pool_.insert([this, sock]{
148         string text = "";
149         getline(*sock, text, '\0');
150         const auto res = compile(text);
151         sock->put(static_cast<uint8_t>(res ? Rpc::OKAY : Rpc::ERROR));
152         sock->flush();
153 
154         if (res) {
155           sock->get();
156 
157           const auto itr = cache_.find(text);
158           assert(itr != cache_.end());
159           ifstream ifs(cache_path_ + "/" + itr->second, ios::binary);
160 
161           stringstream rbf;
162           rbf << ifs.rdbuf();
163           uint32_t len = rbf.str().length();
164 
165           sock->write(reinterpret_cast<const char*>(&len), sizeof(len));
166           sock->write(rbf.str().c_str(), len);
167           sock->flush();
168 
169           sock->get();
170         }
171 
172         busy_ = false;
173         delete sock;
174       });
175     }
176     // Unrecognized RPC
177     else {
178       assert(false);
179       delete sock;
180     }
181   }
182 
183   // Stop the thread pool
184   pool_.stop_now();
185 }
186 
init_pool()187 void QuartusServer::init_pool() {
188   // We have the invariant that there is exactly one compile thread out at any
189   // given time, so no need to prime the pool with anything more than that.
190   pool_.stop_now();
191   pool_.set_num_threads(1);
192   pool_.run();
193 }
194 
init_cache()195 void QuartusServer::init_cache() {
196   // Create the cache if it doesn't already exist
197   System::execute("mkdir -p " + cache_path_);
198   System::execute("touch " + cache_path_ + "/index.txt");
199 
200   // Read cache into memory
201   ifstream ifs(cache_path_ + "/index.txt");
202   cache_.clear();
203   while (true) {
204     string text;
205     getline(ifs, text, '\0');
206     if (ifs.eof()) {
207       break;
208     }
209 
210     string path;
211     getline(ifs, path, '\0');
212     cache_.insert(make_pair(text, path));
213   }
214 }
215 
kill_all()216 void QuartusServer::kill_all() {
217   // Note that we don't kill anything downstream of place and route as these
218   // passess all run to completion in short order.
219   System::execute(R"(pkill -9 -P `ps -ax | grep build_de10.sh | awk '{print $1}' | head -n1`)");
220 }
221 
compile(const std::string & text)222 bool QuartusServer::compile(const std::string& text) {
223   // Nothing to do if this code is already in the cache.
224   if (cache_.find(text) != cache_.end()) {
225     return true;
226   }
227 
228   // Otherwise, compile the code and add a new entry to the cache
229   System::execute("mkdir -p /tmp/de10/");
230   char path[] = "/tmp/de10/program_logic_XXXXXX.v";
231   const auto fd = mkstemps(path, 2);
232   const auto dir = string(path).substr(0, 30);
233   close(fd);
234 
235   System::execute("cp -R " + System::src_root() + "/share/cascade/de10 " + dir);
236   ofstream ofs(path);
237   ofs << text << endl;
238   ofs.close();
239   System::execute("mv " + string(path) + " " + dir + "/ip/program_logic.v");
240 
241   // Note that kill all will only stop this first script. If control reaches
242   // the second (which should finish quickly), we allow it to run to completion
243   // (and assume it will do so without error).
244   const auto res = System::no_block_execute("cd " + dir + " && ./build_de10.sh \"" + quartus_tunnel_command_ + " " + quartus_path_ + "\"", true);
245   if (res != 0) {
246     return false;
247   }
248   System::no_block_execute("cd " + dir + " && ./assemble_de10.sh \"" + quartus_tunnel_command_ + " " + quartus_path_ + "\"", true);
249 
250   stringstream ss;
251   ss << "bitstream_" << cache_.size() << ".rbf";
252   const auto file = ss.str();
253   System::execute("cp " + dir + "/output_files/DE10_NANO_SoC_GHRD.rbf " + cache_path_ + "/" + file);
254 
255   ofstream ofs2(cache_path_ + "/index.txt", ios::app);
256   ofs2 << text << '\0' << file << '\0';
257   ofs2.flush();
258 
259   cache_[text] = file;
260 
261   return true;
262 }
263 
264 } // namespace cascade::avmm
265