CIRCT 24.0.0git
Loading...
Searching...
No Matches
FormatStrings.cpp
Go to the documentation of this file.
1//===- FormatStrings.cpp - Verilog format string conversion ---------------===//
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
10#include "slang/ast/SFormat.h"
11
12using namespace mlir;
13using namespace circt;
14using namespace ImportVerilog;
15using moore::IntAlign;
16using moore::IntFormat;
17using moore::IntPadding;
18using moore::RealFormat;
19using slang::ast::SFormat::FormatOptions;
20
21namespace {
22struct FormatStringParser {
23 Context &context;
24 OpBuilder &builder;
25 /// The remaining arguments to be parsed.
26 ArrayRef<const slang::ast::Expression *> arguments;
27 /// The current location to use for ops and diagnostics.
28 Location loc;
29 /// The default format for integer arguments not covered by a format string
30 /// literal.
31 IntFormat defaultFormat;
32 /// The interpolated string fragments that will be concatenated using a
33 /// `moore.fmt.concat` op.
34 SmallVector<Value> fragments;
35
36 FormatStringParser(Context &context,
37 ArrayRef<const slang::ast::Expression *> arguments,
38 Location loc, IntFormat defaultFormat)
39 : context(context), builder(context.builder), arguments(arguments),
40 loc(loc), defaultFormat(defaultFormat) {}
41
42 /// Entry point to the format string parser.
43 FailureOr<Value> parse(bool appendNewline) {
44 while (!arguments.empty()) {
45 const auto &arg = *arguments[0];
46 arguments = arguments.drop_front();
47 if (arg.kind == slang::ast::ExpressionKind::EmptyArgument)
48 continue;
49 loc = context.convertLocation(arg.sourceRange);
50 if (auto *lit = arg.as_if<slang::ast::StringLiteral>()) {
51 if (failed(parseFormat(lit->getValue())))
52 return failure();
53 } else {
54 if (failed(emitDefault(arg)))
55 return failure();
56 }
57 }
58
59 // Append the optional newline.
60 if (appendNewline)
61 emitLiteral("\n");
62
63 // Concatenate all string fragments into one formatted string, or return an
64 // empty literal if no fragments were generated.
65 if (fragments.empty())
66 return Value{};
67 if (fragments.size() == 1)
68 return fragments[0];
69 return moore::FormatConcatOp::create(builder, loc, fragments).getResult();
70 }
71
72 /// Parse a format string literal and consume and format the arguments
73 /// corresponding to the format specifiers it contains.
74 LogicalResult parseFormat(StringRef format) {
75 bool anyFailure = false;
76 auto onText = [&](auto text) {
77 if (anyFailure)
78 return;
79 emitLiteral(text);
80 };
81 auto onArg = [&](auto specifier, auto offset, auto len,
82 const auto &options) {
83 if (anyFailure)
84 return;
85 if (failed(emitArgument(specifier, format.substr(offset, len), options)))
86 anyFailure = true;
87 };
88 auto onError = [&](auto, auto, auto, auto) {
89 assert(false && "Slang should have already reported all errors");
90 };
91 slang::ast::SFormat::parse(format, onText, onArg, onError);
92 return failure(anyFailure);
93 }
94
95 /// Emit a string literal that requires no additional formatting.
96 void emitLiteral(StringRef literal) {
97 fragments.push_back(moore::FormatLiteralOp::create(builder, loc, literal));
98 }
99
100 /// Consume the next argument from the list and emit it according to the given
101 /// format specifier.
102 LogicalResult emitArgument(char specifier, StringRef fullSpecifier,
103 const FormatOptions &options) {
104 auto specifierLower = std::tolower(specifier);
105
106 // Special handling for format specifiers that consume no argument.
107 // %m/%M prints the hierarchical path of the module instance.
108 if (specifierLower == 'm') {
109 bool useEscapes = std::isupper(specifier);
110 fragments.push_back(
111 moore::FormatHierPathOp::create(builder, loc, useEscapes));
112 return success();
113 }
114
115 // %l prints the library and cell name of the scope
116 if (specifierLower == 'l') {
117 if (context.currentDefinition)
118 emitLiteral(context.currentDefinition->sourceLibrary.name + "." +
119 std::string(context.currentDefinition->name));
120 else
121 emitLiteral("");
122 return success();
123 }
124
125 // Consume the next argument, which will provide the value to be
126 // formatted.
127 assert(!arguments.empty() && "Slang guarantees correct arg count");
128 const auto &arg = *arguments[0];
129 arguments = arguments.drop_front();
130
131 // Handle the different formatting options.
132 // See IEEE 1800-2017 § 21.2.1.2 "Format specifications".
133 switch (specifierLower) {
134 case 'b':
135 return emitInteger(arg, options, IntFormat::Binary);
136 case 'o':
137 return emitInteger(arg, options, IntFormat::Octal);
138 case 'd':
139 return emitInteger(arg, options, IntFormat::Decimal);
140 case 'h':
141 case 'x':
142 return emitInteger(arg, options,
143 std::isupper(specifier) ? IntFormat::HexUpper
144 : IntFormat::HexLower);
145
146 case 'e':
147 return emitReal(arg, options, RealFormat::Exponential);
148 case 'g':
149 return emitReal(arg, options, RealFormat::General);
150 case 'f':
151 return emitReal(arg, options, RealFormat::Float);
152
153 case 't':
154 return emitTime(arg, options);
155
156 case 's':
157 return emitString(arg, options);
158 case 'c':
159 return emitChar(arg, options);
160
161 default:
162 return mlir::emitError(loc)
163 << "unsupported format specifier `" << fullSpecifier << "`";
164 }
165 }
166
167 /// Emit an integer value with the given format.
168 LogicalResult emitInteger(const slang::ast::Expression &arg,
169 const FormatOptions &options, IntFormat format) {
170
171 Type intTy = {};
172 Value val;
173 auto rVal = context.convertRvalueExpression(arg);
174 // To infer whether or not the value is signed while printing as a decimal
175 // Since it only matters if it's a decimal, we add `format ==
176 // IntFormat::Decimal`
177 bool isSigned = arg.type->isSigned() && format == IntFormat::Decimal;
178 if (!rVal)
179 return failure();
180
181 // An IEEE 754 float number is represented using a sign bit s, n mantissa,
182 // and m exponent bits, representing (-1)**s * 1.fraction * 2**(E-bias).
183 // This means that the largest finite value is (2-2**(-n) * 2**(2**m-1)),
184 // just slightly less than ((2**(2**(m)))-1).
185 // Since we need signed value representation, we need integers that can
186 // represent values between [-(2**(2**(m))) ... (2**(2**(m)))-1], which
187 // requires an m+1 bit signed integer.
188 if (auto realTy = dyn_cast<moore::RealType>(rVal.getType())) {
189 if (realTy.getWidth() == moore::RealWidth::f32) {
190 // A 32 Bit IEEE 754 float number needs at most 129 integer bits
191 // (signed).
192 intTy = moore::IntType::getInt(context.getContext(), 129);
193 } else if (realTy.getWidth() == moore::RealWidth::f64) {
194 // A 64 Bit IEEE 754 float number needs at most 1025 integer bits
195 // (signed).
196 intTy = moore::IntType::getInt(context.getContext(), 1025);
197 } else
198 return failure();
199
200 val = moore::RealToIntOp::create(builder, loc, intTy, rVal);
201 } else {
202 val = rVal;
203 }
204
205 auto value = context.convertToSimpleBitVector(val);
206 if (!value)
207 return failure();
208
209 // Determine the alignment and padding.
210 auto alignment = options.leftJustify ? IntAlign::Left : IntAlign::Right;
211 auto padding =
212 format == IntFormat::Decimal ? IntPadding::Space : IntPadding::Zero;
213 IntegerAttr widthAttr = nullptr;
214 if (options.width) {
215 widthAttr = builder.getI32IntegerAttr(*options.width);
216 }
217
218 fragments.push_back(moore::FormatIntOp::create(
219 builder, loc, value, format, alignment, padding, widthAttr, isSigned));
220 return success();
221 }
222
223 LogicalResult emitReal(const slang::ast::Expression &arg,
224 const FormatOptions &options, RealFormat format) {
225
226 // Ensures that the given value is moore.real
227 // i.e. $display("%f", 4) -> 4.000000, but 4 is not necessarily of real type
228 auto value = context.convertRvalueExpression(
229 arg, moore::RealType::get(context.getContext(), moore::RealWidth::f64));
230
231 IntegerAttr widthAttr = nullptr;
232 if (options.width) {
233 widthAttr = builder.getI32IntegerAttr(*options.width);
234 }
235
236 IntegerAttr precisionAttr = nullptr;
237 if (options.precision) {
238 if (*options.precision)
239 precisionAttr = builder.getI32IntegerAttr(*options.precision);
240 else
241 // If precision is 0, we set it to 1 instead
242 precisionAttr = builder.getI32IntegerAttr(1);
243 }
244
245 auto alignment = options.leftJustify ? IntAlign::Left : IntAlign::Right;
246 if (!value)
247 return failure();
248
249 fragments.push_back(moore::FormatRealOp::create(
250 builder, loc, value, format, alignment, widthAttr, precisionAttr));
251
252 return success();
253 }
254
255 // Format an integer with the %t specifier according to IEEE 1800-2023
256 // § 20.4.3 "$timeformat". We currently don't support user-defined time
257 // formats. Instead, we just convert the time to an integer and print it. This
258 // applies the local timeunit/timescale and seem to be inline with what
259 // Verilator does.
260 LogicalResult emitTime(const slang::ast::Expression &arg,
261 const FormatOptions &options) {
262 // Handle the time argument and convert it to a 64 bit integer.
263 auto value = context.convertRvalueExpression(
264 arg, moore::IntType::getInt(context.getContext(), 64));
265 if (!value)
266 return failure();
267
268 // Create an integer formatting fragment.
269 uint32_t width = 20; // default $timeformat field width
270 if (options.width)
271 width = *options.width;
272 auto alignment = options.leftJustify ? IntAlign::Left : IntAlign::Right;
273 auto padding = options.zeroPad ? IntPadding::Zero : IntPadding::Space;
274 fragments.push_back(moore::FormatIntOp::create(
275 builder, loc, value, IntFormat::Decimal, alignment, padding,
276 builder.getI32IntegerAttr(width)));
277 return success();
278 }
279
280 LogicalResult emitString(const slang::ast::Expression &arg,
281 const FormatOptions &options) {
282 // A field width (e.g. `%20s` / `%-20s`) prints the string in a field of at
283 // least that many characters, right- or left-justified and space-padded
284 // (IEEE 1800-2017 § 21.2.1.2). `moore.fmt.string` carries these attributes.
285 if (options.width) {
286 auto value = context.convertRvalueExpression(
287 arg, moore::StringType::get(context.getContext()));
288 if (!value)
289 return failure();
290 auto alignment = options.leftJustify ? IntAlign::Left : IntAlign::Right;
291 auto padding = options.zeroPad ? IntPadding::Zero : IntPadding::Space;
292 fragments.push_back(moore::FormatStringOp::create(
293 builder, loc, value, builder.getI32IntegerAttr(*options.width),
294 moore::IntAlignAttr::get(context.getContext(), alignment),
295 moore::IntPaddingAttr::get(context.getContext(), padding)));
296 return success();
297 }
298
299 // Simplified handling for literals.
300 if (auto *lit = arg.as_if<slang::ast::StringLiteral>()) {
301 emitLiteral(lit->getValue());
302 return success();
303 }
304
305 // Handle expressions
306 if (auto value = context.convertRvalueExpression(
307 arg, builder.getType<moore::FormatStringType>())) {
308 fragments.push_back(value);
309 return success();
310 }
311
312 return mlir::emitError(context.convertLocation(arg.sourceRange))
313 << "expression cannot be formatted as string";
314 }
315
316 LogicalResult emitChar(const slang::ast::Expression &arg,
317 const FormatOptions &options) {
318 if (options.width)
319 return mlir::emitError(loc)
320 << "character format specifier with width not supported";
321
322 auto value = context.convertRvalueExpression(arg);
323 if (!value)
324 return failure();
325
326 auto bitValue = context.convertToSimpleBitVector(value);
327 if (!bitValue)
328 return failure();
329
330 fragments.push_back(moore::FormatCharOp::create(builder, loc, bitValue));
331 return success();
332 }
333
334 /// Emit an expression argument with the appropriate default formatting.
335 LogicalResult emitDefault(const slang::ast::Expression &expr) {
336 FormatOptions options;
337 // Without an explicit format string, default formatting is not limited to
338 // integers, the string-typed arguments also be concerned.
339 if (expr.type->isString())
340 return emitString(expr, options);
341 return emitInteger(expr, options, defaultFormat);
342 }
343};
344} // namespace
345
347 std::span<const slang::ast::Expression *const> arguments, Location loc,
348 IntFormat defaultFormat, bool appendNewline) {
349 FormatStringParser parser(*this, ArrayRef(arguments.data(), arguments.size()),
350 loc, defaultFormat);
351 return parser.parse(appendNewline);
352}
assert(baseType &&"element must be base type")
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
FailureOr< Value > convertFormatString(std::span< const slang::ast::Expression *const > arguments, Location loc, moore::IntFormat defaultFormat=moore::IntFormat::Decimal, bool appendNewline=false)
Convert a list of string literal arguments with formatting specifiers and arguments to be interpolate...