0% found this document useful (0 votes)
51 views4 pages

Cache Simulation Assignment in C

Uploaded by

itaiziman
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
51 views4 pages

Cache Simulation Assignment in C

Uploaded by

itaiziman
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Computer Architecture

Assignment 4 - Simulating the Cache


.........................................................................................................
As always it is important you adhere to the following guidelines:
(1) Work individually;
(2) The submission date is January 5, 23:59;
(3) Submission is via the “submit” system;
(4) This assignment should be written in C and not Assembly!
(5) Ensure that your submission compiles and runs without errors or warnings on BIU’s servers before
submitting. Failure to do so will result in docked points;
(6) At the beginning of every file you submit, add your ID and name in a comment. For example: /*
123456789 Israel Israeli */;
(7) It is forbidden to use AI tools like ChatGPT when writing your homework. Doing so is tantamount
to cheating, and will be treated as such;
(8) You should use the given general main and print cache functions in order to be compatabile with the
auto checking;
(9) You can (and should) assume that the input is valid in terms of read and write requests (addresses
are valid) and that s+t+b is indeed the number of bits that are needed for addressing all the data in
the RAM.
In this assignment, you are asked to write code which simulates how a cache operates. To do so, you
will need to define the two following structs, which will be utilized to represent cache sets and the cache
itself respectively:
1 typedef unsigned char uchar;
2
3 typedef struct cache_line_s {
4 uchar valid;
5 uchar frequency;
6 long int tag;
7 uchar* block;
8 } cache_line_t;
9
10 typedef struct cache_s {
11 uchar s;
12 uchar t;
13 uchar b;
14 uchar E;
15 cache_line_t** cache;
16 } cache_t;
Then you will need to define the following three functions:
1 cache_t initialize_cache(uchar s, uchar t, uchar b, uchar E);
2 uchar read_byte(cache_t cache, uchar* start, long int off);
3 void write_byte(cache_t cache, uchar* start, long int off, uchar new);
The type cache t contains in it four parameters: s, t, b, E which are defined as in the book: S = 2s is
the number of sets, B = 2b is the number of blocks per line in the set, E is the number of lines per set, and
t is the tag length. Recall that given an address of length m = s + t + b, partition the bits into

1
t bits s bits b bits

The cache field is an array of array of cache lines. Each array of cache lines can be thought of as a
cache set, and so cache is simply an array of sets. Each cache line t has a valid bit (ignore the fact that
it is of course actually a byte), its frequency, its tag (which should be t bits long, but we made it a long to
make your (my) life easier), and the block of memory.
The cache you implement should replace lines using the LFU (least frequently used) method, meaning
if it tries to read in memory to a set with no available lines then it replaces the line whose frequency is
minimal. If two lines both have the same minimal frequency, then the first line is chosen (ie. if line 3 and
line 10 both have frequency 1 which is the smallest, then line 3 is chosen).
The read byte and write byte functions accept as inputs start as well as off. You should act as if
start is the zero address and off is the address that you insert into your cache. More specifically, insert
the contents of start[off] into your cache, while using the bits of off as your offset.
Your cache is write-through, meaning that when write byte is called, it writes new both to the cache
and to memory.
The read byte function should return eventually the asked byte from the cache, after making sure that
the cache is updated as we saw in class.
We also provide you with the print cache function (that you must use, for proper formatting), so you
don’t need to worry about whitespace errors But fair warning: it’s going to paste weirdly into your text
editor, sorry. You win some, you lose some.
1 void print_cache(cache_t cache) {
2 int S = 1 << cache.s;
3 int B = 1 << cache.b;
4
5 for (int i = 0; i < S; i++) {
6 printf("Set %d\n", i);
7 for (int j = 0; j < cache.E; j++) {
8 printf("%1d %d 0x%0*lx ", [Link][i][j].valid,
9 [Link][i][j].frequency, cache.t, [Link][i][j].tag);
10 for (int k = 0; k < B; k++) {
11 printf("%02x ", [Link][i][j].block[k]);
12 }
13 puts("");
14 }
15 }
16 }
So for example,
1 int main() {
2 uchar arr[] = {1, 2, 3, 4, 5, 6, 7, 8};
3 cache_t cache = initialize_cache(1, 1, 1, 2);
4 read_byte(cache, arr, 0);
5 read_byte(cache, arr, 1);
6 read_byte(cache, arr, 2);
7 read_byte(cache, arr, 6);
8 read_byte(cache, arr, 7);
9 print_cache(cache);
10 }
Should print

2
1 Set 0
2 1 2 0x0 01 02
3 0 0 0x0 00 00
4 Set 1
5 1 1 0x0 03 04
6 1 2 0x1 07 08

We supply you here your main function (that must be included in your final code!). It simply gets as
input the size of your data (which simulates RAM), the actual data to store in “RAM”, the cache parameters,
and then the bytes it should read (which it reads until it gets a negative value):
1 int main() {
2 int n;
3 printf("Size of data: ");
4 scanf("%d", &n);
5 uchar* mem = malloc(n);
6 printf("Input data >> ");
7 for (int i = 0; i < n; i++)
8 scanf("%hhd", mem + i);
9
10 int s, t, b, E;
11 printf("s t b E: ");
12 scanf("%d %d %d %d", &s, &t, &b, &E);
13 cache_t cache = initialize_cache(s, t, b, E);
14
15 while (1) {
16 scanf("%d", &n);
17 if (n < 0) break;
18 read_byte(cache, mem, n);
19 }
20
21 puts("");
22 print_cache(cache);
23
24 free(mem);
25 }

So for example, the previous example can be equivalently run like so:
1 Size of data: 8
2 Input data >> 1 2 3 4 5 6 7 8
3 s t b E: 1 1 1 2
4 0 1 2 6 7 -1
5
6 Set 0
7 1 2 0x0 01 02
8 0 0 0x0 00 00
9 Set 1
10 1 1 0x0 03 04
11 1 2 0x1 07 08

3
What to Submit
You must submit using the submit system all your code plus a makefile that compiles your code to an
executable named cache. Notice that the files containing your code can be named whatever you like, just
make sure that the result of the makefile is named as mentioned and is compiled without any warnings or
errors!

Common questions

Powered by AI

The function 'initialize_cache' is used to set up the cache with given parameters s, t, b, and E, which define the size and structure of the cache sets and lines. 'read_byte' handles reading a byte from the simulated cache, utilizing an offset to obtain the desired data and updating the cache as necessary. 'write_byte' is responsible for implementing the write-through policy by updating both the cache and memory. These functions are crucial for managing data flow within the cache, ensuring it operates according to specifications laid out for efficient data retrieval and storage .

In cache design, 'S' (number of sets) is derived from 's' (set index bits), where S = 2^s. 'B' (block size) is related to 'b' (block offset), with B = 2^b. 'E' represents the number of cache lines per set. 't' defines the tag length, crucial for identifying data location. Together, these parameters determine the cache's structure and capacity, defining how memory addresses are mapped to cache locations, affecting hit rates and efficiency .

Partitioning the address bits into tag (t), set index (s), and block offset (b) facilitates cache operations by clearly defining how and where data should be stored and retrieved in the cache. The tag bit identifies the specific data, the set index determines which set within the cache will be accessed, and the block offset pinpoints the exact location within a block of a cache line. This partitioning ensures that data mapping between memory and cache is systematic, reducing conflicts and enhancing access efficiency .

The 'cache_line_t' struct is essential for simulating individual lines of a cache. It consists of four main components: 'valid' which determines if the cache line contains valid data, 'frequency' which tracks how often the line has been accessed and is used in the LFU (Least Frequently Used) replacement policy, 'tag' which identifies the data's location in memory, and 'block' which stores the actual data bytes. These components work together to manage data storage and retrieval, ensuring efficient use of cache space through line validation, data identification, and frequency tracking .

The 'print_cache' function is significant as it provides a way to visualize and verify the current state of the cache. It outputs details such as each set's cache lines, their validity, frequency, tag, and the data held. This function is essential for debugging and understanding how the cache handles data during simulations, aiding in both error checking and demonstrations of cache operation under different scenarios .

AI tools are prohibited to ensure academic integrity and guarantee that students develop necessary programming skills independently. AI could provide shortcut solutions, inhibiting deeper understanding and problem-solving skills development, which are crucial for mastering computer architecture concepts. This prohibition encourages students to engage directly with the material, fostering genuine learning experiences rather than reliance on potentially unverified external solutions .

The LFU replacement method affects cache performance by ensuring that the least frequently accessed cache lines are replaced when a new line needs to be loaded, which can help maintain frequently accessed data in the cache. This method can improve hit rates for applications with certain access patterns where frequently accessed data tends to remain relevant longer. However, it may not perform well in workloads where access patterns change rapidly, as the cache might continue storing lines that are no longer relevant but were frequently accessed in the past, leading to inefficiencies .

Relying on the assumption that input is valid can lead to limitations in error handling and robustness testing. It simplifies the implementation by removing the need for input validation, potentially masking issues that could arise with unexpected or incorrect inputs. This assumption may result in overlooked scenarios where inputs do not adhere to expected patterns, which can be problematic if the code is expanded or repurposed for real-world applications requiring rigorous validation .

Including the 'main' function is important because it provides a standardized entry point for the code execution and ensures compatibility with the auto-checking system. It defines how input data is handled, sets up cache parameters, manages byte reading until a terminator is encountered, and handles memory cleanup. This ensures the submitted code adheres to the expected input processing and program flow, facilitating consistent testing and grading .

Using a write-through policy implies that every write operation to the cache is immediately copied to the main memory. This ensures data consistency between cache and memory, minimizing data corruption risk if the cache fails. However, it can lead to increased write cycles and potential slowdowns due to the constant writing back of data. It sacrifices potential performance gains for data accuracy, which is crucial in scenarios where reliable data consistency is required over performance .

You might also like