Skip to content

Commit c328000

Browse files
[MooreToCore][Sim] Added support for moore.fmt.real (#9397)
Since LLVM doesn't have a very clean separation between printing out floats in FP vs Scientific Notation (see `toStringImpl` [here] (https://llvm.org/doxygen/APFloat_8cpp_source.html)), I decided to do what Verilator does here: call [`snprintf`](https://github.com/sifive/verilator/blob/master/include/verilated.cpp#L837). So for now, we're doing C-style prints - I plan to make a patch to LLVM to disambiguate `toStringImpl` for FP notation, but that's for later. Overview: 1. By default, the precision for floating point numbers is 6 for `%f` and `%e` 2. `$fieldWidth` is used to determine padding, and `$fracDigits` is used to denote the number of digits after the decimal point/the alphabet 'e' - the actual formatting is handled by `snprintf` under the hood 3. `%g` has a unique formatting logic: For `exp ≥ 0`: Uses decimal notation if `exp + 1 ≤ max(1, fracDigits)`, otherwise switches to scientific notation with `max(0, fracDigits - 1)` fractional digits. For `exp < 0`: If `exp ≥ -4`: Uses decimal notation with `max(b - 1, 0) + |exp|` fractional digits If `exp < -4`: Uses scientific notation with `max(b - 1, 0)` fractional digits Everything is stripped of trailing zeroes after the decimal point/the alphabet 'e', which, as expected, is in conjunction with [this](https://en.cppreference.com/w/cpp/io/c/printf.html#Notes) I've introduced this patch to fix the current `fmt.real` legalization errors and improve test coverage. I think it is beneficial to merge this even if we remove the formatting folders later, since this might serve as reference code for the arcilator runtime (#7692).
1 parent 0e71521 commit c328000

File tree

12 files changed

+349
-16
lines changed

12 files changed

+349
-16
lines changed

include/circt/Dialect/Moore/MooreOps.td

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,13 +2030,19 @@ def FormatRealOp : MooreOp<"fmt.real", [Pure]> {
20302030
See IEEE 1800-2017 § 21.2.1.2 "Format specifications".
20312031
}];
20322032
let arguments = (ins
2033-
RealType:$value,
2034-
RealFormatAttr:$format
2033+
RealType:$value,
2034+
RealFormatAttr:$format,
2035+
IntAlignAttr:$alignment,
2036+
OptionalAttr<I32Attr>:$fieldWidth,
2037+
OptionalAttr<I32Attr>:$fracDigits
20352038
);
20362039
let results = (outs FormatStringType:$result);
20372040
let assemblyFormat = [{
2038-
$format $value `,`
2039-
attr-dict `:` type($value)
2041+
$format $value `,`
2042+
`align` $alignment
2043+
(`fieldWidth` $fieldWidth^)?
2044+
(`fracDigits` $fracDigits^)?
2045+
attr-dict `:` type($value)
20402046
}];
20412047
}
20422048

include/circt/Dialect/Sim/SimOps.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ static inline mlir::Value getFormattedValue(mlir::Operation *fmtOp) {
4545
return fmt.getValue();
4646
if (auto fmt = llvm::dyn_cast_or_null<circt::sim::FormatCharOp>(fmtOp))
4747
return fmt.getValue();
48+
if (auto fmt = llvm::dyn_cast_or_null<circt::sim::FormatGeneralOp>(fmtOp))
49+
return fmt.getValue();
50+
if (auto fmt = llvm::dyn_cast_or_null<circt::sim::FormatFloatOp>(fmtOp))
51+
return fmt.getValue();
52+
if (auto fmt = llvm::dyn_cast_or_null<circt::sim::FormatScientificOp>(fmtOp))
53+
return fmt.getValue();
4854
return {};
4955
}
5056

include/circt/Dialect/Sim/SimOps.td

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,86 @@ def FormatBinOp : SimOp<"fmt.bin", [Pure]> {
257257
}];
258258
}
259259

260+
def FormatScientificOp : SimOp<"fmt.exp", [Pure]> {
261+
let summary = "Real format specifier";
262+
let description = [{
263+
Format the given real value in scientific (exponential) notation
264+
}];
265+
266+
let arguments = (ins AnyFloat:$value,
267+
DefaultValuedAttr<BoolAttr,"false">:$isLeftAligned,
268+
OptionalAttr<I32Attr>:$fieldWidth,
269+
DefaultValuedAttr<I32Attr,"6">:$fracDigits
270+
);
271+
let results = (outs FormatStringType:$result);
272+
273+
let hasFolder = true;
274+
275+
let assemblyFormat = [{
276+
$value
277+
(`isLeftAligned` $isLeftAligned^)?
278+
(`fieldWidth` $fieldWidth^)?
279+
(`fracDigits` $fracDigits^)?
280+
attr-dict `:` qualified(type($value))
281+
}];
282+
}
283+
284+
def FormatFloatOp : SimOp<"fmt.flt", [Pure]> {
285+
let summary = "Real format specifier";
286+
let description = [{
287+
Format the given real value in floating point (decimal) notation
288+
}];
289+
290+
let arguments = (ins AnyFloat:$value,
291+
DefaultValuedAttr<BoolAttr,"false">:$isLeftAligned,
292+
OptionalAttr<I32Attr>:$fieldWidth,
293+
DefaultValuedAttr<I32Attr,"6">:$fracDigits
294+
);
295+
let results = (outs FormatStringType:$result);
296+
297+
let hasFolder = true;
298+
299+
let assemblyFormat = [{
300+
$value
301+
(`isLeftAligned` $isLeftAligned^)?
302+
(`fieldWidth` $fieldWidth^)?
303+
(`fracDigits` $fracDigits^)?
304+
attr-dict `:` qualified(type($value))
305+
}];
306+
}
307+
308+
def FormatGeneralOp : SimOp<"fmt.gen", [Pure]> {
309+
let summary = "Real format specifier";
310+
let description = [{
311+
Format floating-point numbers using either decimal or scientific notation
312+
based on the exponent and precision parameter fracDigits (default: 6)
313+
314+
For exp ≥ 0:
315+
Uses decimal notation if exp + 1 ≤ max(1, fracDigits), otherwise switches
316+
to scientific notation with max(0, fracDigits - 1) fractional digits
317+
318+
For exp < 0:
319+
If exp ≥ -4: Uses decimal notation with max(b - 1, 0) + |exp| fractional digits
320+
If exp < -4: Uses scientific notation with max(b - 1, 0) fractional digits
321+
}];
322+
323+
let arguments = (ins AnyFloat:$value,
324+
DefaultValuedAttr<BoolAttr,"false">:$isLeftAligned,
325+
OptionalAttr<I32Attr>:$fieldWidth,
326+
DefaultValuedAttr<I32Attr,"6">:$fracDigits
327+
);
328+
let results = (outs FormatStringType:$result);
329+
330+
let hasFolder = true;
331+
332+
let assemblyFormat = [{
333+
$value
334+
(`isLeftAligned` $isLeftAligned^)?
335+
(`fieldWidth` $fieldWidth^)?
336+
(`fracDigits` $fracDigits^)?
337+
attr-dict `:` qualified(type($value))
338+
}];
339+
}
260340

261341
def FormatDecOp : SimOp<"fmt.dec", [Pure]> {
262342
let summary = "Decimal format specifier";

lib/Conversion/ImportVerilog/FormatStrings.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ struct FormatStringParser {
130130
: IntFormat::HexLower);
131131

132132
case 'e':
133+
return emitReal(arg, options, RealFormat::Exponential);
133134
case 'g':
135+
return emitReal(arg, options, RealFormat::General);
134136
case 'f':
135137
return emitReal(arg, options, RealFormat::Float);
136138

@@ -210,13 +212,26 @@ struct FormatStringParser {
210212
auto value = context.convertRvalueExpression(
211213
arg, moore::RealType::get(context.getContext(), moore::RealWidth::f64));
212214

215+
IntegerAttr widthAttr = nullptr;
216+
if (options.width) {
217+
widthAttr = builder.getI32IntegerAttr(*options.width);
218+
}
219+
220+
IntegerAttr precisionAttr = nullptr;
221+
if (options.precision) {
222+
if (*options.precision)
223+
precisionAttr = builder.getI32IntegerAttr(*options.precision);
224+
else
225+
// If precision is 0, we set it to 1 instead
226+
precisionAttr = builder.getI32IntegerAttr(1);
227+
}
228+
229+
auto alignment = options.leftJustify ? IntAlign::Left : IntAlign::Right;
213230
if (!value)
214231
return failure();
215232

216-
// TODO add support for specifics such as width etc
217-
218-
fragments.push_back(
219-
moore::FormatRealOp::create(builder, loc, value, format));
233+
fragments.push_back(moore::FormatRealOp::create(
234+
builder, loc, value, format, alignment, widthAttr, precisionAttr));
220235

221236
return success();
222237
}

lib/Conversion/MooreToCore/MooreToCore.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2046,6 +2046,40 @@ struct FormatIntOpConversion : public OpConversionPattern<FormatIntOp> {
20462046
}
20472047
};
20482048

2049+
struct FormatRealOpConversion : public OpConversionPattern<FormatRealOp> {
2050+
using OpConversionPattern::OpConversionPattern;
2051+
2052+
LogicalResult
2053+
matchAndRewrite(FormatRealOp op, OpAdaptor adaptor,
2054+
ConversionPatternRewriter &rewriter) const override {
2055+
auto fracDigitsAttr = adaptor.getFracDigitsAttr();
2056+
2057+
auto fieldWidthAttr = adaptor.getFieldWidthAttr();
2058+
bool isLeftAligned = adaptor.getAlignment() == IntAlign::Left;
2059+
mlir::BoolAttr isLeftAlignedAttr = rewriter.getBoolAttr(isLeftAligned);
2060+
2061+
switch (op.getFormat()) {
2062+
case RealFormat::General:
2063+
rewriter.replaceOpWithNewOp<sim::FormatGeneralOp>(
2064+
op, adaptor.getValue(), isLeftAlignedAttr, fieldWidthAttr,
2065+
fracDigitsAttr);
2066+
return success();
2067+
case RealFormat::Float:
2068+
rewriter.replaceOpWithNewOp<sim::FormatFloatOp>(
2069+
op, adaptor.getValue(), isLeftAlignedAttr, fieldWidthAttr,
2070+
fracDigitsAttr);
2071+
return success();
2072+
case RealFormat::Exponential:
2073+
rewriter.replaceOpWithNewOp<sim::FormatScientificOp>(
2074+
op, adaptor.getValue(), isLeftAlignedAttr, fieldWidthAttr,
2075+
fracDigitsAttr);
2076+
return success();
2077+
default:
2078+
return rewriter.notifyMatchFailure(op, "unsupported real format");
2079+
}
2080+
}
2081+
};
2082+
20492083
struct DisplayBIOpConversion : public OpConversionPattern<DisplayBIOp> {
20502084
using OpConversionPattern::OpConversionPattern;
20512085

@@ -2470,6 +2504,7 @@ static void populateOpConversion(ConversionPatternSet &patterns,
24702504
FormatLiteralOpConversion,
24712505
FormatConcatOpConversion,
24722506
FormatIntOpConversion,
2507+
FormatRealOpConversion,
24732508
DisplayBIOpConversion
24742509
>(typeConverter, patterns.getContext());
24752510
// clang-format on

lib/Dialect/Sim/SimOps.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,32 @@ static StringAttr formatIntegersByRadix(MLIRContext *ctx, unsigned radix,
7474
return {};
7575
}
7676

77+
static StringAttr formatFloatsBySpecifier(MLIRContext *ctx, Attribute value,
78+
bool isLeftAligned,
79+
std::optional<unsigned> fieldWidth,
80+
std::optional<unsigned> fracDigits,
81+
std::string formatSpecifier) {
82+
if (auto floatAttr = llvm::dyn_cast_or_null<FloatAttr>(value)) {
83+
std::string widthString = isLeftAligned ? "-" : "";
84+
if (fieldWidth.has_value()) {
85+
widthString += std::to_string(fieldWidth.value());
86+
}
87+
std::string fmtSpecifier = "%" + widthString + "." +
88+
std::to_string(fracDigits.value()) +
89+
formatSpecifier;
90+
91+
// Calculates number of bytes needed to store the format string
92+
// excluding the null terminator
93+
int bufferSize = std::snprintf(nullptr, 0, fmtSpecifier.c_str(),
94+
floatAttr.getValue().convertToDouble());
95+
std::string floatFmtBuffer(bufferSize, '\0');
96+
snprintf(floatFmtBuffer.data(), bufferSize + 1, fmtSpecifier.c_str(),
97+
floatAttr.getValue().convertToDouble());
98+
return StringAttr::get(ctx, floatFmtBuffer);
99+
}
100+
return {};
101+
}
102+
77103
ParseResult DPIFuncOp::parse(OpAsmParser &parser, OperationState &result) {
78104
auto builder = parser.getBuilder();
79105
// Parse visibility.
@@ -219,6 +245,24 @@ OpFoldResult FormatBinOp::fold(FoldAdaptor adaptor) {
219245
adaptor.getPaddingChar(), adaptor.getSpecifierWidth());
220246
}
221247

248+
OpFoldResult FormatScientificOp::fold(FoldAdaptor adaptor) {
249+
return formatFloatsBySpecifier(
250+
getContext(), adaptor.getValue(), adaptor.getIsLeftAligned(),
251+
adaptor.getFieldWidth(), adaptor.getFracDigits(), "e");
252+
}
253+
254+
OpFoldResult FormatFloatOp::fold(FoldAdaptor adaptor) {
255+
return formatFloatsBySpecifier(
256+
getContext(), adaptor.getValue(), adaptor.getIsLeftAligned(),
257+
adaptor.getFieldWidth(), adaptor.getFracDigits(), "f");
258+
}
259+
260+
OpFoldResult FormatGeneralOp::fold(FoldAdaptor adaptor) {
261+
return formatFloatsBySpecifier(
262+
getContext(), adaptor.getValue(), adaptor.getIsLeftAligned(),
263+
adaptor.getFieldWidth(), adaptor.getFracDigits(), "g");
264+
}
265+
222266
OpFoldResult FormatCharOp::fold(FoldAdaptor adaptor) {
223267
auto width = getValue().getType().getIntOrFloatBitWidth();
224268
if (width > 8)

test/Conversion/ImportVerilog/basic.sv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,7 +1647,7 @@ module realToTimeConversion;
16471647
// CHECK: [[REAL:%.+]] = moore.uint_to_real [[INT]] : i64 -> f64
16481648
// CHECK: [[SCALE:%.+]] = moore.constant_real 1.000000e+05 : f64
16491649
// CHECK: [[DIV:%.+]] = moore.fdiv [[REAL]], [[SCALE]] : f64
1650-
// CHECK: [[FMT:%.+]] = moore.fmt.real float [[DIV]], : f64
1650+
// CHECK: [[FMT:%.+]] = moore.fmt.real float [[DIV]], align right fracDigits 3 : f64
16511651
initial
16521652
$display("%0.3f", $time);
16531653
endmodule
@@ -1663,7 +1663,7 @@ module timeToRealConversion;
16631663
// CHECK: [[UINT_TO_REAL:%.+]] = moore.uint_to_real [[LOGIC_TO_INT]] : i64 -> f64
16641664
// CHECK: [[SCALE:%.+]] = moore.constant_real 1.000000e+05 : f64
16651665
// CHECK: [[DIV:%.+]] = moore.fdiv [[UINT_TO_REAL]], [[SCALE]] : f64
1666-
// CHECK: [[FMT:%.+]] = moore.fmt.real float [[DIV]], : f64
1666+
// CHECK: [[FMT:%.+]] = moore.fmt.real float [[DIV]], align right : f64
16671667
initial begin
16681668
$display("%f", x);
16691669
end

test/Conversion/ImportVerilog/builtins.sv

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,25 @@ function void DisplayAndSeverityBuiltins(int x, real r);
9292
// CHECK: moore.fmt.int octal [[X]], align left, pad zero width 19 : i32
9393
$write("%-19o", x);
9494

95-
// CHECK: moore.fmt.real float [[R]]
95+
// CHECK: moore.fmt.real float [[R]], align right : f64
9696
$write("%f", r);
97+
// CHECK: moore.fmt.real exponential [[R]], align right : f64
98+
$write("%e", r);
99+
// CHECK: moore.fmt.real general [[R]], align right : f64
100+
$write("%g", r);
101+
// CHECK: moore.fmt.real float [[R]], align left fracDigits 1 : f64
102+
$write("%-.f", r);
103+
// CHECK: moore.fmt.real exponential [[R]], align right fieldWidth 10 : f64
104+
$write("%10e", r);
105+
// CHECK: moore.fmt.real general [[R]], align right fracDigits 5 : f64
106+
$write("%0.5g", r);
107+
// CHECK: moore.fmt.real float [[R]], align left fieldWidth 20 fracDigits 5 : f64
108+
$write("%-20.5f", r);
109+
// CHECK: moore.fmt.real exponential [[R]], align right fieldWidth 10 fracDigits 1 : f64
110+
$write("%10.e", r);
111+
// CHECK: moore.fmt.real general [[R]], align right fieldWidth 9 fracDigits 8 : f64
112+
$write("%9.8g", r);
113+
97114
// CHECK: [[XR:%.+]] = moore.sint_to_real [[X]] : i32 -> f64
98115
// CHECK: [[TMP:%.+]] = moore.fmt.real float [[XR]]
99116
// CHECK: moore.builtin.display [[TMP]]

test/Conversion/MooreToCore/basic.mlir

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ func.func @Statements(%arg0: !moore.i42) {
386386
}
387387

388388
// CHECK-LABEL: func @FormatStrings
389-
func.func @FormatStrings(%arg0: !moore.i42) {
389+
func.func @FormatStrings(%arg0: !moore.i42, %arg1: !moore.f32, %arg2: !moore.f64) {
390390
// CHECK: [[TMP:%.+]] = sim.fmt.literal "hello"
391391
%0 = moore.fmt.literal "hello"
392392
// CHECK: sim.fmt.concat ([[TMP]], [[TMP]])
@@ -411,6 +411,21 @@ func.func @FormatStrings(%arg0: !moore.i42) {
411411
moore.fmt.int hex_lower %arg0, align right, pad zero : i42
412412
// CHECK: sim.fmt.hex %arg0, isUpper true paddingChar 32 specifierWidth 42 : i42
413413
moore.fmt.int hex_upper %arg0, align right, pad space width 42 : i42
414+
415+
// CHECK: sim.fmt.flt %arg1 isLeftAligned true : f32
416+
moore.fmt.real float %arg1, align left : f32
417+
// CHECK: sim.fmt.exp %arg2 isLeftAligned true : f64
418+
moore.fmt.real exponential %arg2, align left : f64
419+
// CHECK: sim.fmt.gen %arg1 isLeftAligned true : f32
420+
moore.fmt.real general %arg1, align left fracDigits 6 : f32
421+
// CHECK: sim.fmt.flt %arg2 isLeftAligned true fracDigits 10 : f64
422+
moore.fmt.real float %arg2, align left fracDigits 10 : f64
423+
// CHECK: sim.fmt.exp %arg1 fieldWidth 9 fracDigits 8 : f32
424+
moore.fmt.real exponential %arg1, align right fieldWidth 9 fracDigits 8 : f32
425+
// CHECK: sim.fmt.gen %arg2 : f64
426+
moore.fmt.real general %arg2, align right : f64
427+
// CHECK: sim.fmt.flt %arg1 fieldWidth 15 : f32
428+
moore.fmt.real float %arg1, align right fieldWidth 15 : f32
414429
// CHECK: sim.proc.print [[TMP]]
415430
moore.builtin.display %0
416431
return

test/Dialect/Moore/basic.mlir

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ func.func @WaitDelay(%arg0: !moore.time) {
394394

395395
// CHECK-LABEL: func.func @FormatStrings
396396
// CHECK-SAME: %arg0: !moore.format_string
397-
func.func @FormatStrings(%arg0: !moore.format_string, %arg1: !moore.i42) {
397+
func.func @FormatStrings(%arg0: !moore.format_string, %arg1: !moore.i42, %arg2: !moore.f32, %arg3: !moore.f64) {
398398
// CHECK: moore.fmt.literal "hello"
399399
moore.fmt.literal "hello"
400400
// CHECK: moore.fmt.concat ()
@@ -421,6 +421,17 @@ func.func @FormatStrings(%arg0: !moore.format_string, %arg1: !moore.i42) {
421421
moore.fmt.int hex_lower %arg1, align left, pad zero width 42 : i42
422422
// CHECK: moore.fmt.int hex_upper %arg1, align left, pad zero width 42 : i42
423423
moore.fmt.int hex_upper %arg1, align left, pad zero width 42 : i42
424+
425+
// CHECK: moore.fmt.real float %arg2, align left : f32
426+
moore.fmt.real float %arg2, align left : f32
427+
// CHECK: moore.fmt.real exponential %arg3, align left : f64
428+
moore.fmt.real exponential %arg3, align left : f64
429+
// CHECK: moore.fmt.real general %arg3, align right fieldWidth 9 fracDigits 8 : f64
430+
moore.fmt.real general %arg3, align right fieldWidth 9 fracDigits 8 : f64
431+
// CHECK: moore.fmt.real float %arg2, align right fieldWidth 12 : f32
432+
moore.fmt.real float %arg2, align right fieldWidth 12 : f32
433+
// CHECK: moore.fmt.real exponential %arg3, align right fracDigits 5 : f64
434+
moore.fmt.real exponential %arg3, align right fracDigits 5 : f64
424435
return
425436
}
426437

0 commit comments

Comments
 (0)