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