Project 6: Cache

Learning Goals

How to use a Cache Simulator to make inference aboout caches with different parameters. In this Project we will develop our own basic Cache Simulator, that will further help you understand cache basics and strengthen your C Programming skills.

 * csim.c:
 * A cache simulator that can replay traces (from Valgrind) and output
 * statistics for the number of hits, misses, and evictions.
 * The replacement policy is LRU.
 * Implementation and assumptions:
 *  1. Each load/store can cause at most 1 cache miss plus a possible eviction.
 *  2. Instruction loads (I) are ignored.
 *  3. Data modify (M) is treated as a load followed by a store to the same
 *  address. Hence, an M operation can result in two cache hits, or a miss and a
 *  hit plus a possible eviction.

CSIM.c: This is the main file that will take a memory trace as an input, simulates the hit/miss/eviciton behavior of cache memory of the trace, and then outputs the total number of hits, misses, and evictions as show below:

hits:4 misses:5 evictions:3

The directory traces contains a collection of memory trace files that we will use to evaluate the correctness of our csim implementation. These memory trace files are generated by a Linux program called Valgrind.

Basic of Caches

Cache Block: The basic unit for Cache storage, may contain multiple bytes/words of data.

Cache Line: Same as a Cache Block. Note that this is not the same thing as a ‘row’ of Cache.

Cache Set: A ‘row” in the Cache. The number of blocks per set is determined by the layout of the Cache (e.g Direct Mapped)

Tag: A unique identifer for a group of data

Valid Bit: A bit of information that indicates whether the data in a block is valid (1) or not (0).

Import Needed Libraries and Global Variables

#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>

/* DO NOT MODIFY THESE VARIABLES **********************************************/

//Globals set by command line args.
int b = 0; //number of block (b) bits
int s = 0; //number of set (s) bits
int E = 0; //number of lines per set

//Globals derived from command line args.
int B; //block size in bytes: B = 2^b
int S; //number of sets: S = 2^s

//Global counters to track cache statistics in access_data().
int hit_cnt = 0;
int miss_cnt = 0;
int evict_cnt = 0;

//Global to control trace output
int verbosity = 0; //print trace if set

Setting Data Types

int lru_counter = 0; // Global Variable to track the next value for lru

//Type mem_addr_t: Use when dealing with addresses or address masks.
typedef unsigned long long int mem_addr_t;

// Type cache_line_t: Use when dealing with Cache lines.

typedef struct cache_line {                    
    char valid;
    mem_addr_t tag;
    int lru; // used for lru tracking
} cache_line_t;

// Type cache_set_t: Use when dealing with cache sets
// Note: Each set is a Pointer to a heap array of one or more cache lines.
typedef cache_line_t* cache_set_t;

// Type cache_t: Use when dealing with the cache.
// Note: A cache is a pointer to a heap array of "one or more sets".
typedef cache_set_t* cache_t;

// Create the cache (i.e., pointer var) we're simulating.
// Cache is a Combination of Sets
cache_t cache;  

Functions to Implement

Allocates the data structure for a Cache with S sets and E lines per set. Then it Initializes all valid bits and tags with 0s.

void init_cache() {

		S = 1; // initiallizes S to 1       

		for(int i = 0; i < s; i++){ // calculates the value of S

				S *= 2;
		cache = (cache_t)malloc(sizeof(cache_set_t)*S); // Allocates Memory for 'S' sets
        // Iterates through all the Sets 'S'
 		for(int i = 0; i < S; i++){ 

				*(cache+i)= (cache_set_t)malloc(sizeof(cache_line_t)*E); // Allocates Memory for E lines in the current set

                // Iterates through all lines in the Current Set

				for(int j = 0; j < E; j++){ 

     				(*(*(cache+i)+j)).valid = 0; // Sets valid bit to 0

      			(*(*(cache+i)+j)).tag = 0; // Sets tag bits to 0
						(*(*(cache+i)+j)).lru = 0; // Sets the lru count of the line to 0

Free Cache Function

Frees all heap allocated memory used by the cache.

void free_cache() {             
        // Iterate backwares through all the Sets
		for(int i = S-1; i >= 0; i--){

            cache[i] = 0;               // Frees the Memory each Set is pointing and Sets it to NULL
  	free(cache); // Frees the Memory Cache
    cache = 0; 

Access Data Function

Simulates data access at a given “address” memory address in the Cache. If already in Cache, increment hit_cnt If not in the Cache, Cache it (set tag), increment miss_cnt If a line is evicted, increment evict_cnt

void access_data(mem_addr_t addr) {      

		// get set and tag
		mem_addr_t tag = addr >> (s+b);
		int set = (addr - (tag << (s+b))) >> b;

		// Hit (Block is in our Cache)
		for(int i = 0; i < E; i++){
				if(cache[set][i].valid && cache[set][i].tag == tag){


						cache[set][i].lru = lru_counter;



		// If not a hit, then it will be a miss!


        // Lets Check the type of Miss (Miss or Miss and Eviction)

		for(int i = 0; i < E; i++){

         if(!cache[set][i].valid){ // if only a miss, then a least one line is not valid

					cache[set][i].valid = 1; // sets validity

				    cache[set][i].lru = lru_counter; // udates counter

				    cache[set][i].tag = tag; // updates tag bit

		// if program reaches here, then an eviction should occur

    int change_line = 0; // the line to be changed

    // find the line to change

    for(int i = 0; i < E; i++){

        if(cache[set][i].lru < cache[set][change_line].lru)

            change_line = i;
		// Update the values in the line to be changed

		cache[set][change_line].lru = lru_counter; // Updates Counter

		cache[set][change_line].tag = tag; // Updates tag


Replay Trace

Replays the given Trace file against the Cache, and reads the input trace file line by line.

Extracts the type of each memory access : L/S/M

Translate each ‘L’ as a load i.e. 1 Memory Access Translate each ‘S’ as a store i.e. 1 Memory Access Translate each ‘M’ as a load followed by a store i.e. 2 Memory Access

void replay_trace(char* trace_fn) {           
    char buf[1000];  
    mem_addr_t addr = 0;
    unsigned int len = 0;
    FILE* trace_fp = fopen(trace_fn, "r"); 

    if (!trace_fp) { 
        fprintf(stderr, "%s: %s\n", trace_fn, strerror(errno));
    while (fgets(buf, 1000, trace_fp) != NULL) {
        if (buf[1] == 'S' || buf[1] == 'L' || buf[1] == 'M') {
            sscanf(buf+3, "%llx,%u", &addr, &len);
            if (verbosity)
                printf("%c %llx,%u ", buf[1], addr, len);

            // GIVEN: 1. addr has the address to be accessed
            //        2. buf[1] has type of acccess(S/L/M)
            // call access_data function here depending on type of access
						if(buf[1] == 'L'){ // checks if buf[1] is of type L
								access_data(addr); // accesses data to load
						else if(buf[1] == 'S'){ // check if buf[1] is of type S
								access_data(addr); // accesses data to store 
						else if(buf[1] == 'M'){ // check if buf[1] is of type M
								access_data(addr); // accesses memory twice
								// does nothing because buf[1] is not valid
            if (verbosity)


Prints information on how to use csim to Standard Output. This function is provided.

void print_usage(char* argv[]) {          

    printf("Usage: %s [-hv] -s <num> -E <num> -b <num> -t <file>\n", argv[0]);
    printf("  -h         Print this help message.\n");
    printf("  -v         Optional verbose flag.\n");
    printf("  -s <num>   Number of s bits for set index.\n");
    printf("  -E <num>   Number of lines per set.\n");
    printf("  -b <num>   Number of b bits for block offsets.\n");
    printf("  -t <file>  Trace file.\n");
    printf("  macos>  %s -s 4 -E 1 -b 4 -t traces/yi.trace\n", argv[0]);
    printf("  macos>  %s -v -s 8 -E 2 -b 4 -t traces/yi.trace\n", argv[0]);

Prints summary of the Cache Simulation Statistics of a file passed in. This Function is provided.

void print_summary(int hits, int misses, int evictions) {        

    printf("hits:%d misses:%d evictions:%d\n", hits, misses, evictions);

    FILE* output_fp = fopen(".csim_results", "w");


    fprintf(output_fp, "%d %d %d\n", hits, misses, evictions);


Main Function to Run

Main function that makes the Caches and runs everything.

int main(int argc, char* argv[]) {          

    char* trace_file = NULL;
    char c; 

    // Parse the command line arguments: -h, -v, -s, -E, -b, -t 
    while ((c = getopt(argc, argv, "s:E:b:t:vh")) != -1) {
        switch (c) {
            case 'b':
                b = atoi(optarg);
            case 'E':
                E = atoi(optarg);
            case 'h':
            case 's':
                s = atoi(optarg);
            case 't':
                trace_file = optarg;
            case 'v':
                verbosity = 1;

    //Make sure that all required command line args were specified.
    if (s == 0 || E == 0 || b == 0 || trace_file == NULL) {
        printf("%s: Missing required command line argument\n", argv[0]);

    // Initialize Cache.

    // Replay the memory access trace.

    // Free memory allocated for cache.

    //Print the statistics to a file.
    //DO NOT REMOVE: This function must be called for test_csim to work.
    print_summary(hit_cnt, miss_cnt, evict_cnt);
    return 0;   

Compile Program with Makefile

CC = gcc
CFLAGS = -Wall -std=gnu99 -m64 -g

all: csim.c
	$(CC) $(CFLAGS) -o csim csim.c -lm 

# Clean the src dirctory
	rm -f csim

Run the Makefile


Sould output a csim exectuable file in our directory.

Testing Program



./csim: Missing required command line argument
Usage: ./csim [-hv] -s <num> -E <num> -b <num> -t <file>
  -h         Print this help message.
  -v         Optional verbose flag.
  -s <num>   Number of set index bits.
  -E <num>   Number of lines per set.
  -b <num>   Number of block offset bits.
  -t <file>  Trace file.

  macos>  ./csim -s 4 -E 1 -b 4 -t traces/yi.trace
  macos>  ./csim -v -s 8 -E 2 -b 4 -t traces/yi.trace

Run with Trace

./csim -s 4 -E 1 -b 4 -t traces/yi.trace


hits:7 misses:2 evictions:0

Download Trace Files Here:

Download Traces for Testing (Zip File)

Here is how the file structure tree should be in this project:

├── Makefile
├── csim
├── csim.c
└── traces
    ├── dave.trace
    ├── debug.trace
    ├── long.trace
    ├── trans.trace
    ├── yi.trace
    └── yi2.trace