CIRCT 22.0.0git
Loading...
Searching...
No Matches
VCDTraceEncoder.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// TraceEncoder subclass converting and outputting a stream of raw trace buffers
10// to a VCD file.
11//
12//===----------------------------------------------------------------------===//
13
15
16#include <algorithm>
17#include <cassert>
18#include <cctype>
19#include <iostream>
20#include <ostream>
21#include <string>
22#include <string_view>
23
24using namespace circt::arc::runtime::impl;
25
26namespace {
27
28// Helper for referencing the name of a signal within the model's name array
29struct SignalNameRef {
30 SignalNameRef(uint64_t signalIndex, uint64_t nameOffset)
31 : signalIndex(signalIndex), nameOffset(nameOffset) {}
32
33 uint64_t signalIndex;
34 uint64_t nameOffset;
35
36 std::string_view getStringView(const char *nameBlob) const {
37 return std::string_view(nameBlob + nameOffset);
38 }
39};
40
41// Write the given signal and its value to the ASCII data buffer.
42// Note: This is the encoder's hot spot.
43static inline void dumpSignal(const VCDSignalTableEntry &signal, char *dest,
44 const uint8_t *data) {
45 const auto *sigStr = signal.id.cStr();
46 if (signal.numBits > 1)
47 *(dest++) = 'b';
48 for (unsigned n = signal.numBits; n > 0; --n)
49 *(dest++) = (data[(n - 1) / 8] & (1 << ((n - 1) % 8)) ? '1' : '0');
50 if (signal.numBits > 1)
51 *(dest++) = ' ';
52 for (unsigned n = 0; n < signal.id.getNumChars(); ++n)
53 *(dest++) = *(sigStr++);
54 *(dest) = '\n';
55}
56
57static inline void dumpSignalToString(const VCDSignalTableEntry &signal,
58 std::string &dest, const uint8_t *data) {
59 auto strOffset = dest.size();
60 dest.resize(strOffset + signal.getDumpSize());
61 dumpSignal(signal, &dest[strOffset], data);
62}
63
64static void writeVCDHeader(std::basic_ostream<char> &os) {
65 // TODO: Add the current date to the header. For now, keep the output
66 // stable to facilitate comparisons.
67 os << "$version\n Some cryptic ArcRuntime magic\n$end\n";
68 os << "$timescale 1ns $end\n";
69}
70
71} // namespace
72
74
75VCDSignalId::VCDSignalId(uint64_t index) {
76 raw.fill('\0');
77 unsigned pos = 0;
78 const char base = ('~' - '!') + 1;
79 do {
80 assert(pos < (sizeof(raw) - 1) && "Signal ID out of range");
81 raw[pos] = '!' + static_cast<char>(index % base);
82 index /= base;
83 ++pos;
84 } while (index != 0);
85 numChars = pos;
86}
87
89 ArcState *state,
90 const std::filesystem::path &outFilePath,
91 bool debug)
92 : TraceEncoder(modelInfo, state, numTraceBuffers, debug),
93 outFilePath(outFilePath) {}
94
96 const auto *info = modelInfo->traceInfo;
97 signalTable.reserve(info->numTraceTaps);
98 for (uint64_t i = 0; i < info->numTraceTaps; ++i)
99 signalTable.emplace_back(i, info->traceTaps[i].stateOffset,
100 info->traceTaps[i].typeBits);
101}
102
103static void appendLegalizedName(std::string &buffer,
104 const std::string_view &name) {
105 if (name.empty()) {
106 buffer.append("<EMPTY>");
107 return;
108 }
109 for (auto c : name) {
110 // TODO: Escape illegal characters
111 if (c == ' ')
112 buffer.push_back('_');
113 else if (std::isprint(c))
114 buffer.push_back(c);
115 }
116}
117
119 assert(!signalTable.empty());
120 const auto *info = modelInfo->traceInfo;
121 std::string vcdHdr;
122 const char *sigNameBlob = info->traceTapNames;
123 auto nameRefVector = std::vector<SignalNameRef>();
124 // Start of the name/alias
125 uint64_t sigOffset = 0;
126 // Start of the next signal's first name
127 uint64_t sigOffsetNext = info->traceTaps[0].nameOffset + 1;
128 uint64_t signalIndex = 0;
129 while (signalIndex < info->numTraceTaps) {
130 nameRefVector.emplace_back(signalIndex, sigOffset);
131 auto nameLen = nameRefVector.back().getStringView(sigNameBlob).length();
132 // Advance to the next name/alias
133 sigOffset += nameLen + 1;
134 if (sigOffset >= sigOffsetNext) {
135 // We've reached the next signal
136 assert(sigOffset == sigOffsetNext);
137 ++signalIndex;
138 // Bump to the new next signal
139 if (signalIndex < info->numTraceTaps)
140 sigOffsetNext = info->traceTaps[signalIndex].nameOffset + 1;
141 }
142 }
143
144 // Sort lexicographically
145 std::stable_sort(
146 nameRefVector.begin(), nameRefVector.end(),
147 [sigNameBlob](const SignalNameRef &a, const SignalNameRef &b) {
148 return a.getStringView(sigNameBlob) < b.getStringView(sigNameBlob);
149 });
150
151 std::vector<std::string_view> currentScope;
152 for (auto &name : nameRefVector) {
153 auto nameStr = name.getStringView(sigNameBlob);
154 std::vector<std::string_view> sigScope;
155 size_t charOffset = 0;
156 // Push the signal's scope names onto the stack
157 auto newOffset = std::string::npos;
158 while ((newOffset = nameStr.find('/', charOffset)) != std::string::npos) {
159 sigScope.push_back(nameStr.substr(charOffset, newOffset - charOffset));
160 charOffset = newOffset + 1;
161 }
162 // Count how many scopes match the current scope
163 unsigned commonScopes = 0;
164 for (size_t i = 0; i < std::min(currentScope.size(), sigScope.size());
165 ++i) {
166 if (sigScope[i] == currentScope[i])
167 ++commonScopes;
168 else
169 break;
170 }
171 // Pop scopes until we have reached the first common scope
172 while (commonScopes < currentScope.size()) {
173 currentScope.pop_back();
174 vcdHdr.append(currentScope.size() + 1, ' ');
175 vcdHdr.append("$upscope $end\n");
176 }
177 // Push the new scopes
178 while (sigScope.size() > currentScope.size()) {
179 vcdHdr.append(currentScope.size() + 1, ' ');
180 vcdHdr.append("$scope module ");
181 appendLegalizedName(vcdHdr, sigScope[currentScope.size()]);
182 vcdHdr.append(" $end\n");
183 currentScope.push_back(sigScope[currentScope.size()]);
184 }
185 // Write the signal declaration
186 vcdHdr.append(currentScope.size() + 1, ' ');
187 vcdHdr.append("$var wire ");
188 vcdHdr.append(std::to_string(signalTable[name.signalIndex].numBits));
189 vcdHdr.append(" ");
190 vcdHdr.append(signalTable[name.signalIndex].id.cStr());
191 vcdHdr.append(" ");
192 appendLegalizedName(vcdHdr, nameStr.substr(charOffset));
193 vcdHdr.append(" $end\n");
194
195 outFile << vcdHdr;
196 vcdHdr.clear();
197 }
198}
199
200static inline void writeTimestepToBuffer(int64_t currentStep,
201 std::vector<char> &buf) {
202 buf.push_back('#');
203 auto stepStr = std::to_string(currentStep);
204 std::copy(&stepStr.c_str()[0], &stepStr.c_str()[stepStr.size()],
205 std::back_inserter(buf));
206 buf.push_back('\n');
207}
208
210 // Use a large 32K IO buffer as we might be writing gigabytes of data
211 const size_t fileBufferCapacity = 32 * 1024;
212 fileBuffer = std::unique_ptr<char[]>(new char[fileBufferCapacity]);
213 outFile.rdbuf()->pubsetbuf(fileBuffer.get(), fileBufferCapacity);
214 outFile.open(outFilePath, std::ios::out | std::ios::trunc);
215 if (!outFile.is_open()) {
216 std::cerr << "[ArcRuntime] WARNING: Unable to open VCD trace file "
217 << outFilePath << " for writing. No trace will be produced."
218 << std::endl;
219 return false;
220 }
221 if (debug)
222 std::cout << "[ArcRuntime] Created VCD trace file: " << outFilePath
223 << std::endl;
224
225 // Create the signal table from the model's metadata
227
228 writeVCDHeader(outFile);
229 // Write out the signal hierarchy
230 outFile << "$scope module " << modelInfo->modelName << " $end\n";
232 outFile << "$upscope $end\n";
233 outFile << "$enddefinitions $end\n";
234 // Dump the entire initial state
235 outFile << "#0\n";
236 std::string outstring;
237 for (const auto &sig : signalTable) {
238 assert(sig.stateOffset < modelInfo->numStateBytes);
239 dumpSignalToString(sig, outstring, &state->modelState[sig.stateOffset]);
240 }
241 outFile << outstring;
242
243 outFile.flush();
244 return true;
245}
246
248
250 size_t offset = 0;
251 assert(workerStep <= work.firstStep);
252 if (workerStep != work.firstStep) {
253 // The new buffer starts at a different step than the previous one ended on
254 workerStep = work.firstStep;
256 }
257 // Walk through the the buffer
258 const uint64_t *basePtr = work.getData();
259 auto stepIter = work.stepMarkers.begin();
260 while (offset < work.size) {
261 // The trace tap ID and index in the signal table
262 auto idx = basePtr[offset];
263 ++offset;
264 // Pointer to the signal value
265 const void *voidPtr = static_cast<const void *>(basePtr + offset);
266 // Dump the signal and advance the offset
267 auto numChars = signalTable[idx].getDumpSize();
268 auto strOffset = workerOutBuffer.size();
269 workerOutBuffer.resize(strOffset + numChars);
270 dumpSignal(signalTable[idx], &workerOutBuffer[strOffset],
271 static_cast<const uint8_t *>(voidPtr));
272 offset += signalTable[idx].getStride();
273 assert(offset <= work.size);
274 // Check if we have reached a new time step marker
275 if (stepIter != work.stepMarkers.end() && stepIter->offset == offset) {
276 workerStep += stepIter->numSteps;
278 stepIter++;
279 }
280 }
281 assert(offset == work.size);
282 assert(stepIter == work.stepMarkers.end());
283 // Write out our buffer
284 outFile.write(workerOutBuffer.data(), workerOutBuffer.size());
285 workerOutBuffer.clear();
286}
287
289 assert(workerOutBuffer.empty());
290 // Terminate and flush the file
291 workerStep++;
293 outFile.write(workerOutBuffer.data(), workerOutBuffer.size());
294 outFile.flush();
295 workerOutBuffer.clear();
296}
297
299 if (outFile.is_open())
300 outFile.close();
301}
302
303} // namespace circt::arc::runtime::impl
assert(baseType &&"element must be base type")
Abstract TraceEncoder managing trace buffers and the encoder thread.
const ArcRuntimeModelInfo *const modelInfo
Metadata of the traced model.
void startUpWorker() override
Called by the worker thread before entering the encode loop.
std::ofstream outFile
Output file stream.
std::vector< char > workerOutBuffer
Concatenation buffer of the worker thread.
std::unique_ptr< char[]> fileBuffer
Internal buffer of the output stream.
void windDownWorker() override
Called by the worker thread after leaving the encode loop.
void createHierarchy()
Build and dump the signal hierarchy.
const std::filesystem::path outFilePath
Path to the output file.
void encode(TraceBuffer &work) override
Encode the given trace buffer. Called by the worker thread.
std::vector< VCDSignalTableEntry > signalTable
Table of signals: The index matches their Trace Tap ID.
bool initialize(const ArcState *state) override
Set-up the encoder before starting the worker thread.
int64_t workerStep
Current time step of the worker thread.
void initSignalTable()
Create the table of signals.
VCDTraceEncoder(const ArcRuntimeModelInfo *modelInfo, ArcState *state, const std::filesystem::path &outFilePath, bool debug)
void finalize(const ArcState *state) override
Finish trace encoding. Called by the simulation thread.
static void appendLegalizedName(std::string &buffer, const std::string_view &name)
static void writeTimestepToBuffer(int64_t currentStep, std::vector< char > &buf)
Definition debug.py:1
Static information for a compiled hardware model, generated by the MLIR lowering.
Definition Common.h:70
struct ArcModelTraceInfo * traceInfo
Signal tracing information. NULL iff the model is not trace instrumented.
Definition Common.h:78
uint64_t numStateBytes
Number of bytes required for the model's state.
Definition Common.h:74
const char * modelName
Name of the compiled model.
Definition Common.h:76
Combined runtime and model state for a hardware model instance.
Definition Common.h:44
uint8_t modelState[]
Definition Common.h:58
A heap allocated buffer containing raw trace data and time step markers.
int64_t firstStep
Time step of the buffer's first entry.
std::vector< TraceBufferMarker > stepMarkers
Time step markers.
uint32_t size
Number of valid elements, set on dispatch.
uint64_t * getData() const
Get the pointer to the buffer's storage.
const char * cStr() const
Get the ID as null terminated string.
unsigned getNumChars() const
Get the number of characters in the ID.
unsigned getDumpSize() const
Get the number of characters required to dump this signal's ID and value.
uint32_t numBits
Bit width of the signal.