CIRCT 23.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
346FailureOr<Value> Context::convertFormatString(
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")
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
const slang::ast::DefinitionSymbol * currentDefinition
The definition symbol of the module body currently being converted.
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
Value convertToSimpleBitVector(Value value)
Helper function to convert a value to its simple bit vector representation, if it has one.
MLIRContext * getContext()
Return the MLIR context.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.