The Abacus simulator has a couple of parameters to specify the pipeline and the memory architecture that is considered for the simulation. This page explains these parameters and the output of the simulator. For a list of detailed instructions of the Abacus processor, have a look at the Abacus reference card.
#const SingleCycle no // whether everything is done in one stage
#const PipeFetch 1 // pipeline stage where next program counter is set #const PipeDecode 2 // pipeline stage where operand registers are read #const PipeExecute 3 // pipeline stage where ALU results are produced #const PipeMemAcs 4 // pipeline stage where Load results are produced #const PipeRegWrite 5 // pipeline stage where registers are written
#const RegBypass yes // whether register reads yield current writes
#const Forwarding yes // whether forwarding from PipeExecute and PipeMemAcs
#const BranchInDecode yes // immediately write new PC in PipeDecode for Branches
For unconditional branches, i.e., jump instructions, we always assume that these are completely executed in PipeDecode so that no nop operations are required after jump instructions. This is convenient since we can then use j 1 as an abbrevation for nop.
// ----------------------------------------------------------------------------- // parameters for single-cycle (instruction set) simulation // ----------------------------------------------------------------------------- #const SingleCycle yes // whether everything is done in one stage
// --------------------------------------------------------------------------------- // parameters for classic five-stage pipeline with forwarding and register bypassing // --------------------------------------------------------------------------------- #const SingleCycle no // whether everything is done in one stage #const PipeFetch 1 // pipeline stage where next program counter is set #const PipeDecode 2 // pipeline stage where operand registers are read #const PipeExecute 3 // pipeline stage where ALU results are produced #const PipeMemAcs 4 // pipeline stage where Load results are produced #const PipeRegWrite 5 // pipeline stage where registers are written #const RegBypass yes // whether register reads yield current writes #const Forwarding yes // whether forwarding from PipeExecute and PipeMemAcs #const BranchInDecode no // immediately write new PC in PipeDecode for Branches
You can also specify longer pipelines where the execution or the memory access may take more than one cycle. What matters for these pipelines are still the above five values. For instance, the following settings define a pipeline with 14 stages where four cycles are needed for execution and seven for memory access:
// --------------------------------------------------------------------------------- // parameters for a pipeline with 14 stages using forwarding and register bypassing // --------------------------------------------------------------------------------- #const SingleCycle no // whether everything is done in one stage #const PipeFetch 1 // pipeline stage where next program counter is set #const PipeDecode 2 // pipeline stage where operand registers are read #const PipeExecute 6 // pipeline stage where ALU results are produced #const PipeMemAcs 13 // pipeline stage where Load results are produced #const PipeRegWrite 14 // pipeline stage where registers are written #const RegBypass yes // whether register reads yield current writes #const Forwarding yes // whether forwarding from PipeExecute and PipeMemAcs #const BranchInDecode no // immediately write new PC in PipeDecode for Branches
The simulator will print the state of the computer system after execution of every instruction. Such a state of the computer system might look as follows:
---------------------------------------------------------------- step 67 at instruction 11 : bnz $5,-6 ---------------------------------------------------------------- mm : 05:04:03:02:01:00 bk[st] : v d tg 05:04:03:02:01:00 i : Reg[i] 0 : 00.05.00.03.00.01 0[ 0] : 1 1 4 00.0E.00.0C.00.0A 0 : 00.00 1 : 00.00.00.00.00.00 1[ 0] : 1 1 1 00.11.00.0F.00.0D 1 : 00.14 2 : 00.00.00.00.00.00 2[ 1] : 1 1 0 00.0B.00.09.00.07 2 : 00.09 3 : 00.00.00.00.00.00 3[ 1] : 1 1 4 00.00.00.12.00.10 3 : 00.13 4 : 00.00.00.00.00.00 4 : 00.14 5 : 00.00.00.00.00.00 5 : FF.FF 6 : 00.02.00.00.00.00 6 : 00.00 7 : 00.08.00.06.00.04 7 : 00.00 8 : 00.00.00.00.00.00 9 : 00.00.00.00.00.00 10 : 00.00.00.00.00.00 11 : 00.00.00.00.00.00 12 : 00.00.00.00.00.00 13 : 00.00.00.00.00.00 14 : 00.00.00.00.00.00 15 : 00.00.00.00.00.00
The three columns correspond with the main memory, the data cache and the register file. The content of the main memory is printed blockwise, so the leftmost column lists the block addresses. The words inside these blocks have addresses that can be calculated by the block address and the position inside the block which is listed in the title of that table. For example, in the above case there is a data word 00.06 in block 7 at byte addresses 03 and 02, so the memory address of that word is 7 * 3 + 1 (since there are three words in a block and that is the second on in the block).
The second column shows the content of the data cache. It is also organized in blocks since memory transactions between the main memory and the data cache always refer to blocks. In the above example, there are four blocks and the block addresses in the data cache are shown in the column denoted with bk. The number given in square brackets right to it denotes the set number. The blocks of a set can be rearranged to any place inside the same set, but not in other sets. The next two bits v and d denote whether the cache block is valid (v) and whether it is dirty (d). Initially, all blocks are invalid and become valid after loading a block from memory to the cache, and they become invalid after writing back the block. A block becomes dirty if it is written by a store instruction and has not yet been written back to the main memory. The column with title tg denotes the tag of the block address so that one can recompute the memory block address of the block in the cache. For example, the first block at cache block address 0 is in the set st=0 and has tag tg=4, so that it refers to the memory block at address 2 * tg + st = 8 since there are 2 sets in the cache. The final rightmost column is the content of the block.
The rightmost column is simply the content of the register file. Abacus has eight registers with a bitwidth of DataWidth that are printed in that table. As can be seen, a register content has two bytes, hence DataWith was 16 in the above example, BlockSize was 6 bytes, MemSize was 16*6=96 bytes, CacheSize was 4 * 6 = 24 bytes, and SetAssoc=2.