Procedural Code Generation in Depth

Kratos allows you to construct complex expressions and statements programmatically. As hinted in Generator of Generators, the python free-style front-end uses the procedural code generation to support the Python-style codes.

Expressions

You can construct expression the same way you expect in Python or C++. You can reuse old expressions by adding more operators to them.

Expression Stacking

Some assignments may need complex expressions that span multiple variables. This is very straightforward in kratos since kratos’ expression is just an ordinary Python/C++ object.

For instance, if we want to have a reduce summation on a list variables, you can do this in Python

vars = []
for i in range(num_vars):
    vars.append(self.var("variable_{0}".format(i), width))

sum = vars[0]
for i in range(1, num_vars):
    sum = sum + vars[i]

output = self.var("output", width)
self.add_stmt(output.assign(sum))

In this example we create a list of variables that stored in vars with width. Then we use sum to create the sum reduction. In the end, we create a output variable and assign sum to the output using assign() function call. Notice that we add this assign statement into the top level (using self). We will cover more details about the assign statement below. The generated verilog looks like below for this example code block when width = 16 and num_vars = 4.

logic  [15:0] variable_0;
logic  [15:0] variable_1;
logic  [15:0] variable_2;
logic  [15:0] variable_3;
logic  [15:0] output;
assign output = ((variable_0 + variable_1) + variable_2) + variable_3;

Statements

Statements are the building blocks of kratos IR. Each statement object has it’s parent block. The parent can either be the generator or another statement. If it is generator, it has to be the following statement types - SequentialCodeBlock - CombinationalCodeBlock - AssignStmt

Note

If a AssignStmt’s parent is the generator, it will be a continuous assignment, i.e., assign var1 = var2. Due to the backend optimization, the assign statement may not be realized into the verilog code. You should use the debugger if you want to debug the optimization and verilog code generation.

To add a statement to the generator, you can use

[gen].add_stmt(stmt)

where [gen] can either be self in the generator or some generator object.

AssignStmt

AssignStmt is the mostly used statements. kratos provides assign function call to handle the directional assignment. As shown in the examples below, to create an AssignStmt, you can call

var_to.assign(var_from)

Where the var_from is assigned to var_to. Notice that kratos also provides finer control over the assignment type by passing additional flag:

var_to.assign(var_from, AssignmentType.Blocking)

This will create a blocking assignment. If you don’t provide the flag, kratos will determine the assignment type at compile time. Since the compiler is checking and validating the assignment type, it’s recommended to just leave it blank and let the compiler figure it out, unless you have some practical reasons.

SequentialCodeBlock

Kratos’s Python interface provides an easy way to create sequential code block. Similar to verilog, the sequential block needs a sensitivity list. The sensitivity list is constructed the same way as in the @always block. It’s in the format of List[Tuple[BlockEdgeType, Var]], i.e. a list of tuples. You can either use BlockEdgeType.Posedge or BlockEdgeType.Negedge for the first entry. The second entry is the variable/port defined in the generator. You can obtained a sequential block by calling sequential from the generator instance. For instance:

clk = self.clock("clk")
seq_block = self.sequential((posedge, clk))

This will produce the following verilog code:

input logic  clk
// ...
always @(posedge clk) begin
end

To add more statements to it, you can construct other types of statements and then call add_stmt(new_stmt), such as seq_block.add_stmt(new_stmt).

Note

  • By calling [gen].sequential(), we implicitly add the sequential block into the generator. As a result, you don’t have to call [gen].add_stmt(seq) to add to the generator.
  • All the assign statement in SequentialCodeBlock will be either converted or checked to make sure they’re all non-blocking assignments.

CombinationalCodeBlock

CombinationalCodeBlock is very similar to SequentialCodeBlock, except that they don’t need a sensitivity list. To construct one, you can call

comb_block = self.combinational()

Kratos will generate the following system verilog code:

always_comb begin
end

You can add statements the same as SequentialCodeBlock by calling add_stmt.

Note

  • By calling [gen].combinational(), we implicitly add the combinational block into the generator. As a result, you don’t have to call [gen].add_stmt(seq) to add to the generator.
  • All the assign statement in CombinationalCodeBlock will be either converted or checked to make sure they’re all blocking assignments.

SwitchStmt

Switch statement allows you to construct case blocks in verilog. Similar to C++ or verilog, you need to have a condition (target). In kratos, this should be either an expression or a port/variable. You can add a switch case by using add_switch_case(value, stmts) statement. You can provide None to value to specify the default case.

Here is an example on how can you can construct a switch statement:

var = self.var("value", 16)
var_1 = self.var("value1", 16)
var_2 = self.var("value2", 16)
stmt = SwitchStmt(var)
# you can use a single statement
stmt.case_(1, var_1.assign(1))
stmt.case_(2, var_2.assign(1))
# you can also pass in a list of statements
stmt.case_(None, [var_1.assign(0),
                  var_2.assign(0)])
# remember to add it to a either sequential or combinational code block!
# we re-use the sequential block we created above.
seq_block.add_stmt(stmt)

Here is the generated system verilog:

logic  [15:0] value;
logic  [15:0] value1;
logic  [15:0] value2;

always @(posedge clk) begin
  case (value)
    default: begin
      value1 <= 16'h0;
      value2 <= 16'h0;
    end
    16'h2: begin
      value2 <= 16'h1;
    end
    16'h1: begin
      value1 <= 16'h1;
    end
  endcase
end

IfStmt

Similar to the SwitchStmt, the IfStmt needs a condition/target as well. An IfStmt has two parts, the then body and else body. You can add statements to either body by calling add_then_stmt and add_else_stmt.

Here is an example on how to construct an IfStmt.

var = self.var("var", 1)
value = self.var("value", 1)

if_ = IfStmt(var == 0)
if_.then_(value.assign(1))
if_.else_(value.assign(0))

# remember to add it to a either sequential or combinational code block!
# we re-use the combinational block we created above.
comb_block.add_stmt(if_stmt)

Notice that by design choice kratos doesn’t override __eq__ in Python. Unless it’s changed you have to use eq function call.

Here is the generated verilog:

logic   value;
logic   var;
always_comb begin
  if (var == 1'h0) begin
    value = 1'h1;
  end
  else begin
    value = 1'h0;
  end
end

Note

If you have nested IfStmt, in some cases the compiler may do some optimization to optimize them away. Please refer to the passes to see more details.

Syntax sugars

Kratos provides some syntax sugar to make statement creations less verbose. You can call if_ and switch_ to create statements in a functional manner. Notice that if you call if_ and switch_ directly from combinational and sequential, the statements will be added to the always block automatically.

For assignments, you can use functional call the assign the values, such as var_to(var_src). Here are some examples on the syntax sugars:

mod = Generator("mod")
out_ = mod.var("out", 1)
in_ = mod.input("in", 1)
comb = mod.combinational()
comb.if_(in_ == 1).then_(out_(0)).else_(out_(1))

This gives us

module mod (
  input logic  in
);

logic   out;
always_comb begin
  if (in == 1'h1) begin
    out = 1'h0;
  end
  else out = 1'h1;
end
endmodule   // mod

Here is an example for sequential block

mod = Generator("mod")
out_ = mod.var("out", 1)
in_ = mod.input("in", 1)
clk = mod.clock("clk")
comb = mod.sequential((posedge, clk))
comb.switch_(in_).case_(1, out_(1)).case_(0, out_(0))

This gives us

module mod (
  input logic  clk,
  input logic  in
);

logic   out;

always_ff @(posedge clk) begin
  unique case (in)
    1'h0: out <= 1'h0;
    1'h1: out <= 1'h1;
  endcase
end
endmodule   // mod

Comments

Almost every statement in kratos have an attribute called comment, you can set it any comment you want. It will be generated as a comment above the statement. For instance:

stmt = a.assign(b)
self.add_stmt(stmt)
stmt.comment = "this is a comment"
a.comment = "this is another comment"

When you’re using always_comb or always_ff function, you don’t have access to the stmt object. In this case, you can use comment() function. For instance,

def code(self):
    comment("Another comment")
    self._out3 = self._in