Skip to content

Conversation

@KavyaChopra04
Copy link
Contributor

The standard wasn't super clear about the semantics, so here's what I figured out after twiddling with some test cases using Icarus:

  1. For Octal, Hexadecimal, and Binary numbers, they are padded with 0s upto the minimum width that a number of that datatype would fit into (eg i22 would fit into (22 + 2)/3 = 8 octal bytes, so 106 in octal would be printed as 00000152), and then any additional bytes are padded with spaces (so $write("%10o",val) would be 00000152). Even contraction would have the whole 0-padded value be printed ($write("%3o",val) would still be 00000152). A similar logic follows for hexadecimal and binary
  2. For decimal numbers, the default padding is spaces themselves, but they can contract too. $write("%4d",val) (where val is an i22 with value 106) would cause 106 to be printed (an extra space to fill up the padding of 4). Expansion is pretty straightforward, and similar to Octals. Signed numbers can also require an extra character to be printed sometimes.

This PR implements the above, and adds some test cases to substantiate them. Happy to add more tests if needed as well

Copy link
Contributor

@fabianschuiki fabianschuiki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for reworking this and bringing it up to par with the SV spec 🥳👍. Just a few minor nits and potential to reduce code duplication a bit.

Comment on lines 195 to 198
mlir::IntegerAttr widthAttr = nullptr;
if (options.width) {
widthAttr = builder.getI32IntegerAttr(*options.width);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
mlir::IntegerAttr widthAttr = nullptr;
if (options.width) {
widthAttr = builder.getI32IntegerAttr(*options.width);
}
IntegerAttr widthAttr = nullptr;
if (options.width)
widthAttr = builder.getI32IntegerAttr(*options.width);

// TODO: These should honor the width, alignment, and padding.

char padChar = adaptor.getPadding() == IntPadding::Space ? 32 : 48;
mlir::IntegerAttr padCharAttr = rewriter.getI8IntegerAttr(padChar);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mlir::IntegerAttr padCharAttr = rewriter.getI8IntegerAttr(padChar);
IntegerAttr padCharAttr = rewriter.getI8IntegerAttr(padChar);

auto widthAttr = adaptor.getSpecifierWidthAttr();

bool isLeftAligned = adaptor.getAlignment() == IntAlign::Left;
mlir::BoolAttr isLeftAlignedAttr = rewriter.getBoolAttr(isLeftAligned);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mlir::BoolAttr isLeftAlignedAttr = rewriter.getBoolAttr(isLeftAligned);
BoolAttr isLeftAlignedAttr = rewriter.getBoolAttr(isLeftAligned);

Comment on lines 148 to 177
SmallVector<char, 8> strBuf;
intAttr.getValue().toString(strBuf, 16U, /*Signed*/ false,
/*formatAsCLiteral*/ false,
/*UpperCase*/ false);
/*UpperCase*/ adaptor.getIsHexUppercase());

unsigned width = intAttr.getType().getIntOrFloatBitWidth();
unsigned padWidth = width / 4;
if (width % 4 != 0)
padWidth++;

unsigned numSpaces;
if (adaptor.getSpecifierWidth().has_value()) {
numSpaces = std::max(
0U, adaptor.getSpecifierWidth().value() -
std::max(padWidth, static_cast<unsigned>(strBuf.size())));
} else {
numSpaces = 0;
}

SmallVector<char, 1> spacePadding(numSpaces, ' ');
padWidth = padWidth > strBuf.size() ? padWidth - strBuf.size() : 0;

SmallVector<char, 8> padding(padWidth, '0');
return StringAttr::get(getContext(), Twine(padding) + Twine(strBuf));
SmallVector<char, 8> padding(padWidth, adaptor.getPaddingChar());
if (adaptor.getIsLeftAligned()) {
return StringAttr::get(getContext(), Twine(padding) + Twine(strBuf) +
Twine(spacePadding));
}
return StringAttr::get(getContext(), Twine(spacePadding) + Twine(padding) +
Twine(strBuf));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance you could factor this code out into a helper function that unifies formatting for binary, octal, hexadecimal, and decimal? I think this only differs by the base passed to the int's toString().

// CHECK: sim.fmt.hex %arg0 : i42
moore.fmt.int hex_upper %arg0, width 42, align right, pad space : i42
// CHECK: sim.fmt.dec %arg0 {specifierWidth = 42 : i32} : i42
moore.fmt.int decimal %arg0, align right, pad space, width 42 : i42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for signed but no width after this?

  // CHECK: sim.fmt.dec %arg0 {isSigned = true} : i42
  moore.fmt.int decimal %arg0, align right, pad space, signed : i42

Comment on lines 1423 to 1431
}
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Stray whitespace change. Keep a newline at the end of the file.

Comment on lines 414 to 415
// CHECK: moore.fmt.int decimal %arg1, align left, pad zero, width 42, signed : i42
moore.fmt.int decimal %arg1, align left, pad zero, width 42, signed : i42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check that the parser also eats the following? I suspect it might not 😬:

  // CHECK: moore.fmt.int decimal %arg1, align left, pad zero, signed : i42
  moore.fmt.int decimal %arg1, align left, pad zero, signed : i42

Comment on lines 147 to 155
%wrd = sim.fmt.dec %1 : i16
%wld = sim.fmt.dec %1 {isLeftAligned = true}: i16
%wrds = sim.fmt.dec %1 {isSigned} : i16
%wlds = sim.fmt.dec %1 {isLeftAligned = true, isSigned}: i16
%wrdp = sim.fmt.dec %1 {specifierWidth = 10 : i32}: i16
%wldp = sim.fmt.dec %1 {isLeftAligned = true, specifierWidth = 10 : i32}: i16
%wrdps = sim.fmt.dec %1 {specifierWidth = 10 : i32, isSigned}: i16
%wldps = sim.fmt.dec %1 {isLeftAligned = true, specifierWidth = 10 : i32, isSigned}: i16
%dec = sim.fmt.concat (%wrd, %comma, %wld, %comma, %wrds, %comma, %wlds, %comma, %wrdp, %comma, %wldp, %comma, %wrdps, %comma, %wldps)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for testing all this! 🖐️

// CHECK-DAG: %[[FD:.*]] = sim.fmt.dec %[[ARG]] : i32
// CHECK-DAG: %[[FH:.*]] = sim.fmt.hex %[[ARG]] : i32
// CHECK-DAG: %[[FO:.*]] = sim.fmt.oct %[[ARG]] : i32
// CHECK-DAG: %[[FB:.*]] = sim.fmt.bin %[[ARG]] {specifierWidth = 32 : i32} : i32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This makes me wonder if we should just call the optional width width instead of specifierWidth. No need to change anything in the PR 😁 Just a thought for a later refactoring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My rationale to go with specifierWidth was to disambiguate the datatype width (which is used when you don't add anything to the specifier, eg in %d and is inferred in SimOps.cpp) vs the specifier width (which is used when you explicitly specify the width you want, eg %10d, which we inherit from the input file itself). Should we still rename it to width?

@fabianschuiki fabianschuiki added Moore Simulator Involving the Sim dialect or other simulation concerns labels Jan 2, 2026
Copy link
Contributor

@fabianschuiki fabianschuiki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the tweaks and factoring out the code 🥳 🙌. I've rebased this onto main and fixed a few breakages with the time formatting that landed on main in the meantime. Going to merge this as soon as CI is happy!

@fabianschuiki fabianschuiki merged commit 197dc38 into llvm:main Jan 5, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Moore Simulator Involving the Sim dialect or other simulation concerns

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants