CSE141L Final: Processor Benchmarking
CSE 141L Homepage | Lab Overview | Lab
Description
| Deadline: |
- Progress Report: Sun, May 28th at 11:59PM
|
| Changes: |
- None.
|
Lab Overview:
Congratulations! Now that you have a correctly working processor, we are almost at the end of our development cycle.
Since we want to be able to tout the excellent performance of our processors in our upcoming press release, you are
going to need to perform a few more benchmarks to gauge your processor's speed. If you are unsatisfied with your results,
you may wish to perform various optimizations in order to improve your performance. If you are feeling up to the challenge,
you may even join the multi-core revolution and make your processor a dual-, quad-, or even higher core system! Good luck.
Lab Deliverables:
In this lab you will be demonstrating the final result of your processor implementation. Your main focus will be on
benchmarking your processor.
- Demonstration: A live demonstration of your processor will be required for all groups.
- Progress Report: Your progress report will consist mainly of the answers to the questions posed in this
lab. Your report should also contain copies of the final datapath for both your front-end (fetch) and back-end
(execution). In addition, you will need to include all the the source files for the final version of your project.
This includes all your Verilog source files, assembly code files (*.s, *.coe), testbenches, and Megawizard generated
files. You should also include an updated copy of your ISA manual (if you didn't implement certain instructions,
make sure they do not show up in your manual). Your final grade will ultimately be determined by whether you have
a working processor so it goes without saying that the files you submit must be synthesizable and implementable and
produce the correct output on all the benchmark programs.
You will need to turn in an electronic copy of your progress reporti via moodle by the deadline. The answers to the
questions should be in a PDF file. The Verilog files (as well as your PDF) should be included in a ZIP file with the following
name convention: cse141L-final-lastname1-firstname1-AND-lastname2-firstname2.zip.
Lab Description:
Jump to: Performance Benchmarking | Uniqueness of Design |
Processor Optimization (Optional) | Multi-core Implementation (Optional)
Performance Benchmarking
For the official performance evaluation, you will be using the following benchmarks:
- fib_native: Your native implementation of Fibonacci (i.e. written in your ISA). It must be able to handle
arbitrary inputs such as: -1, 2, 9, and 31.
- fib_sg: The SuperGarbage implementation of Fibonacci (i.e. run through your VirtualMachine). This also must
be able to handle arbitrary inputs.
- sort_sg: The SuperGarbage implementation of Sort (i.e. app2). It is enough to correctly process the given
app2.in input data.
| Q1 |
What is the maximum operating frequency of your processor? For the 3 benchmarks, fill out the following table for the performance
of your processor. The input to fib_native and fib_sg should both be 12 (and the result should be 144) while sort_sg
should use the app2.in input file from lab 3, part B.
| Frequency: |
fib_sg |
fib_native |
sort_sg |
| # of Cycles for the Execution |
|
|
|
| Dynamic Instruction Count |
|
|
|
| Cycles Per Instruction (CPI) |
|
|
|
| Execution Time |
|
|
|
| Tested and Working in Timing Simulation (testing with smaller input ok)? |
|
|
|
|
| Q2 |
Briefly describe your methodology for measuring the number of cycles for execution and the dynamic instruction count
that you reported in Q1. |
| Q3 |
For the fib_sg and fib_native benchmarks, grab a screenshot showing the final result of the calculations. Again,
the input should be 12 which should produce a result of 144. |
| Q4 |
Fill out the following table on resource usage of your design. If there are any other resources we missed
here, include them.
| Resource Type |
Used |
Available |
Utilization |
| Logic Cells |
|
|
|
| Registers |
|
|
|
| I/O Pins |
|
|
|
| Total RAM Block Bits |
|
|
|
| DSP Block 9-bit elements |
|
|
|
|
| Q5 |
What is the critical path in your processor implementation? Draw it on a copy of your finalized datapath
schematic. |
Uniqueness of Design
Please answer the following question about the unique aspects of your implementation. This will help
us in determining special awards that will be given out based on your designs.
| Q6 |
Please describe any feature(s) of your processor or implementation that would be considered novel,
cool, unusual and/or different than the "average" 141L project. For example, any branch
prediction/BTB scheme, pipelining tricks, shadow bits, interesting instructions, use of special purpose
registers, interesting addressing modes, banking, I/O devices, implementation of multicore, etc. |
All work beyond this point is optional.
Optimize Your Processor (Optional)
As you will recall from CSE141, execution time is a function of 3 factors:
- Cycle Time
- CPI
- (Dynamic) Instruction Count
Therefore, if you wish to improve the performance of your processor, you can do so by improving one of these
three things. Remember that your grade depends mostly on you having a correctly working processor. Before you start
optimizing your processor, be sure to save all the files for your correctly operating processor. Also, as you may take
several stages of optimization, make sure you save your files after each stage.
Cycle Time
The cycle time of your processor not only depends on your design, but also on how Altra turns your design into hardware.
Since the quality of the output of the Altra tools depends on the optimization parameters you give them, you can try
different options to get better results. The easiest thing to do (and the one you should try first) is to set aggressive timing
constraints to be met. Setting tougher constraints will force Altra to work longer and not give up on a suboptimal design.
While this option of improving cycle time does not require modifications to your processor, it does come at a cost: computation
time.
There may be other options available in Altra tools (such as "Timing Advisor") to explore. You may want to speak with your
classmates to find out about anecdotal experiences with various parameters.
As you reach diminishing returns from tweaking Quartus II synthesis and implementation parameters, the next step is to try to
improve the cycle time of your processor by modifying the design itself (i.e. your Verilog implementation). Remember that it is
the critical path that ultimately determines the operating frequency. Your efforts should therefore be aimed at reducing the
critical path using such techniques as MUX reordering, moving logic across pipeline stages, or introducing more pipelining.
By examining the timing reports and other Quartus II outputs, you can determine the parts of your design (e.g., the critical path)
that you may want to focus on optimizing. You often can rewrite the code to reduce critical paths (e.g., reordering muxes, or moving
logic across pipeline stages, or pipelining more.) Again, before you start changing your design, make sure to save your
work!
Cycles Per Instruction (CPI)
While the opportunities of significantly reducing your CPI are limited, there are still several techniques you might try.
One area of possible optimization is in branch prediction. The P-bit of your ISA is a static prediction so it may not always
be very accurate. Adding an advanced dynamic branch predictor to your front end may yield much better prediction results.
Loads and stores are another source of optimization. Stores can be optimized to take only a single cycle by not waiting for
the store to go to memory before beginning execution of the next instruction. Loads can also be pipelined. Oh, and did we
mention that you should save your work often?
Dynamic Instruction Count
The final area of optimization you might explore is reducing the instruction count. Improvements in this area will come from
you improving your skills as a compiler and crafting better assembly code. Some of the techniques discussed in CSE141, such
as loop unrolling and loop fusion, may yield better assembly code. You may also want to consider changing the way you handle
load of immediates. By storing them in memory and using a basic "load" instruction, you may be able to significantly reduce
the number of instructions in your programs. One thing to keep in mind when doing assembly optimizations is that you should
first think about the performance impact of your change. Reducing your assembly code length by 2 instructions may not be worth
the time and effort spend meticulously hand-optimizing the code.
Also, you should note that in the course of your optimization, you should not change the basic algorithms being used for
the benchmarks. For example, there are much better ways of calculating fibonacci numbers but your algorithm still needs to be
recursive and do the same number of comparisons as the original version. Also, you should not try to optimize the SuperGarbage
binaries although it is acceptable to optimize VirtualMachine. Finally, don't forget to make sure that your "optimizations"
don't change the end result of the algorithm (i.e. they should still be correct ;)).
Multi-core Processor (Optional)
Nearly all mainstream processors sold today consist of multiple cores on a single processor. These multi-core designs are a result
of increased number of transistors on a chip and the descreasing returns of optimizing a single core. As you may have noticed
from Q2, you also have some extra hardware resources that you might be able to translate into extra cores. Surprisingly, the
process is not that difficult!
The following figure shows a simple dual-core design.
In this example, there are two slightly modified cores (i.e. execution units). The biggest change in this system is the location
of the data memory. In your single-core design, the data memory resided within the execution unit. However, in a multi-core design
the data memory is located outside of the backend so that it can be shared among multiple execution units.
Sharing of the data memory can lead to contention on the memory among the cores. Since the memory can only handle a single
request at a time, if multiple requests are received simultaneously, there must be some form of arbitration. For the core that
loses out on arbitration, the 'refused' signal is asserted. The simplest arbitration scheme is to always give priority to one of
the cores. Unfortunately, this is an unfair scheme and can lead to poor performance of the lesser priority core is memory-intensive.
To handle this problem, you will likely want to devise a better method of arbitration.
More sophisticated designs may employ banking, where the data memory is actually composed of several smaller memories. The
lower bits of the address indicate which bank an address goes to. In this case, the arbitration logic will only assert the refused
signal if the two cores try to address the same bank.
Multi-core Performance
Adding multiple cores to a processor makes sense only when it leads to an improvement in performance. There are generally two ways
which we can take advantage of multi-core processors: by running multiple applications on them or by parallelizing the
applications. For this lab, we are only interested in the latter. Parallelizing a program involves dividing it into multiple
parts that can be executed simultaneously by multiple cores. In general, parallelizing a program is not a trivial task. Of the
three benchmarks used in this lab, the easiest to parallelize is fib_native. Using a simple modification, you should see almost
linear speedup (i.e. a speed up of N when N cores are used) with fib_native. (Memory contention will likely
lead to your performance be less than this though.)
Provided below is a parallel version of the fibonacci algorithm:
// the following variables are shared between the two processors
int sync = 0;
int val = 0x3DEADBEEF;
int fib_core0(int n)
{
if (n < 5)
return fib_internal(n);
val = n-2; // write value to core 1
sync = 1; // signal to core 1 that value is ready
int temp = fib_internal(n-1); // compute my part of fib
while (sync == 1); // wait for core1 to be ready
return temp + val;
}
int fib_core1(int n)
{
while (1) {
while (sync == 0); // wait for core0 to send value
int temp = fib_internal(val);
val = temp; // write value for core0 to receive
sync = 0; // signal back to core1 that we are done;
}
}
In this code, core0 relegates the task of computing fib(n-2) to core1 while it computes fib(n-1).
After both results are available, core0 adds them to get the final result. You will note that both
'sync' and 'val' are shared variables so communication between cores occurs through these variables.
The VirtualMachine function is much harder to parallelize but if you can do it effectively, we
will give you a gold star for the day!
Multi-core Implementation Details
There are a couple subtle points that need to be addressed before your multi-core implementation will
work. Listed below are a few of the points you will need to take under consideration:
- Having multiple cores running simultaneously means you will most likely have multiple
stacks. These need to be separated in the data memory so that the cores do not interfere with
each other. Another other non-shared (i.e. private) data also needs to be separated.
- In order to supply instruction to multi execution units, you will likely need multiple
fetch units. (You might think up some ingenious way of getting around this by modifying your
fetch unit, so dream big :)). You have two choices of how to initialize the memories for each
fetch unit. One way is to write a single program with all the code and have the cores
start fetching from different places (depending on which code they need to execute).
- You can avoid having to maintain different Verilog for the different cores by having
case statements built-in to determine behavior based on which core it is. To do this,
you need to have some state for each core which maintains their identity (is it core0? core1?
coreN?). You can then use a special I/O port to initialize the identity after resetting
the processor.
This lab created by Donghwan Jeon, 2007. Modified by Sat Garcia, 2008. Modified by Arash Arfaee 2010