calyxir / calyx

Intermediate Language (IL) for Hardware Accelerator Generators
https://calyxir.org
MIT License
450 stars 44 forks source link

`@data` default assignment optimization does not work on external memories #2012

Closed andrewb1999 closed 3 weeks ago

andrewb1999 commented 3 weeks ago

Related to #2011 when external memories get externalized they lose their @data attributes on data ports which prevents them from having their default assignments optimized away in the verilog backend. This is particularly problematic because chaining with memory accesses can often be a frequency bottleneck for designs.

rachitnigam commented 3 weeks ago

Should be an easy fix! We just need to move the attributes from the cell ports to the signatures.

rachitnigam commented 3 weeks ago

@andrewb1999 mentioned that changing the externalize pass to move the @data attribute was not enough. My guess is that this is because infer-data-path runs early in the optimization pipeline and the changes are not propagated.

calebmkim commented 3 weeks ago

Here is why the simple changes doesn't work. Consider the following example:

component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) {
  cells {
    // out.write_data's parent is out, which is marked as @data
    @external @data out = seq_mem_d1(32, 1, 1);
    ...
} 

Here, any assignments to out.write_data = ... will be given the "default to x" behavior (as opposed to "default to 0"). This is because both the out.write_data port and theout cell are marked as @data.

Compare this to after running the externalize pass. We get:

// out_write_data's parent is now main which is not marked as @data 
component main(...@data out_write_data: 32....) { ... }

Now, out_write_data will be given "default to 0" behavior, since the ThisComponent (i.e, @main), is not marked as @data. In other words, we have lost information when we inlined (namely, we have lost the information that out was an @data cell).

Solutions

The easiest solution is to check, in the externalize pass, that out is a @data cell before giving out_write_data the @data attribute.

Then, in the Verilog backend, if we know the parent is ThisComponent and the port is @data, then we can give "default to x" behavior, even without checking if the port's parent cell is marked @data (since we already checked in the @externalize pass).

// for any ports in main's signature (i.e., ports in which ThisComponent is the parent, we only need to check the port for @data
component main(...@data out_write_data: 32....) { 
  // any other ports need to be marked @data and their parent needs to be @data in order to get the "default to x" behavior 
}

Also, we know that if @data is an output port in the signature of ThisComponent, then we know this is the result of the externalize pass. In every other case, @data goes on the input port of the signature. Therefore, if we see @data on the output port of the signature of ThisComponent, then we know we don't need to check the parent cell-- we already checked that during externalize