10000 [Calyx-FIRRTL] Support for template FIRRTL primitives by ayakayorihiro · Pull Request #1864 · calyxir/calyx · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[Calyx-FIRRTL] Support for template FIRRTL primitives #1864

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 31 commits into from
Jan 29, 2024

Conversation

ayakayorihiro
Copy link
Contributor
@ayakayorihiro ayakayorihiro commented Jan 23, 2024

As discussed briefly in #1860 , FIRRTL modules are monomorphic; i.e. you cannot pass parameters into modules in the same fashion that you can pass parameters to Calyx primitives. Therefore, we need to generate specific FIRRTL modules that represent a Calyx primitive usage that we later instantiate in our code. The first step towards this was writing a backend (primitive-uses) that generates a JSON file that contains all of the primitive uses in a particular Calyx program (#1860).

This PR contains two main changes: (1) a Python script (generate-firrtl-with-primitives.py) that uses a base FIRRTL file and the primitive usage information (in the JSON file) to produce a complete FIRRTL program; (2) excluding the previous extmodule declarations for primitives (added in #1835 ) by default and instead adding a flag (--emit-primitive-extmodules) for them.

Some more rationale about (2): we can't use extmodule declarations for our purposes of using ESSENT. But for testing and other reasons, it would be nice to have the option to produce extmodule declarations and use the preexisting Verilog implementations instead.

I'm currently adding fud2 support for the pipeline described below in (1)! As always, any suggestions would be very appreciated :)

Usage

(1) Producing a complete FIRRTL file

First, use the firrtl backend to produce the base FIRRTL file and the primitive-uses backend to produce the primitive uses information:

cargo run examples/tutorial/language-tutorial-mem.futil -b firrtl -o language-tutorial-mem-base-firrtl.fir
cargo run examples/tutorial/language-tutorial-mem.futil -b primitive-uses -o primitive-uses.json

Then, run generate-firrtl-with-primitives.py to produce the complete FIRRTL program:

python3 tools/firrtl/generate-firrtl-with-primitives.py language-tutorial-mem-base-firrtl.fir primitive-uses.json

(2) To produce FIRRTL that contains extmodule declarations, run the firrtl backend with the flag --emit-primitive-extmodules. ex)

cargo run examples/tutorial/language-tutorial-mem.futil -b firrtl --emit-primitive-extmodules

Running the firrtl backend without the option will produce the FIRRTL translation without extmodule declarations (note: this code will not compile in FIRRTL on its own).

@ayakayorihiro ayakayorihiro self-assigned this Jan 23, 2024
@rachitnigam
Copy link
Contributor

The -x flags are usually used to provide information to passes. I would recommend some other, non-x flag to specify this behavior for the backend

@ayakayorihiro ayakayorihiro changed the title [Calyx-FIRRTL] Commandline option for generating extmodule declarations in translated FIRRTL [Calyx-FIRRTL] Support for template FIRRTL primitives Jan 23, 2024
@ayakayorihiro
Copy link
Contributor Author

I fixed up the flag based on @rachitnigam 's suggestion and made all of the changes I wanted to make to this PR! Sorry for the confusion earlier.

Copy link
Contributor
@sampsyo sampsyo left a comment

Choose a reason for hiding this comment

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

Wahoo; looks wonderful!! This all makes a lot of sense to me. I have a few minor Python-level suggestions for the new generator script, but this seems to be playing the role we need it too.

There is one slightly strange thing about the way the new generate-firrtl-with-primitives.py tool works, and I wanted to bring it up even though I see why it has to be this way. Basically, before seeing this PR, I expected that the tool here could just generate a primitives file, and then we could combine it after the fact with the original "main" program. Like, the workflow would go like this:

$ calyx -b firrtl foo.calyx -o foo.firrtl
$ calyx -b primitive-uses foo.calyx -o foo.prims.json
$ gen_firrtl_primitives.py < foo.prims.json > prims.firrtl
$ cat foo.firtl prims.firrtl > complete.firrtl

But, sadly, cat doesn't typically suffice to combine two FIRRTL files. The problem is that FIRRTL files need to start with circuit declaration, and we need to somehow insert the primitive into the same circuit as the main Calyx-generated code. Hence the need for the Python script to take the main FIRRTL code as input, and the need for the somewhat funky interleaving of output lines in its prints.

Anyway, I think it would be cool to somehow simplify this… maybe with some other way of linking FIRRTL circuits that I haven't discovered yet? But I would not let that hold up this PR; we can look into that separately!

Comment on lines 32 to 40
if len(sys.argv) != 3:
args_desc = [
"FIRRTL_FILE",
"PRIMITIVE_USES_JSON"
]
print(f"Usage: {sys.argv[0]} {' '.join(args_desc)}")
return 1
firrtl_file = open(sys.argv[1])
primitive_uses_file = open(sys.argv[2])
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be slightly nice to refactor main a tiny bit to separate the argument parsing from the "real work." Like, there could be a separate function declared like this:

def generate(firrtl_file, primitive_uses_file):

…that is then called by main, which otherwise just has the responsibility of looking in sys.argv.

print(firrtl_file.readline().rstrip())
# Display the primitive definitions.
primitive_insts = json.load(primitive_uses_file)
if len(primitive_insts) != 0 :
Copy link
Contributor

Choose a reason for hiding this comment

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

A Python shortcut is relevant here:

Suggested change
if len(primitive_insts) != 0 :
if primitive_insts:

Comment on lines 51 to 55
tmp_file = open(tmp_file_name, "w")
# execute the subprocess containing m4
subprocess.run(m4_args, stdout=tmp_file)
for line in open(tmp_file_name, "r"):
print(line.rstrip())
Copy link
Contributor

Choose a reason for hiding this comment

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

It's possible to avoid the temporary file here, I think. The subprocess module has a capture_output option that lets you obtain the stdout as a string directly. So something like this is likely to suffice:

proc = subprocess.run(m4_args, capture_output=True)
print(proc.stdout)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I actually tried this before getting to the temporary file hack, but went for the temp file because I got a byte string from capture_output and didn't know I can convert byte strings to strings 😓 Fixed!

primitive_name = inst["name"]
args = []
# hack to replace the module name with the corresponding parameterized version
# FIXME: figure out a way to do substring replacement in m4
Copy link
Contributor

Choose a reason for hiding this comment

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

As straightforward as this is, one maybe-bad suggestion here might be that we could just not use m4. I know I suggested it originally, and it is nice that it can do most of the work for us, but since we already need a little Python script wrapped around the m4 invocation, we could imagine just using Python's str.replace to similar effect. Even if it does mean a bit more "heavy lifting" in Python-land, it would be one less moving part overall!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I honestly think this is not that bad of an idea :) I ran into some weird write races when using the m4 subprocess rig, so having everything be in the one python script would be nice. Let me make that change!

@sampsyo
Copy link
Contributor
sampsyo commented Jan 26, 2024

To start answering my own question about this: it looks like the FIRRTL spec very recently added a new concept called public module: chipsalliance/firrtl-spec@c7292b3

This seems to be the "official" mechanism for composing separate FIRRTL circuits together. Barring that, this text-level "interleaving" is probably the right thing to do!

@ayakayorihiro
Copy link
Contributor Author

But, sadly, cat doesn't typically suffice to combine two FIRRTL files. The problem is that FIRRTL files need to start with circuit declaration, and we need to somehow insert the primitive into the same circuit as the main Calyx-generated code. Hence the need for the Python script to take the main FIRRTL code as input, and the need for the somewhat funky interleaving of output lines in its prints.

Yeah, the problem was that I couldn't output everything using one command, which caused some trouble with the fud2 support 😞 My original little bash script that put everything together (when the python script was only producing primitive definitions) looked like this:

# cat the first line of the firrtl
head -1 ${TMP_DIR}/tmp.fir > ${OUT}
# produce the primitives
python3 ${CALYX_DIR}/tools/firrtl/generate-firrtl-primitives.py ${TMP_DIR}/tmp.json >> ${OUT}
# cat the 2-nth line of the firrtl
tail -n +2 ${TMP_DIR}/tmp.fir >> ${OUT}

I suppose I could've put these three commands into a single bash subprocess (via ()) but I thought it would be more elegant to have the python script produce the entire FIRRTL instead.

To start answering my own question about this: it looks like the FIRRTL spec very recently added a new concept called public module: chipsalliance/firrtl-spec@c7292b3

This seems to be the "official" mechanism for composing separate FIRRTL circuits together. Barring that, this text-level "interleaving" is probably the right thing to do!

Thank you for finding this! I'll keep things as is for now but I think it would be great to use public modules in the future once I've been able to test the straightline/hacky way of things :)

@ayakayorihiro
Copy link
Contributor Author

I changed the generate-firrtl-with-primitives.py script to remove the m4 subprocess and use Python's string replacement as @sampsyo suggested in the comment above! I've tested some things so I'm somewhat confident it does what we want it to, but I'd appreciate another pass on the generate-firrtl-with-primitives.py script 🙂

Copy link
Contributor
@sampsyo sampsyo left a comment

Choose a reason for hiding this comment

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

Looks awesome!! It came out significantly simpler this way, without going through m4. Seems good to go from here!

@@ -0,0 +1,66 @@
import json
import os
import subprocess
Copy link
Contributor

Choose a reason for hiding this comment

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

The subprocess import is now unused.

@sampsyo
Copy link
Contributor
sampsyo commented Jan 28, 2024

I'll keep things as is for now but I think it would be great to use public modules in the future once I've been able to test the straightline/hacky way of things :)

Cool! FWIW, while I haven't checked thoroughly, I expect that the Scala-based FIRRTL compiler doesn't support public module because the concept appears to be so new… and who knows if any tooling supports it yet. So I think this string-gluing approach you're doing will be just fine for the foreseeable future!

@ayakayorihiro ayakayorihiro enabled auto-merge (squash) January 29, 2024 03:28
@ayakayorihiro ayakayorihiro merged commit 82894f9 into main Jan 29, 2024
@ayakayorihiro ayakayorihiro deleted the firrtl-extmodule-option branch January 29, 2024 03:40
rachitnigam pushed a commit that referenced this pull request Feb 16, 2024
* First pass for backend to produce json file containing primitive instantiation info

* pretty print the json

* Map from param names to values

* Fix clippy error

* usage message

* Fix doc comments

* Change JSON format for parameter names and values

* First commit of python script to process json and generate primitives

* Fix formatting error

* Hack to generate module name

* Add some primitives

* Generate module name + primitive templates for all documented primities

* fix comment

* Changed naming and fixed unchanged name

* Make parameter collection functional

* Option for enabling extmodule declarations for primitives

* Fix formatting error from extracted function

* Fix test configurations to produce extmodule

* Rename script and pass in FIRRTL file

* Change commandline option to not use -x

* Update tests to match new commandline option

* Hack to get around problem of subprocess and print output competing in fud2

* Add error message if primitive template file does not exist

* Fix bug and added error message. Also added std_reg and undef primitives

* Fix script from PR comments

* Refactor script to use python to replace strings instead of using m4 subprocess

* Remove unnecessary import
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.

3 participants
0