cucapra / filament

Fearless hardware design
http://filamenthdl.com/
MIT License
159 stars 9 forks source link

The Filament `gen` Framework #364

Closed rachitnigam closed 11 months ago

rachitnigam commented 1 year ago

Filament now has all the features required to model ADLs as parameterized generators. The next step is to build tools that enable us to quickly connect together tools and enable optimization flows. There are a couple of relevant efforts here:

At a high-level, I want to enable the following:

Next up, I want to build a tool called opt that can use results from synthesis or estimation flows and modulate the knobs of the generator tools to optimize the design while guaranteeing. This modulation should not affect the final designs because the wrappers should be timing abstract.

Together, these tools will make Filament's ADL integration story very clear: transparent integration and powerful optimization.

Language-level Representation

First, we need to figure out how to represent modules that need to be generated from some tool. I think the simplest thing to do here is define something like the extern block where users provide shims for modules and have them be generated from the external tool:

// Use the tool named "aetherling to generate the module"
generate "tool:aetherling" {
    // The CLI invocation of Conv2D takes at least the argument width.
    // However, this is not enough information to invoke the tool.
    comp Conv2D[width]<'G: II>(...) with {
        some II where II > 0;
    };

    comp Sharpen[width, depth]<'G: II>(...) with {
        some II where II > 0;
    };
}

The above command will use the tool named aetherling to generate the modules Conv2D and Sharpen. We can use this module like any other extern module in Filament:

comp main<'G: 1>(...) {
    C0 := new Conv2D[10];
    C1 := new Conv2D[20];
    S0 := new Sharpen[30, 5];
}

For the purposed of the Filament compiler, there is no difference between a generate block and an extern block; they both declare modules that are not Filament source files.

Tool Interface

To figure out how to use a particular tool, we need a couple of pieces of information about it:

  1. The modules the tool is capable of generating
  2. The parameters needed for each module and constraints on it
  3. The existential parameters reported in the generation of a module

Note that all of this information is already present in the above generate block! The big insight here is that the language-level construct actually already has a lot of the information needed to interface with the tool. However, a couple of pieces of information is missing:

  1. How does the tool mangle the names of modules its generating? Does Conv2D[10] need to be transformed to Conv2D_10?
  2. What is the command-line interface for the tool?

All of this information is defined in a TOML file:

name = "aetherling"
path = "/.local/bin/aetherling"
# Optional tool that ingests the STDOUT and STDERR of the tool and generates a JSON file that provides information about the existential parameters. Not needed if the tool itself conforms to this interface.
output_parser = "aetherling-info-parse"

# Extra configuration for generating the `Conv2D` module
[module.Conv2D]
parameters = ["width"]
# The output format mentions parameters defined by the module
name_format = "Conv2D_$width"
# CLI format. Note the extra argument passed for managing the latency. This parameter is not modeled in the interface of the `conv2d` module.
cli_format = "$tool conv2d --width=$width --latency=4"
# The output format. Each key indexes into the JSON structure that reports the output and connects that information to the required existential parameters. The JSON access format is the same one as `jq`.
outputs = {
    "II": ".initiation_interval",
    "L": ".num_cycles"
}

[module.Sharpen]

And we slightly modify the generate definition to point to this tool declaration:

generate "tool:aetherling.toml" { ... }

Now we have a complete tool interface in our hands!

Generation Manifest

To invoke the tool, we parse the uses of the modules and generate the following manifest file:

{
    "aetherling": [
        { "name": "Conv2D", "width": 10 },
        { "name": "Conv2D", "width": 20 },
        { "name": "Sharpen", "width": 30, "depth": 5 },
    ],
    "another-tool": []
}

The program uses the Conv2D module twice and Sharpen once with the given parameters. Given this information, the gen tool can invoke the various tools and generate the modules. The result will be one or more files Verilog files and corresponding Filament shim files:

extern "aetherling_conv2d_10.sv" {
    comp Conv2D_10<'G: 5>(...) { ... }
}
extern "aetherling_conv2d_20.sv" {
    comp Conv2D_20<'G: 11>(...) { ... }
}

comp Conv2D[width]<'G: II>(inps) -> (outs) with {
    some II where II > 0;
} {
    if width == 10 {
        c := new Conv2D_10<'G>(inps);
        outs = c.outs;
        II := 5
    } else if width == 20 {
        c := new Conv2D_20<'G>(inps);
        outs = c.outs;
        II := 11
    } else {
        assert false;
    }
}

There is a couple of things going on here:

Once we have all these generated modules, we can go ahead and link them to the original Filament program and compile it as usual.

rachitnigam commented 1 year ago

Two things that came up during the meeting:

rachitnigam commented 1 year ago

Marking this as available! Let's build an MVP that can support connection with flopoco which is kind of the perfect use case for this. In building it, I suspect we'll find lots of pain points and places to refine the design.