Skip to content

chipsalliance/cde

Repository files navigation

CDE

A Scala library for Context-Dependent Environments, where a key-value environment is passed down a module hierarchy and each returned value depends on the key and the query’s origin. CDE is provably superior to existing parameterization schemes because it avoids introducing non-local source code changes when a design is modified, while also enabling features for large-scale design space exploration of compositions of generators.

User Guide

The library presents its key-value storage under an abstract class Parameters. Values stored in Parameters are each associated with a case object extends Field[T].

The main interface for user to create a Parameters object is using Config object. Its apply method takes (View, View, View) => PartialFunction[Any, Any] as a lookup table.

//Field MyKey1 contains value of type Int
case object MyKey1 extends Field[Int]
//Field MyKey2 contains value of type String, with default value "None"
case object MyKey2 extends Field[String]("None")

// The meaning of parameter (site, here, up) will be explained later
val p: Parameters = Config((site, here, up) => {
  case MyKey1 => 0
  case MyKey2 => "MyValue"
})

// Apply Paramaters object to Field to query
assert(p(MyKey1) == 0)

Parameter Overrides

We can use one Parameters to override another. Each single Config is like a row in a table, while each Field is a column in the table. To concat two table together, we have alter and orElse methods. alter puts the rhs at bottom of the table and orElse puts the rhs at top of the table.

A query will inspect the table from bottom to up, row by row, until it finds the first row having the key defined.

For example: Config1.alter(Config2).alter(Config3) yields

Key1 Key2 Key3 ...
Config1 V1
Config2 V2 V3
Config3 V4

And now p(Key1) == V2, p(Key2) == V3 and p(Key3) == V4.

The same Parameters can also be defined by Config3.orElse(Config2).orElse(Config1). There is also deprecated shorthand ++ for orElse, so Config3 ++ Config2 ++ Config1 is also valid.

Environment Reference

Each query contains the entire environment of where the query originates. This is pass to the lookup table of each Config by (site, here, up) arguments.

  • here dynamically refers to the current row of the table
  • up dynamically refers to the rows appearing up than the current row
  • site dynamically refers to the entire table. When it gets called by here or up queries, it still refers to the entire table instead of current row or upper half as indicated by here or up respectively.

For example, in the following Parameters

Key1 Key2 Key3 Key4
Config1 1 site(Key1)
Config2 2 here(Key1) up(Key2)
Config3 3

The value for each key is

  • Key1: 3, given by Config3
  • Key2: 3, as it is value of Key1 in the entire table
  • Key3: 2, as it is value of Key1 defined in the current row
  • Key4: 3, as it is value of Key2 defined in the upper row, which is in turn value of Key1 defined in the table, which should be 3 as overriden by Config3.

If one config layer does not refer environment at all, alterMap and alterPartial can be used to avoid create a redundant Config object, as they accept Map and PartialFunction as their parameter.

Implementation Details

This section discusses the internal data structure used to track environment information. Normal reader can skip the section.

alter and orElse function wraps two operand as ChainParameters, which forms a binary tree when alter are called multiple times. Query on ChainParameters calls chain method.

The chain method traverses the tree by wrapping the right child in ChainView, which records up for later reference, and invoking chain on the left child. This happens recursively until a leaf node is found. Then if the requested key is not in the node, we can turn to chain of currently up node.

The following figure illustrates a querying process of Parameters constructed by C1.alter(C2).alter(C3).orElse(C4). chain is invoked on the node in blue, and ChainView generated is in red.

Query Example