Skip to content

[FIRRTL] Add fprintf operation #8346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 3, 2025
Merged

[FIRRTL] Add fprintf operation #8346

merged 11 commits into from
Apr 3, 2025

Conversation

uenoku
Copy link
Member

@uenoku uenoku commented Mar 24, 2025

Add support for fprintf operation to FIRRTL dialect. This operation is similar
to printf but writes to a file instead of stdout.

The operation has the following syntax:
fprintf(clock, cond, "output_file", "format_string", substitutions...)
Example:

FIRRTL version 4.0.0
circuit PrintTest:
  ; CHECK-LABEL: @PrintTest
  public module PrintTest :
    input clock : Clock
    input cond : UInt<1>
    input var : UInt<32>
    fprintf(clock, cond, "test.txt", "test %d\n", var)
    fprintf(clock, cond, "test.txt", "[{{SimulationTime}}]: {{HierarchicalModuleName}}\n")
`ifndef __CIRCT_LIB_LOGGING
  // CIRCT Logging Library
  package __circt_lib_logging;
    class FileDescriptor;
      static int global_id [string];
      static function int get(string name);
        if (global_id.exists(name) == 32'h0)
          global_id[name] = $fopen(name);
        return global_id[name];
      endfunction
    endclass
  endpackage
  `define __CIRCT_LIB_LOGGING
`endif // not def __CIRCT_LIB_LOGGING
module PrintTest(
  input       clock,
              cond,
  input [7:0] a
);

  `ifndef SYNTHESIS
    reg [31:0] fd_test_txt;
    initial begin
      fd_test_txt = __circt_lib_logging::FileDescriptor::get("test.txt");
    end // initial
    always @(posedge clock) begin
      if ((`PRINTF_COND_) & cond) begin
        $fwrite(fd_test_txt, "hello");
        $fwrite(fd_test_txt, "[%0t]: %m\n", $time);
      end
    end // always @(posedge)
  `endif // not def SYNTHESIS
endmodule

Currently file name is not interpreted as format string.

Add support for fprintf operation to FIRRTL dialect. This operation is similar
to printf but writes to a file instead of stdout. The output file name is
interpreted as a format string in SystemVerilog, allowing users to use %m to
create unique file names per instance.

The operation has the following syntax:
fprintf(clock, cond, "output_file", "format_string", substitutions...)
@darthscsi
Copy link
Contributor

I believe this approach will wind up with different file handles and thus different buffers if opening the same file from different modules.

@terpstra
Copy link

So, it's nice that you can use %m in the filename. However, it immediately begs the question of if I can use {{SimulationTime}} or %d. These would be super useful. For example, I could use hartid for %d and then I'd be able to produce exactly the same behavior as the current loggers. On the other hand, supporting that might impose an undue burden for implementations like firesim.

@uenoku
Copy link
Member Author

uenoku commented Mar 24, 2025

I believe this approach will wind up with different file handles and thus different buffers if opening the same file from different modules.

Yes, currently it's user's responsibility to give unique names for each instantiation. Users can insert %m into the file name to prevent that issue as well. I don't think there is an easy way to provide same file descriptors globally for the same files. Class static member is usually common workaround for this problem but SV dialect doesn't have a class or notion of static, so it's going to require a lot of work in that case.

@uenoku
Copy link
Member Author

uenoku commented Mar 24, 2025

However, it immediately begs the question of if I can use {{SimulationTime}} or %d

It's really tricky to make filename depend on runtime values (so I generally don't want to support SimulationTime), but I think it's possible to support compile time constant (like hartID). Unfortunately FIRRTL doesn't have good support for parameters yet so I think it's not feasible to support near future.

@uenoku
Copy link
Member Author

uenoku commented Mar 27, 2025

Based on the offline discussion I'm working on sharing file descriptor for the same file names in the global scope. A common way in SV is to use static class member and function, but CIRCT has neither class nor notion of static at this point so we explored the direction to introduce CIRCT system verilog library, and use it as external function.

It's still necessary to clean up LowerToHW and design circt library more carefully but this is current output:

`ifndef __CIRCT_LIB_LOGGING
  // CIRCT Logging Library
  package __circt_lib_logging;
      class FileDescriptor;
          static int global_id [string];
          static function int get(string name);
              if (!global_id.exists(name))
                  global_id[name] = $fopen(name);
              return global_id[name];
          endfunction
      endclass
  endpackage
  `define __CIRCT_LIB_LOGGING
`endif // not def __CIRCT_LIB_LOGGING
module PrintTest(
  input        clock,
               cond,
  input [31:0] var_0
);

  `ifndef SYNTHESIS
    reg [31:0] fd_test_txt;
    reg [31:0] fd_25m_txt;
    initial begin
      fd_test_txt = __circt_lib_logging::FileDescriptor::get($sformatf("test.txt"));
      fd_25m_txt = __circt_lib_logging::FileDescriptor::get($sformatf("%m.txt"));
    end // initial
    always @(posedge clock) begin
      if ((`PRINTF_COND_) & cond) begin
        $fwrite(`PRINTF_FD_, "test %d\n", var_0);
        $fwrite(fd_test_txt, "test %d\n", var_0);
        $fwrite(fd_test_txt, "hello");
        $fwrite(fd_25m_txt, "test %d\n", var_0);
      end
    end // always @(posedge)
  `endif // not def SYNTHESIS
endmodule

@uenoku uenoku force-pushed the dev/hidetou/fprintf branch from fddfd03 to 6a2b9e7 Compare April 2, 2025 10:29
@uenoku uenoku marked this pull request as ready for review April 2, 2025 10:29
@uenoku uenoku force-pushed the dev/hidetou/fprintf branch from 6a2b9e7 to 31dd01b Compare April 2, 2025 10:43
@uenoku
Copy link
Member Author

uenoku commented Apr 2, 2025

PR is now ready for review. I removed format string support in file name in the latest revision (so %m is not allowed now). This is because we can support tiny subset of format specifier (probably only %m) to guarantee that file names are static but current format string is used mostly for dynamic values (%d, %t etc). To avoid confusion currently file name is not treated as either verilog or FIRRTL format string.

Copy link
Member

@seldridge seldridge left a comment

Choose a reason for hiding this comment

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

LGTM

Only nits and comments. I like the final output.

UPF will be a problem here as registers can go to x. We should track this and come up with a solution.

Comment on lines +4721 to +4723
fd = builder.create<sv::RegOp>(
builder.getIntegerType(32),
builder.getStringAttr("fd_" + outputFile.getValue()));
Copy link
Member

Choose a reason for hiding this comment

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

This will have the same problem as plusargs for UPF simulations (or any simulation which introduces an external source of x). This does not need to be solved now. However, this will mean that if we do a UPF power cycle, that suddenly all of these will try to write to an fd which is x.

A way to handle this is to use both an initial block and an always block. Open an issue to track this.

Copy link
Member Author

Choose a reason for hiding this comment

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

The value will never be leaked to designs unlike plusargs, so I hope it's less problematic.

Copy link

@davidbiancolin davidbiancolin left a comment

Choose a reason for hiding this comment

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

Very exciting!

This is more of an aside, but perhaps we want more specific preprocessor macros for switching off some of these "unsynthesizable" constructs since (and i need to fact check this) different emulators and more advanced prototyping systems will be able to handle different subsets of them.

@@ -4549,7 +4621,8 @@ LogicalResult FIRRTLLowering::visitStmt(RefReleaseInitialOp op) {

// Printf is a macro op that lowers to an sv.ifdef.procedural, an sv.if,
// and an sv.fwrite all nested together.
LogicalResult FIRRTLLowering::visitStmt(PrintFOp op) {
template <class T>

Choose a reason for hiding this comment

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

nit: you probably need to expand the scope of the comment above?

@uenoku uenoku merged commit 87ae029 into main Apr 3, 2025
5 checks passed
@uenoku uenoku deleted the dev/hidetou/fprintf branch April 3, 2025 15:26
@sequencer
Copy link
Contributor

After this is merged, should the macro PRINTF_COND_ introduced from #7983 being reverted? I think we can align to this feature.

KelvinChung2000 pushed a commit to KelvinChung2000/circt that referenced this pull request Apr 22, 2025
Add support for fprintf operation to FIRRTL dialect. This operation is similar
to printf but writes to a file instead of stdout.

The operation has the following syntax:
```
fprintf(clock, cond, "output_file", "format_string", substitutions...)
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants