CIRCT 22.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 if (specifierLower == 'm' || specifierLower == 'l')
108 return mlir::emitError(loc)
109 << "unsupported format specifier `" << fullSpecifier << "`";
110
111 // Consume the next argument, which will provide the value to be
112 // formatted.
113 assert(!arguments.empty() && "Slang guarantees correct arg count");
114 const auto &arg = *arguments[0];
115 arguments = arguments.drop_front();
116
117 // Handle the different formatting options.
118 // See IEEE 1800-2017 § 21.2.1.2 "Format specifications".
119 switch (specifierLower) {
120 case 'b':
121 return emitInteger(arg, options, IntFormat::Binary);
122 case 'o':
123 return emitInteger(arg, options, IntFormat::Octal);
124 case 'd':
125 return emitInteger(arg, options, IntFormat::Decimal);
126 case 'h':
127 case 'x':
128 return emitInteger(arg, options,
129 std::isupper(specifier) ? IntFormat::HexUpper
130 : IntFormat::HexLower);
131
132 case 'e':
133 case 'g':
134 case 'f':
135 return emitReal(arg, options, RealFormat::Float);
136
137 case 't':
138 return emitTime(arg, options);
139
140 case 's':
141 return emitString(arg, options);
142
143 default:
144 return mlir::emitError(loc)
145 << "unsupported format specifier `" << fullSpecifier << "`";
146 }
147 }
148
149 /// Emit an integer value with the given format.
150 LogicalResult emitInteger(const slang::ast::Expression &arg,
151 const FormatOptions &options, IntFormat format) {
152 auto value =
154 if (!value)
155 return failure();
156
157 // Determine the width to which the formatted integer should be padded.
158 unsigned width;
159 if (options.width) {
160 width = *options.width;
161 } else {
162 width = cast<moore::IntType>(value.getType()).getWidth();
163 if (format == IntFormat::Octal)
164 // 3 bits per octal digit
165 width = (width + 2) / 3;
166 else if (format == IntFormat::HexLower || format == IntFormat::HexUpper)
167 // 4 bits per hex digit
168 width = (width + 3) / 4;
169 else if (format == IntFormat::Decimal)
170 // ca. 3.322 bits per decimal digit (ln(10)/ln(2))
171 width = std::ceil(width * std::log(2) / std::log(10));
172 }
173
174 // Determine the alignment and padding.
175 auto alignment = options.leftJustify ? IntAlign::Left : IntAlign::Right;
176 auto padding =
177 format == IntFormat::Decimal ? IntPadding::Space : IntPadding::Zero;
178
179 fragments.push_back(moore::FormatIntOp::create(builder, loc, value, format,
180 width, alignment, padding));
181 return success();
182 }
183
184 LogicalResult emitReal(const slang::ast::Expression &arg,
185 const FormatOptions &options, RealFormat format) {
186
187 // Ensures that the given value is moore.real
188 // i.e. $display("%f", 4) -> 4.000000, but 4 is not necessarily of real type
189 auto value = context.convertRvalueExpression(
190 arg, moore::RealType::get(context.getContext(), moore::RealWidth::f64));
191
192 if (!value)
193 return failure();
194
195 // TODO add support for specifics such as width etc
196
197 fragments.push_back(
198 moore::FormatRealOp::create(builder, loc, value, format));
199
200 return success();
201 }
202
203 // Format an integer with the %t specifier according to IEEE 1800-2023
204 // § 20.4.3 "$timeformat"
205 LogicalResult emitTime(const slang::ast::Expression &arg,
206 const FormatOptions &options) {
207
208 // Only handle `TimeType` values.
209 auto value = context.convertRvalueExpression(
210 arg, moore::TimeType::get(context.getContext()));
211 if (!value)
212 return failure();
213
214 mlir::IntegerAttr width = nullptr;
215 if (options.width) {
216 mlir::Type i32Ty =
217 mlir::IntegerType::get(context.getContext(), /*width=*/32);
218 width = mlir::IntegerAttr::get(i32Ty, options.width.value());
219 }
220
221 // Delegate actual formatting to `moore.fmt.time`, annotate width if
222 // provided
223 if (width) {
224 fragments.push_back(
225 moore::FormatTimeOp::create(builder, loc, value, width));
226 } else {
227 fragments.push_back(moore::FormatTimeOp::create(builder, loc, value));
228 }
229
230 return success();
231 }
232
233 LogicalResult emitString(const slang::ast::Expression &arg,
234 const FormatOptions &options) {
235 if (options.width)
236 return mlir::emitError(loc)
237 << "string format specifier with width not supported";
238
239 // Simplified handling for literals.
240 if (auto *lit = arg.as_if<slang::ast::StringLiteral>()) {
241 emitLiteral(lit->getValue());
242 return success();
243 }
244
245 // Handle expressions
246 if (auto value = context.convertRvalueExpression(
247 arg, builder.getType<moore::FormatStringType>())) {
248 fragments.push_back(value);
249 return success();
250 }
251
252 return mlir::emitError(context.convertLocation(arg.sourceRange))
253 << "expression cannot be formatted as string";
254 }
255
256 /// Emit an expression argument with the appropriate default formatting.
257 LogicalResult emitDefault(const slang::ast::Expression &expr) {
258 FormatOptions options;
259 return emitInteger(expr, options, defaultFormat);
260 }
261};
262} // namespace
263
264FailureOr<Value> Context::convertFormatString(
265 std::span<const slang::ast::Expression *const> arguments, Location loc,
266 IntFormat defaultFormat, bool appendNewline) {
267 FormatStringParser parser(*this, ArrayRef(arguments.data(), arguments.size()),
268 loc, defaultFormat);
269 return parser.parse(appendNewline);
270}
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.
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.