OpenASIP 2.2
Loading...
Searching...
No Matches
RISCVInstructionExecutor.cc
Go to the documentation of this file.
1/*
2 Copyright (C) 2025 Tampere University.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18/**
19 * @file RISCVInstructionExecutor.cc
20 *
21 * Definition of RISCVInstructionExecutor class.
22 *
23 * @author Eetu Soronen 2025 (eetu.soronen@tuni.fi)
24 * @note rating: red
25 */
26
27#include <memory>
28#include <string>
29#include <vector>
30
31#include "BEMGenerator.hh"
32#include "BinaryEncoding.hh"
33#include "InstructionFormat.hh"
34#include "Machine.hh"
35#include "Operation.hh"
36#include "OperationContext.hh"
37#include "OperationPool.hh"
38#include "RISCVFields.hh"
39#include "RISCVTools.hh"
40
42constexpr unsigned OPC_CUSTOM_0 = 0b0001011;
43constexpr unsigned OPC_CUSTOM_1 = 0b0101011;
44
45std::map<std::string, int> customOps = {};
46std::unique_ptr<OperationPool> pool = nullptr;
47
48/**
49 * Helper function that executes an OSAL instruction.
50 *
51 * Takes in the opName, instruction width, and the inputs, and returns the
52 * output vector.
53 *
54 * @param opName operation to be executed
55 * @param width instruction width (typically 32 or 64)
56 * @param inputs array of input values
57 * @param inputsCount number of values in the inputs array
58 * @return vector of SimValue objects.
59 */
60std::vector<SimValue>
62 const char* opName, uint8_t width, const uint64_t* inputs,
63 int inputsCount) {
64 if (RISCVInstructionExecutor::pool == nullptr) {
65 RISCVInstructionExecutor::pool = std::make_unique<OperationPool>();
66 }
67
68 Operation& op = RISCVInstructionExecutor::pool->operation(opName);
69 if (&op == &NullOperation::instance()) {
72 std::string("ExecuteInstruction error: No behavior "
73 "implementation found for operation '") +
74 opName + "'");
75 }
76 OperationBehavior& behavior = op.behavior();
77
78 if (&behavior == &NullOperationBehavior::instance()) {
79 throw std::logic_error(
80 std::string("ExecuteInstruction error: No behavior "
81 "implementation found for operation '") +
82 opName + "'");
83 }
84
85 OperationContext opContext(opName);
86 behavior.createState(opContext);
87
88 if (inputsCount < op.numberOfInputs()) {
89 behavior.deleteState(opContext);
92 std::string("ExecuteInstruction error: Not enough input values"));
93 }
94
95 const int opInputs = op.numberOfInputs();
96 const int opOutputs = op.numberOfOutputs();
97 const int opValues = opInputs + opOutputs;
98
99 std::vector<std::unique_ptr<SimValue>> simValues(opValues);
100
101 for (int i = 0; i < opInputs; ++i) {
102 simValues[i] = std::make_unique<SimValue>(inputs[i], width);
103 }
104 for (int i = 0; i < opOutputs; ++i) {
105 simValues[opInputs + i] = std::make_unique<SimValue>(0, width);
106 }
107
108 std::vector<SimValue*> simValPtrs(opValues);
109 for (int i = 0; i < opValues; ++i) {
110 simValPtrs[i] = simValues[i].get();
111 }
112
113 if (!behavior.simulateTrigger(simValPtrs.data(), opContext)) {
114 behavior.deleteState(opContext);
117 std::string(
118 "ExecuteInstruction error: operation execution failed"));
119 }
120
121 std::vector<SimValue> results(opOutputs);
122 for (int i = 0; i < opOutputs; i++) {
123 results[i] = *simValues[opInputs + i];
124 }
125
126 behavior.deleteState(opContext);
127 return results;
128}
129
130} // namespace RISCVInstructionExecutor
131
132extern "C" {
133
134/**
135 * Initializes the OpenASIP Machine.
136 *
137 * Should be called before other functions. Creates the <instruction name,
138 * opcode> map for the custom operations, and the OperationPool instruction
139 * cache object.
140 *
141 * @param machinePath path to the .adf machine file.
142 * @param error error messages in case of failure. Can also be a nullptr if
143 * desired. Must be freed by the client.
144 * @return 0 on success, -1 on failure.
145 */
146int
147initializeMachine(const char* machinePath, char** error) {
148 std::unique_ptr<TTAMachine::Machine> machine = nullptr;
149
150 try {
151 machine = std::unique_ptr<TTAMachine::Machine>(
153 } catch (const SerializerException& e) {
154 if (error != nullptr) {
155 std::string errorStr =
156 std::string("InitializeMachine error: ") + e.errorMessage();
157 *error = strdup(errorStr.c_str());
158 }
159 return -1;
160 }
161 BinaryEncoding* bem = BEMGenerator(*machine).generate();
162 if (bem == nullptr) {
163 *error = strdup("InitializeMachine error: failed to generate bem");
164 return -1;
165 }
166
169 *error = strdup(
170 "InitializeMachine error: failed to generate custom ops map");
171 return -1;
172 }
173
174 if (RISCVInstructionExecutor::pool == nullptr) {
175 RISCVInstructionExecutor::pool = std::make_unique<OperationPool>();
176 }
177
178 delete bem;
179 return 0;
180}
181
182/**
183 * Resets the machine to uninitialized state.
184 *
185 * Deletes the customOps map loaded and deletes the
186 * OperationPool instruction cache.
187 */
188int
194
195/**
196 * Unpacks a RISC-V R4-type instruction and returns its string representation
197 * if found from the machine file.
198 *
199 * Remember to call Initialize machine first.
200 *
201 * @param opcode full RISC-V opcode. Register values are ignored.
202 * @param output The char* representation of the opcode, if it is found. Must
203 * be freed by the client.
204 * @param error Error messages in case of failure. Must be freed by the
205 * client.
206 * @return 0 on success, -1 on failure.
207 */
208int
209unpackInstruction(uint32_t instruction, char** output, char** error) {
211 *error = strdup(
212 "UnpackInstruction error: customOps map is empty. Did you "
213 "initialize the machine first?");
214 return -1;
215 }
216
217 if (output == nullptr) {
218 if (error != nullptr) {
219 *error =
220 strdup("UnpackInstruction error: Output parameter is null");
221 }
222 return -1;
223 }
224
225 R4Instruction decodedInstruction =
227
228 bool isCustom0 =
229 (decodedInstruction.baseopcode ==
231 bool isCustom1 =
232 (decodedInstruction.baseopcode ==
234
235 if (!isCustom0 && !isCustom1) {
236 if (error != nullptr) {
237 *error = strdup("UnpackInstruction error: Unknown base opcode");
238 }
239 return -1;
240 }
241
242 std::string opName = "";
243 for (const auto& op : RISCVInstructionExecutor::customOps) {
244 uint32_t encoding = op.second;
245 bool isMatch = false;
246
247 if (isCustom1 &&
248 RISCVTools::getFunc2Int(encoding) == decodedInstruction.funct2 &&
249 RISCVTools::getFunc3Int(encoding) == decodedInstruction.funct3) {
250 isMatch = true;
251 } else if (
252 isCustom0 &&
253 RISCVTools::getFunc7Int(encoding) == decodedInstruction.funct7 &&
254 RISCVTools::getFunc3Int(encoding) == decodedInstruction.funct3) {
255 isMatch = true;
256 }
257
258 if (isMatch) {
259 *output = strdup(op.first.c_str());
260 return 0;
261 }
262 }
263
264 if (error != nullptr) {
265 *error = strdup("UnpackInstruction error: Operation not found");
266 }
267 return -1;
268}
269/**
270 * Executes a custom 32-wide instruction.
271 *
272 * The instruction behavior is searched automatically by OperationPool
273 * from the OSAL search paths. See chapter 4.4 in the manual.
274 *
275 * @param opName The operation name as it is in the machine file.
276 * @param inputs Input value(s) of the operation. Can contain more values
277 * than the operation needs, in that case only the first values will be
278 * used.
279 * @param inputsCount number of inputs
280 * @param output The result of the operation.
281 * @param error Will not be touched in case of success. Must be freed by
282 * the client.
283 * @return 0 on success, -1 on failure.
284 */
285int
287 const char* opName, const uint32_t* inputs, uint32_t inputsCount,
288 uint32_t* output, char** error) {
289 if (output == nullptr) {
290 if (error != nullptr) {
291 *error = strdup(
292 "ExecuteInstruction32 error: Output parameter is null");
293 }
294 return -1;
295 }
296
297 try {
298 std::vector<uint64_t> inputs64(inputsCount);
299 for (uint32_t i = 0; i < inputsCount; i++) {
300 inputs64[i] = static_cast<uint64_t>(inputs[i]);
301 }
302
303 std::vector<SimValue> results =
305 opName, 32, inputs64.data(), inputsCount);
306 for (size_t i = 0; i < results.size(); i++) {
307 output[i] = results.at(i).uIntWordValue();
308 }
309 return 0;
310 } catch (Exception& e) {
311 if (error != nullptr) {
312 *error = strdup(e.errorMessage().c_str());
313 }
314 return -1;
315 }
316}
317
318/**
319 * Executes a custom 64-wide instruction.
320 *
321 * The instruction behavior is searched automatically by OperationPool
322 * from the OSAL search paths. See chapter 4.4 in the manual.
323 *
324 * @param opName The operation name as it is in the machine file.
325 * @param inputs Input value(s) of the operation. Can contain more values
326 * than the operation needs, in that case only the first values will be
327 * used.
328 * @param inputsCount number of inputs
329 * @param output The result of the operation.
330 * @param error Will not be touched in case of success. Must be freed by
331 * the client.
332 * @return 0 on success, -1 on failure.
333 */
334int
336 const char* opName, const uint64_t* inputs, uint32_t inputsCount,
337 uint64_t* output, char** error) {
338 if (output == nullptr) {
339 if (error != nullptr) {
340 *error = strdup(
341 "ExecuteInstruction64 error: Output parameter is null");
342 }
343 return -1;
344 }
345
346 try {
347 std::vector<SimValue> results =
349 opName, 64, inputs, inputsCount);
350 for (size_t i = 0; i < results.size(); i++) {
351 output[i] = results.at(i).uLongWordValue();
352 }
353 return 0;
354 } catch (Exception& e) {
355 if (error != nullptr) {
356 *error = strdup(e.errorMessage().c_str());
357 }
358 return -1;
359 }
360}
361}
TTAMachine::Machine * machine
the architecture definition of the estimated processor
#define THROW_EXCEPTION(exceptionType, message)
Exception wrapper macro that automatically includes file name, line number and function name where th...
Definition Exception.hh:39
int executeInstruction32(const char *opName, const uint32_t *inputs, uint32_t inputsCount, uint32_t *output, char **error)
int initializeMachine(const char *machinePath, char **error)
int executeInstruction64(const char *opName, const uint64_t *inputs, uint32_t inputsCount, uint64_t *output, char **error)
int resetMachine()
int unpackInstruction(uint32_t instruction, char **output, char **error)
BinaryEncoding * generate()
std::string errorMessage() const
Definition Exception.cc:123
static NullOperationBehavior & instance()
static NullOperation & instance()
virtual void createState(OperationContext &context) const
virtual bool simulateTrigger(SimValue **io, OperationContext &context) const =0
virtual void deleteState(OperationContext &context) const
virtual OperationBehavior & behavior() const
Definition Operation.cc:388
virtual int numberOfInputs() const
Definition Operation.cc:192
virtual int numberOfOutputs() const
Definition Operation.cc:202
static int getFunc2Int(const int encoding)
static R4Instruction decodeR4Instruction(const uint32_t opcode)
static int getFunc7Int(const int encoding)
static void findCustomOps(std::map< std::string, int > &customOps_, BinaryEncoding *bem_)
static int getFunc3Int(const int encoding)
static Machine * loadFromADF(const std::string &adfFileName)
Definition Machine.cc:899
std::unique_ptr< OperationPool > pool
std::map< std::string, int > customOps
std::vector< SimValue > executeInstructionHelper(const char *opName, uint8_t width, const uint64_t *inputs, int inputsCount)