Chapter 3 - Coding for Synthesis

Contents

Introduction
Coding Rules
Doing the Lab

Introduction

This tutorial introduces coding considerations for synthesis - essentially how to code to minimize chip area or to maximize operation throughput (minimize latency), or how to strike a balance between these extremes. It also introduces you to methods of setting compiler constraints to alter the synthesis result of a given design.

Coding Rules

There are many factors that influence the coding style used for any particular design. Some of these might be:

For a thorough discussion of these coding issues, see Chapter 13 in "Understanding Behavioral Synthesis" by John Elliott.

Coding for behavioral compilation is often subtly different from common practices of Verilog or VHDL coding. In most cases the differences are actually adherence to behavioral coding rules that are good practice design rules that are less stringent for non-behavioral coding. The behavioral coding rules are:

Basic Rules

Reset Rules

Rules for Conditionals

Loop Coding Rules - General

Cycle-Fixed Coding Rules - Basic

Cycle-Fixed Coding Rules - Conditional Loops

Superstate-Fixed Coding Rules - Loops

Rules About the Behavior of I/O and Variables

In the above rules we mentioned rolled and unrolled loops, and we mentioned cycle-fixed and superstate modes. Following is a brief discussion of each issues.

Rolled Versus Unrolled Loops

A rolled loop is one in which no iteration of the loop can start before the previous one has finished. An unrolled loop is essentially a series of assignments that can happen in parallel. The illustration below shows how a loop is unrolled and how a constant is handled. Because the only iterated item used in the original loop is the loop index, and the boundaries are known, they can be pre-calculated so each value is propagated into the series of statements.

On the other hand, the implication of this for scheduling can be profound. Take for example the following loop:

for (i=0; i<=31; i=i+1) begin : outer_loop
  Out1[i] = a0 * a1 + a2 * a3 - a4 + a5 + a6 * a7; 
  for (j=0; j<=15; j=j+1) begin : inner_loop
    Out2[j] = b0*b1+b2*b3-b4+b5+b6*b7-b8+b9; 
  end
end
This loop has 9 operations (multiplies, adds, subtracts) contained within an inner loop of 16 iterations contained within an outer loop of 32 iterations that itself contains 7 operations. If the loop is unrolled it results in 4832 events that need to be individually scheduled. This will result in a huge amount of computation for the schedule operation, which means a long schedule run. If we keep both loops rolled the result is only 16 events to schedule.

So, why would we ever want to unroll a loop? Basically it is a throughput (latency) versus area issue. A rolled loop requires the smallest amount of real estate, but it costs the number of clocks used in the loop multiplied by the number of iterations of the loop to execute the loop. An unrolled loop schedules individual hardware for each loop iteration, but it executes the loop only once with the number of clock cycles of the operations contained within the loop.

Rules (suggestions) for unrolling or not unrolling

Cycle-Fixed Versus Superstate-Fixed Modes

Cycle-Fixed mode is relatively straight forward to understand. You specify the clocks you want following the rules described earlier in this tutorial. Then you see if the design can be scheduled into real hardware.

Superstate fixed mode "changes the rules". While you write your design to follow a behavior, you may somewhat ignore the actual timing an adder, multiplier, or other function actually take (in terms of hardware clock cycles). Scheduling figures out how many clock cycles are actually required for a particular technology or architecture, and Behavioral Compiler "plugs in" those clock cycles.

This does not mean you can totally ignore clocks. For example: entering, exiting, and iterating in a loop takes a state transition in the FSM generated. Hence:

The benefit of this is that it gives you far more freedom to explore different technologies or architectures. A possible disadvantage is that because of the clock cycles superstate-fixed mode inserts, the RTL or gate level implementation may simulate differently from the simulation of the behavioral level. This can be overcome with proper testbench design.

Doing the Lab

In the interest of not spending too much time waiting for elaboration and scheduling, we will use a simpler design to illustrate coding for behavioral compilation. Change your working directory to coding.

unix% cd coding

unix% bc_shell | tee Log/coding.log

bc_shell > analyze -s -f verilog thresh.v

bc_shell > report_design_lib work

bc_shell > elaborate -s thresh

bc_shell > create_clock clk -p 15

bc_shell > bc_check_design -io cycle

At this point, you will get an error if the design cannot be scheduled.

Use your favorite ASCII editor to view the file thresh.v which you will find in the Verilog directory. Compare the thresh.v file you analyzed and elaborated to the file below. Edit thresh.v and save it as thresh1.v .

//  This is to be scheduled in cycle fixed scheduling mode.
//  2.  clock period = 15 ns
`define ck begin @(posedge clk); if (reset) disable reset_loop; end
module thresh(clk, reset, in1, in2, threshold, out1);
  input clk, reset;
  input [7:0] in1;
  input [7:0] in2;
  input [15:0] threshold;
  output out1;
  reg out1;
  always begin : reset_loop
    out1 <= 0;
    `ck
    forever begin : alg_loop
      while (in1 * in2 > threshold) begin: loop1
        `ck
        `ck
        out1 <= 1;
      end 	// while
      `ck
      `ck
      out1 <= 0;
      `ck
    end // alg_loop
  end // reset_loop
endmodule
The change needed is to add a clock in the while statement and after the while statement's end.

Now that you have corrected and saved your design, clear the design from the Behavioral Compiler memory and run through the steps with the new design version.

bc_shell > remove_design -design

bc_shell > analyze -s -f verilog thresh1.v

bc_shell > report_design_lib work

bc_shell > elaborate -s thresh

bc_shell > create_clock clk -p 15

bc_shell > bc_check_design -io cycle

If you completed the edit correctly, you will not get an error. If you do get an error, go back and make the necessary corrections and repeat these steps before proceeding with timing the design.

bc_shell > bc_time_design

bc_shell > report_resource_estimates > Reports/estim.rpt

bc_shell > write -h -o DB/thresh_timed.db

Examine the estim.rpt file and note the following:

What is the delay through the multiplier (*)? _____________

What is the chain delay through the multiplier (*)? ________________

What is the word level delay through the Comparator (>)? _________

This information will be helpful in understanding the following steps.

bc_shell > schedule -io cycle -effort medium

bc_shell > report_schedule > Reports/thresh_15ns.rpt

bc_shell > write -h -o DB/thresh_15ns_sch.db

bc_shell > bc_view

Observe the estimated area and latency of the design.

This tutorial section about coding styles stops at this point. If you are interested, you can go through these steps trying other clock periods such as 10ns to see the difference. You could also re-run the lab this time using superstate mode for the I/O mode. If you try further experiments, remember to save the scheduled db with a representative name such as thresh_10ns_sch.db and save your reports with similar names. Then you can do comparisons between the different runs.

This tutorial section briefly described the issues related to loops, conditionals, resets, cycle-fixed versus superstate mode, and other considerations of coding for behavioral compilation. It showed you the power of behavioral compilation over coding for targeted architectures and/or technologies. The Behavioral Compiler class covers these issues in detail.

On to Chapter 4 - Simulation