Replies: 6 comments 12 replies
-
are you imagining this will simply be replaced with a component of the given type, e.g. if
|
Beta Was this translation helpful? Give feedback.
-
Yeah! That's a good way of putting it. But to preserve the fact this is a specific instantiation of the
…assuming we don't hate my C++-style namespacing of |
Beta Was this translation helpful? Give feedback.
-
@sampsyo Any thoughts on how namespace and name resolution should work? We've run into this problem in #431 where generated and library names collide. Obviously, namespaces are a nice solution but I don't want to build a full-blown name resolution system. Is there an approach that is both incremental and sane? |
Beta Was this translation helpful? Give feedback.
-
Looks like FuseSoC provide a way to call generators: https://fusesoc.readthedocs.io/en/stable/user/build_system/generators.html |
Beta Was this translation helpful? Give feedback.
-
Because we are circling around this category of discussion as a result of #1596 and related stuff, I just wanted to revisit the most relevant pieces that I think are worth discussing. In general, I still think the above strawperson proposal is not a bad place to start, despite the shortcomings I listed originally. As a synopsis, the extension would look like this—with slightly massaged syntax:
In a sense, user-facing idea here is to allow parameterization of Calyx-implemented components in a way that doesn't look too different from the way we allow parameterization of Verilog-implemented primitives. That is, if Concretely, the proposed language extension has only two things:
To implement this extension, we would write a new pass that runs the generators and resolves the component names, "monomorphizing" them. Here's what this pass would do:
The point of this process is that namespaces are the point where metaprogramming happens, meaning that this is the point where we invoke generator scripts and do any caching we want to do. A cryptographic hash of the parameters suffices to unambiguously identify the generated code—unless, of course, the code for the generator changes (so we probably want a way to invalidate this cache). I realize that this proposal has the uncomfortable property of turning the Calyx compiler into something more like a build system. That is, the Calyx compiler doesn't currently need to do any complicated orchestration of commands, files on the filesystem, etc.; that is all fud's job. The proposed pass above would need to do a bunch of command invocation, all of which could fail, and manage an on-disk cache and stuff—pretty complicated real-world stuff that is not the traditional domain of a compiler, TBH! I acknowledge that this is not great. But in a way, I don't see a complete way around the fact that parameterization needs to be a first-class language feature to be useful. There is a chance we want the above rewriting to be an external tool rather than a pass built into the compiler, but that is a minor difference in the end. |
Beta Was this translation helpful? Give feedback.
-
One outcome from today's synchronous discussion was that we're all pretty much on board with pursuing something like the strawperson proposal above, and what we need next is a techincal design we can implement. dependencies and stuffWe also discussed the biggest downside to this proposal, which is the messiness of having the "interface" to generator programs be Unix command invocations. This presents lots of annoying problems having to do with dependencies: how do we make sure these generator programs have everything they need to actually run? If the generator program is just a Python script, for example, it might have Python dependencies or need a specific version of the Python interpreter… supplying all of that is a huge pain that we do not want to solve. One can imagine "fancy" solutions to this that include:
But I think what we should do for now is just punt: generators are Unix commands, and satisfying those dependencies is not our problem. If you need Python dependencies, then set up a virtualenv or whatever ahead of time. If you need to build a Rust generator tool, run toolingThe biggest question to be resolved, in my view, is what the tooling looks like. That is, how does the thing work that is responsible for executing generator commands, caching their output, mangling names, and rewriting code to use those mangled names? We have tossed around 3 main options:
I'll resist recapping all the reasons, but I (somewhat strongly) prefer option 3. I think an interesting phase of work would be speccing out how that standalone tool would work. non-parameterized namespacesHere's a question that came up: do we want (at any point) to support the non-parameterized version of modules/namespaces? Like, should a Calyx program—with no parameterization/code generation in sight—be able to write something like this?
Here, the
So this by itself is, like, marginally convenient but not all that important. It could be a semi-pleasant way to avoid naming conflicts. But it's not super compelling for an IR, where convenience shouldn't matter much. (Whether we implement it as a compiler pass, part of the AST loading, or a separate transformation seems orthogonal.) The reason to do it would be that it would simplify the library-generation process into two steps. That is, we would first generate a module, transforming this:
Into this:
That first step does all the generation, but namespaces are still preserved. Then the second step would perform the name-mangling to flatten namespaces. Having these two transformations be somewhat decoupled into smaller steps seems nice. So that would be a reason to add non-parameterized namespaces to Calyx: merely to support parameterized (generator-driven) namespaces in a simpler way. If we did chose this route, we would build non-parameterized namespaces first, and then implement the whole generator-invoking tool on top of that. |
Beta Was this translation helpful? Give feedback.
-
Especially given the recent discussion about numerical libraries culminating in #416, I've been thinking a bit about what libraries should look like in Calyx. Aside from the status quo, i.e., the ability to
import
other Calyx files, this note explores the general question of what a more complete ecosystem of modules for Calyx would look like.The vision is this: if we wanted to let developers do something like
calyx-pkg install fixedpt
to get access to a complete complement of fixed-point math operators, how would that need to work? How do you make a Cargo or npm for hardware libraries (and as a consequence expunge the term "IP block" from the universe forever)? The goal here is not to actually design a solution but to frame the problem and outline the shape that a solution must have.In particular, let's imagine that we have already solved the boring parts: the package registry, where to host the actual files, versioning, dependency resolution, namespacing, integrity checks—those we could just steal from the real Cargo. But what makes packaging and modularity different from the same concept in software when we're talking about hardware? I think the biggest difference is the need for module-level metaprogramming.
Some Observations and a Contradiction
Here are a few direct observations about the problem:
These observations lead to a contradiction: we want libraries; libraries inherently involve metaprogramming; but it would be ridiculous to invent our own metaprogramming system in Calyx. I think the key to resolving this contradiction is to give up: to avoid solving the metaprogramming problem ourselves and instead foist the responsibility onto any old Calyx generator.
Meta-Parameters to Calyx Components
Let's first take stock of the kind of metaprogramming that is sneakily already possible in Calyx: parameters to Verilog-implemented Calyx
extern
s. For example, our core.futil currently contains a declaration like this:where
width
is a parameter that gets threaded into the SystemVerilog implementation. Calyx programs that use this library can now declare cells likestd_add(32)
,std_add(42)
if they want, whatever! The implementation starts like this:That
parameter
keyword is the entry point to Verilog's own metaprogramming facility (known as "elaboration," but it's the same thing). So in a way, Calyx parameters are a way to abdicate the need for metaprogramming to the Verilog compiler. It might seem strange at first that we have this whole parameter facility just for Verilog code—that proper Calyx code doesn't enjoy the same functionality. As argued above, though, Calyx is supposed to represent generated code, not generating code. Adding this style of metaprogramming to Calyx code would be a mess and a slippery slope with no clear endpoint.So why do we have these parameters at all? I think there are a few reasons why we need them:
std_add_1
,std_add_2
, and so on. It's really, really nice from a practical perspective for frontends to have a way to use any width of adder they want without worrying about getting the naming convention right—or whether we pre-generated enough adder names ahead of time.std_add
performs integer addition—presumably by recognizing the name. It would be horrible if this pass needed to parse the name ofstd_add_42
to draw the same conclusion. The same need applies to a Calyx interpreter that needs to know how to add numbers without simulating the SystemVerilog.So even if it seems odd at first glance, I think our current design has already struck a nice "division of labor" for the library metaprogramming problem. Calyx is involved in parameters at the type level, but it abdicates responsibility for metaprogramming at the term level. Doing the ordinary polymorphism thing for the port widths in
std_add
is very useful for making library declarations intelligible, but Calyx is wise to avoid getting its hands dirty with actually interpolating the parameters in the implementation. By delegating the latter responsibility to the already-existing metaprogramming facility in the SystemVerilog synthesizer, Calyx gets to sidestep the unpleasantness of deciding what the world's greatest metaprogramming facility would look like.Strawperson Proposal for Metaprogrammed Modules
What if we took this same division of responsibilities and ran with it? Calyx would stay in charge of how parameters affect its (quite simple) type system and delegate the actual metaprogramming problem to those who do it best already: Calyx frontends.
The strawperson proposal is this: Calyx libraries invoke an external tool to generate the Calyx implementations for a given set of parameters. The Calyx compiler runs an actual command line in a subprocess to instantiate the library. Making up some syntax, we might define an exponentiation library like this:
This
extern
block works like a normal SystemVerilog-backedextern
, but the!
indicates that we have to run a program instead of just reading a file to get the implementation. (And the generated code may be Calyx code instead of SystemVerilog source.) I have invented some silly interpolation syntax for indicating how to pass parameters likewidth
to the generator via command-line arguments.This imaginary syntax also introduces names (
mymath
) and parameters (width
) for these extern blocks. The idea is stolen directly from ML module functors. Relative to the current syntax for parameterizedextern
primitives, this functor style would allow several related declarations to share a single parameter. It would also simplify the logistics a bit by letting a single generator execution to produce a big Calyx file with multiple parameterized component implementations (instead of requiring a new subprocess for every instantiated component). You'd instantiate our exponentiation component, for example, using syntax likemymath(32)::exp
.Now, this strawperson has some pretty obvious problems! We have essentially made Unix itself the de facto abstraction for metaprogramming. Requiring the compiler to invoke arbitrary external commands during compilation would have severe pitfalls:
rm -rf /
?Yes, all of these drawbacks make this proposal seem kind of silly—they're not pitfalls that your favorite language's module system must typically contend with. I contend that they may, however, be the necessary price to be paid for decoupling the metaprogramming mechanism from the language. And there may be creative ways to mitigate these problems:
mymath(32)
module without rerunning the generator.Related Work
Beta Was this translation helpful? Give feedback.
All reactions