2 Control Dependence Graph Generation
3 Loop Duration Hint
4 Host Timing Analysis
5 Adding Guest Code to the Procedure
6 Specifying Timing Requirements for Basic Blocks
7 Determining Guest Code Idle Time
8 Discrete Padding to Schedule Guest Instructions
9 Discrete Padding to Eliminate Jitter
We will use the file tut.s for this tutorial.
main: push r1
ldi r20,100 ldi ZL, lo(foo) ldi ZH, hi(foo)
loop1: st r20, Z+ dec r20 brne loop1 pop r4 pop r3 pop r2 pop r1 ret
Run it through Thrint (thrint tut.s), and we get this CDG, which is generated by default.
Thrint’s parser will complain if it doesn’t like the input. The Thrint FAQ and Hints page has a section on Parser Troubleshooting which is useful in this case.
Assuming that the input file was parsed correctly, we would also like to see the assembly code that Thrint reconstructs based on its internal CDG representation of our code. Use the –S switch to turn out assembler source code output.
$ thrint –S tut.s
Original duration -1
Final duration -1 -1
Program terminated normally
Examining the output file tut.int.s shows us that thrint has reorganized some directives, but otherwise the program is the same:
If we want to see instructions in the CDG we need to specify the i suboption for graph generation: switch –Gi.
$ thrint –S –Gi tut.s
Notice that the duration of the procedure may be undefined (X and -1 indicate this) since the loop count for loop1 is unknown. (if the duration isn't undefined, this means that the data-flow and loop iteration count analyses were successful, and you can skip over this section). This also appears in the CDG. We can provide a hint in the iterations directives file called tut.id. The Procedure and End lines are legacy requirements and must be included.
PROCEDURE main INTO main ENDS
HINT LOOP loop1 100 ITERATIONS
Thrint now provides an estimate of execution times:
Original duration 522 522
Final duration 522 522
Program terminated normally
The CDG also now contains a duration for the loop and start times for the node(s) following it.
Now let’s examine the execution time characteristics of this procedure. Thrint will generate a list of deterministic timing segments if requested with the –Th switch (analyze timing for host procedure). This information is contained in two data files: tut.main.s.dat and tut.main.j.dat. The first file (the segment file) describes the nodes in each segment, and how much cumulative jitter exists when that node begins (what the difference between the earliest and the latest start times could be). Loops are unrolled.
# main to loop1_0
# CODE main
The second (the jitter file) describes how much timing jitter that node incurs. Again, loops are unrolled.
These files are formatted as input files for gnuplot. Thrint creates two command files for gnuplot (tut.s.gpc and tut.j.gpc) which will create graphs when run through gnuplot.
plot "tut.main.s.dat" with linespoints
plot "tut.main.j.dat" with steps
If you have gnuplot installed, you can run it….
Now let's add some real-time instructions to our code. We need to add some guest code to our simple procedure and then specify that they are guest nodes, and when they need to execute. For example, let's send r22 out through PortB at certain times. First, insert the guest source code before the ret:
ldi r22, 55
ldi r17, 9
add r22, r17
out PORTB, r22
out PORTB, r22
out PORTB, 3
Next we need to modify the integration directives file. We add processor clock speed information (10 MHz) and a default tolerance for timing errors (0 cycles) as global parameters. The clock frequency isn’t essential at this point, but it will be useful if we want to define integration targets in time (e.g. microseconds) rather than cycles. Multiple tolerance directives are supported, the most recent one defines the tolerance for successive BLOCK directives.
We then add timing targets for each of the guest basic blocks; these times are referenced to the start of the procedure’s execution. We specify the real-time instructions should start well after the loop, beginning at cycle 603.
TOLERANCE 0 CY CLOCK_FREQUENCY 10 MHz PROCEDURE main INTO main ENDS HINT LOOP loop1 100 ITERATIONS BLOCK Guest0 AT 603 CY BLOCK Guest1 AT 606 CY BLOCK Guest2 AT 610 CY BLOCK Guest3 AT 614 CY BLOCK Guest4 AT 917 CY END
$ Thrint –Gi tut.s
We can now examine the CDG created by Thrint for the input procedure; the new code is added and highlighted in yellow. This shows how Thrint is representing the program internally.
Next we examine the idle time between guest nodes now that the guest code has been added and sub-thread timing requirements have been specified. Use the –Tg switch to add guest timing analysis to Thrint’s tasks. -hv may also be needed to disable timing verification (we aren't scheduling codeyet ).
$ thrint –Gi –Tg tut.s
Analyzing Guest Idle Time
Created idle time analysis files:tut.main.i.gpc and tut.main.i.dat
Created idle time analysis files:latest_proc.i.gpc and latest_proc.i.dat
Guest Idle Time: CIdleNodeList(size=2): [2*1, 509*1]
Thrint reports that it has found idle time of two sizes: one which is two cycles long and one which is 509 cycles long.
Thrint processes this information into a histogram to simplify the analysis of this data. Two files are created: tut.main.i.dat holds idle time histogram data and tut.main.i.gpc is a command file for gnuplot. The first looks like this.
The first column holds the start (lower edge) of the cycle bin, while the second column holds the amount of idle time with that granularity. We can again use gnuplot and the .gpc file to plot this information, or excel if you prefer.
We can now tell Thrint to schedule the real-time instructions to ensure they begin at the right times. Thrint inserts nops or loops of nops to do this. We must change the id file to indicate discrete padding with nops is needed.
PROCEDURE main DISCRETE_PADDED
Then we can run thrint on the file to pad it with nops.
$ thrint -i -Gi tut.s
Thrint indicates its progress with the following text:
Padding dedicated guest function with nops
Padding procedure main as dedicated guest.
Done with padding away jitter in discrete guest.
Done with padding discrete guest for sub-thread scheduling.
Re-Analyzing Timing Statically
Verifying Integrated Guest Timing
The following CDG is created.
(Note that if we do not use a -G option (such as -Gi) to generate a CDG, the
vcg file created shows the CDG before padding and leaves out the code we're
An output assembly file tut.int.s is also created. It shows how padding instructions have been added. These come in two varieties: padding nops (e.g. at Guest1p and Guest2p) and padding nop loops (e.g. at Guest4p_*).
Guest0: ldi r22,55 ldi r17,9 Guest1p: nop Guest1: add r22,r17 Guest2p: nop nop nop
Guest2: out PORTB,r22 Guest25: dec r22 Guest3p: nop nop Guest3: out PORTB,r22 dec r22 Guest4p_pli:
push r16 ldi r16,99 Guest4p_plb: dec r16 brne Guest4p_plb Guest4p_plf: pop r16 Guest4: out PORTB,3 ProcExit: ret
Thrint can also pad away timing jitter in code without scheduling any instructions to execute at specific times. The following example, using pad_test.s and pad_test.id, eliminates jitter before and within a loop.
$ thrint -Gi pad_test.s ... Final duration 1210 1614 Program terminated normally
Here we see that there are 414 cycles of timing jitter for the function.
We use the ID file and the command line options to indicate we wish to pad away that timing jitter. The ID file only requires the DISCRETE_PADDED keyword.
PROCEDURE main DISCRETE_PADDED END
On the command line we specify we want assembly code generated (-S), code
transformations specified in the ID file performed (-i) and a CDG with instruction
information created (-Gi).
$ thrint -S -i -Gi pad_test.s
CDG of padded code
The final duration of the code is now constant. Here is the padded code pad_test.int.s.
We are now ready for integration, which we specify with the –i option. Thrint creates a file with its integration plan for each segment to be integrated in a procedure. For example, main.int.log holds the integration plan for our example.
Stay tuned for the next episode!