5.1. Hammer IR and Meta Variables
5.1.1. Hammer IR
Hammer IR is the primary standardized data exchange format of Hammer. Hammer IR standardizes physical design constraints such as placement constraints and clock constraints. In addition, the Hammer IR also standardizes communication among and to Hammer plugins, including tool control (e.g. loading tools, etc) and configuration options (e.g. number of CPUs).
5.1.2. The hammer-config library
The hammer-config library is the part of Hammer responsible for parsing Hammer YAML/JSON configuration files into Hammer IR. Hammer IR is used for the standardization and interchange of data between the different parts of Hammer and Hammer plugins.
Note
There is a built-in order of precedence, from lowest to highest:
Hammer’s
defaults.yml
Tool plugin’s
defaults.yml
Tech plugin’s
defaults.yml
User’s Hammer IR (using
-p
on the command line)JSON/YAML files specified with
-p
later on the command line have higher precedence and keys appearing later if duplicated in a given file also take precedence. In the examples below, “Level #” will be used to denote the level of precedence of the configuration snippet.
The get_setting()
method is available to all Hammer technology/tool plugins and hooks (see Extending Hammer with Hooks).
5.1.3. Basics
foo:
bar:
adc: "yes"
dac: "no"
The basic idea of Hammer IR in YAML/JSON format is centered around a hierarchically nested tree of YAML/JSON dictionaries. For example, the above YAML snippet is translated to two variables which can be queried in code - foo.bar.adc
would have yes
and foo.bar.dac
would have no
.
5.1.4. Overriding
Hammer IR snippets frequently “override” each other. For example, a technology plugin might provide some defaults which a specific project can override with a project YAML snippet.
For example, if the base snippet contains foo: 12345
and a snippet in a file of higher precedence contains foo: 54321
, then get_setting("foo")
would return 54321
.
5.1.5. Meta actions
Sometimes it is desirable that variables are not completely overwritten, but instead modified.
For example, say that the technology plugin provides:
vlsi.tech.foobar65.bad_cells: ["NAND4X", "NOR4X"]
And let’s say that in our particular project, we find it undesirable to use the NAND2X
and NOR2X
cells. However, if we simply put the following in our project YAML, the references to NAND4X
and NOR4X
disappear and we don’t want to have to copy the information from the base plugin, which may change, or which may be proprietary, etc.
vlsi.tech.foobar65.bad_cells: ["NAND2X", "NOR2X"]
The solution is meta variables. This lets Hammer’s config parser know that instead of simply replacing the base variable, it should do a particular special action. Any config variable can have _meta
suffixed into a new variable with the desired meta action.
In this example, we can use the append
meta action:
vlsi.tech.foobar65.bad_cells: ["NAND2X", "NOR2X"]
vlsi.tech.foobar65.bad_cells_meta: append
This will yield the desired result of ["NAND4X", "NOR4X", "NAND2X", "NOR2X"]
when get_setting("vlsi.tech.foobar65.bad_cells")
is called in the end.
5.1.6. Applying multiple meta actions
Multiple meta actions can be applied sequentially if the _meta
variable is an array. Example:
In Level 1:
foo.flash: yes
In Level 2 (located at /opt/foo):
foo.pipeline: "CELL_${foo.flash}.lef"
foo.pipeline_meta: ['subst', 'prependlocal']
Result: get_setting("foo.pipeline")
returns /opt/foo/CELL_yes.lef
.
5.1.7. Common meta actions
append
: append the elements provided to the base list. (See the abovevlsi.tech.foobar65.bad_cells
example.)prepend
: prepend the elements provided to the base list. Useful where list order matters, but make sure the file containing this directive is at the end of the list of files passed tohammer-vlsi
with-p
.subst
: substitute variables into a string.Base:
foo.flash: yes
Meta:
foo.pipeline: "${foo.flash}man" foo.pipeline_meta: subst
Result:
get_setting("foo.flash")
returnsyesman
lazysubst
: by default, variables are only substituted from previous configs. Usinglazysubst
allows us to defer the substitution until the very end.Example without
lazysubst
:Level 1:
foo.flash: yes
Level 2:
foo.pipeline: "${foo.flash}man" foo.pipeline_meta: subst
Level 3:
foo.flash: no
Result:
get_setting("foo.flash")
returnsyesman
Example with
lazysubst
:Level 1:
foo.flash: yes
Level 2:
foo.pipeline: "${foo.flash}man" foo.pipeline_meta: lazysubst
Level 3:
foo.flash: no
Result:
get_setting("foo.flash")
returnsnoman
In general, putting
lazy
in front of all other directives will defer the meta processing until the very end.crossref
- directly reference another setting. Example:Level 1:
foo.flash: yes
Level 2:
foo.mob: "foo.flash" foo.mob_meta: crossref
Result:
get_setting("foo.mob")
returnsyes
You can also append/prepend instead of substitute using
crossappendref
andcrossprependref
.transclude
- transclude the given path. Example:Level 1:
foo.bar: "/opt/foo/myfile.txt" foo.bar_meta: transclude
Result:
get_setting("foo.bar")
returns<contents of /opt/foo/myfile.txt>
prependlocal
- prepend the local path or package resource directory of this config file. Example:Level 1 (located at /opt/foo):
foo.bar: "myfile.txt" foo.bar_meta: prependlocal
Result:
get_setting("foo.mob")
returns/opt/foo/myfile.txt
deepsubst
- likesubst
but descends into sub-elements. Example:Level 1:
foo.bar: "123"
Level 2:
foo.bar: baz: "${foo.bar}45" quux: "32${foo.bar}" foo.bar_meta: deepsubst
Result:
get_setting("foo.bar.baz")
returns12345
andget_setting("foo.bar.baz")
returns32123
5.1.8. Type Checking
Any existing configuration file can and should be accompanied with a corresponding configuration types file.
This allows for static type checking of any key when calling get_setting
.
The file should contain the same keys as the corresponding configuration file, but can contain the following as values:
primitive types (
int
,str
, etc.)collection types (
list
)collections of key-value pairs (
list[dict[str, str]]
,list[dict[str, list]]
, etc.) These values are turned into custom constraints (e.g.PlacementConstraint
,PinAssignment
) later in the Hammer workflow, but the key value pairs are not type-checked any deeper.optional forms of the above (
Optional[str]
)the wildcard
Any
type
Hammer will perform the same without a types file, but it is highly recommended to ensure type safety of any future plugins.
5.1.9. Key History
With the ruamel.yaml
package, Hammer can emit what files have modified any configuration keys in YAML format.
Turning on key history is accomplished with the --dump-history
command-line flag.
The file is named {action}-output-history.yml
and is located in the output folder of the given action.
Example with the file test-config.yml
:
synthesis.inputs: input_files: ["foo", "bar"] top_module: "z1top.xdc" vlsi: core: technology: "hammer.technology.nop" synthesis_tool: "hammer.synthesis.nop"
test/syn-rundir/syn-output-history.yml
after executing the command hammer-vlsi --dump-history -p test-config.yml --obj_dir test syn
:
synthesis.inputs.input_files: # Modified by: test-config.yml - LICENSE - README.md synthesis.inputs.top_module: z1top.xdc # Modified by: test-config.yml vlsi.core.technology: nop # Modified by: test-config.yml vlsi.core.synthesis_tool: hammer.synthesis.nop # Modified by: test-config.yml
Example with the files test-config.yml
and test-config2.yml
, respectively:
synthesis.inputs: input_files: ["foo", "bar"] top_module: "z1top.xdc" vlsi: core: technology: "hammer.technology.nop" synthesis_tool: "hammer.synthesis.nop"par.inputs: input_files: ["foo", "bar"] top_module: "z1top.xdc" vlsi: core: technology: "${foo.subst}" par_tool: "hammer.par.nop" foo.subst: "hammer.technology.nop2"
test/syn-rundir/par-output-history.yml
after executing the command hammer-vlsi --dump-history -p test-config.yml -p test-config2.yml --obj_dir test syn-par
:
foo.subst: hammer.technology.nop2 # Modified by: test-config2.yml par.inputs.input_files: # Modified by: test-config2.yml - foo - bar par.inputs.top_module: z1top.xdc # Modified by: test-config2.yml synthesis.inputs.input_files: # Modified by: test-config.yml - foo - bar synthesis.inputs.top_module: z1top.xdc # Modified by: test-config.yml vlsi.core.technology: hammer.technology.nop2 # Modified by: test-config.yml, test-config2.yml vlsi.core.synthesis_tool: hammer.synthesis.nop # Modified by: test-config.yml vlsi.core.par_tool: hammer.par.nop # Modified by: test-config2.yml
5.1.10. Key Description Lookup
With the ruamel.yaml
package, Hammer can execute the info
action, allowing users to look up the description of most keys.
The comments must be structured like so in order to be read properly:
foo: bar # this is a comment # this is another comment for the key "foo"
Hammer will take the descriptions from any defaults.yml
files.
Running hammer-vlsi -p test-config.yml info
(assuming the above configuration is in defaults.yml
):
foo.bar Select from the current level of keys: foo.bar apple price Select from the current level of keys: apple ---------------------------------------- Key: foo.bar.apple Value: banana Description: # type of fruit History: ["defaults.yml"] ---------------------------------------- Continue querying keys? [y/n]: y foo.bar Select from the current level of keys: foo.bar apple price Select from the current level of keys: price ---------------------------------------- Key: foo.bar.price Value: 2 Description: # price of fruit History: ["defaults.yml"] ----------------------------------------
Keys are queried post-resolution of all meta actions, so their values correspond to the project configuration after other actions like syn
or par
.
5.1.11. Reference
For a more comprehensive view, please consult the hammer.config
API documentation in its implementation here:
https://github.com/ucb-bar/hammer/blob/master/hammer/config/config_src.py
https://github.com/ucb-bar/hammer/blob/master/tests/test_config.py
In config_src.py
, most supported meta actions are contained in the directives
list of the get_meta_directives
method.