Skip to content

Commit 0fba069

Browse files
committed
Merge branch 'next'
2 parents 42ca9e0 + 34e5439 commit 0fba069

20 files changed

+566
-481
lines changed

.coveragerc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ exclude_lines =
1414

1515
# Only check coverage for source files
1616
include =
17-
cachesimulator/simulator.py
18-
cachesimulator/table.py
17+
cachesimulator/*.py

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015-2016 Caleb Evans
3+
Copyright (c) 2015-2018 Caleb Evans
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# Cache Simulator
22

3-
*Copyright 2015-2016 Caleb Evans*
3+
*Copyright 2015-2018 Caleb Evans*
44
*Released under the MIT license*
55

66
[![Build Status](https://travis-ci.org/caleb531/cache-simulator.svg?branch=master)](https://travis-ci.org/caleb531/cache-simulator)
77
[![Coverage Status](https://coveralls.io/repos/caleb531/cache-simulator/badge.svg?branch=master)](https://coveralls.io/r/caleb531/cache-simulator?branch=master)
88

9-
This program simulates a processor cache for the MIPS instruction set architecture. It can simulate all three fundamental caching schemes: direct-mapped, *n*-way set associative, and fully associative.
9+
This program simulates a processor cache for the MIPS instruction set
10+
architecture. It can simulate all three fundamental caching schemes:
11+
direct-mapped, *n*-way set associative, and fully associative.
1012

11-
The program must be run from the command line and requires Python 3 to run. Executing the program will run the simulation and print an ASCII table containing the details for each supplied word address, as well as the final contents of the cache.
13+
The program must be run from the command line and requires Python 3.4+ to run.
14+
Executing the program will run the simulation and print an ASCII table
15+
containing the details for each supplied word address, as well as the final
16+
contents of the cache.
1217

1318
To see example input and output, see `examples.txt`.
1419

@@ -30,22 +35,30 @@ The size of the cache in words (recall that one word is four bytes in MIPS).
3035

3136
#### --word-addrs
3237

33-
One or more word addresses (separated by spaces), where each word address is a base-10 positive integer.
38+
One or more word addresses (separated by spaces), where each word address is a
39+
base-10 positive integer.
3440

3541
### Optional parameters
3642

3743
#### --num-blocks-per-set
3844

39-
The program internally represents all cache schemes using a set associative cache. A value of `1` for this parameter (the default) implies a direct-mapped cache. A value other than `1` implies either a set associative *or* fully associative cache.
45+
The program internally represents all cache schemes using a set associative
46+
cache. A value of `1` for this parameter (the default) implies a direct-mapped
47+
cache. A value other than `1` implies either a set associative *or* fully
48+
associative cache.
4049

4150
#### --num-words-per-block
4251

4352
The number of words to store for each block in the cache; the default value is `1`.
4453

4554
#### --num-addr-bits
4655

47-
The number of bits used to represent each given word address; this value is reflected in the *BinAddr* column in the reference table. If omitted, the default value is the number of bits needed to represent the largest of the given word addresses.
56+
The number of bits used to represent each given word address; this value is
57+
reflected in the *BinAddr* column in the reference table. If omitted, the
58+
default value is the number of bits needed to represent the largest of the given
59+
word addresses.
4860

4961
#### --replacement-policy
5062

51-
The replacement policy to use for the cache. Accepted values are `lru` (Least Recently Used; the default) and `mru` (Most Recently Used).
63+
The replacement policy to use for the cache. Accepted values are `lru` (Least
64+
Recently Used; the default) and `mru` (Most Recently Used).

cachesimulator/__main__.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
5+
from cachesimulator.simulator import Simulator
6+
7+
8+
# Parse command-line arguments passed to the program
9+
def parse_cli_args():
10+
11+
parser = argparse.ArgumentParser()
12+
13+
parser.add_argument(
14+
'--cache-size',
15+
type=int,
16+
required=True,
17+
help='the size of the cache in words')
18+
19+
parser.add_argument(
20+
'--num-blocks-per-set',
21+
type=int,
22+
default=1,
23+
help='the number of blocks per set')
24+
25+
parser.add_argument(
26+
'--num-words-per-block',
27+
type=int,
28+
default=1,
29+
help='the number of words per block')
30+
31+
parser.add_argument(
32+
'--word-addrs',
33+
nargs='+',
34+
type=int,
35+
required=True,
36+
help='one or more base-10 word addresses')
37+
38+
parser.add_argument(
39+
'--num-addr-bits',
40+
type=int,
41+
default=1,
42+
help='the number of bits in each given word address')
43+
44+
parser.add_argument(
45+
'--replacement-policy',
46+
choices=('lru', 'mru'),
47+
default='lru',
48+
# Ignore argument case (e.g. "mru" and "MRU" are equivalent)
49+
type=str.lower,
50+
help='the cache replacement policy (LRU or MRU)')
51+
52+
return parser.parse_args()
53+
54+
55+
def main():
56+
57+
cli_args = parse_cli_args()
58+
sim = Simulator()
59+
sim.run_simulation(**vars(cli_args))
60+
61+
62+
if __name__ == '__main__':
63+
main()

cachesimulator/bin_addr.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env python3
2+
3+
4+
class BinaryAddress(str):
5+
6+
# Retrieves the binary address of a certain length for a base-10 word
7+
# address; we must define __new__ instead of __init__ because the class we
8+
# are inheriting from (str) is an immutable data type
9+
def __new__(cls, bin_addr=None, word_addr=None, num_addr_bits=0):
10+
11+
if word_addr is not None:
12+
return super().__new__(
13+
cls, bin(word_addr)[2:].zfill(num_addr_bits))
14+
else:
15+
return super().__new__(cls, bin_addr)
16+
17+
@classmethod
18+
def prettify(cls, bin_addr, min_bits_per_group):
19+
20+
mid = len(bin_addr) // 2
21+
22+
if mid < min_bits_per_group:
23+
# Return binary string immediately if bisecting the binary string
24+
# produces a substring which is too short
25+
return bin_addr
26+
else:
27+
# Otherwise, bisect binary string and separate halves with a space
28+
left = cls.prettify(bin_addr[:mid], min_bits_per_group)
29+
right = cls.prettify(bin_addr[mid:], min_bits_per_group)
30+
return ' '.join((left, right))
31+
32+
# Retrieves the tag used to distinguish cache entries with the same index
33+
def get_tag(self, num_tag_bits):
34+
35+
end = num_tag_bits
36+
tag = self[:end]
37+
if len(tag) != 0:
38+
return tag
39+
else:
40+
return None
41+
42+
# Retrieves the index used to group blocks in the cache
43+
def get_index(self, num_offset_bits, num_index_bits):
44+
45+
start = len(self) - num_offset_bits - num_index_bits
46+
end = len(self) - num_offset_bits
47+
index = self[start:end]
48+
if len(index) != 0:
49+
return index
50+
else:
51+
return None
52+
53+
# Retrieves the word offset used to select a word in the data pointed to by
54+
# the given binary address
55+
def get_offset(self, num_offset_bits):
56+
57+
start = len(self) - num_offset_bits
58+
offset = self[start:]
59+
if len(offset) != 0:
60+
return offset
61+
else:
62+
return None

cachesimulator/cache.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
3+
from cachesimulator.bin_addr import BinaryAddress
4+
from cachesimulator.reference import ReferenceCacheStatus
5+
from cachesimulator.word_addr import WordAddress
6+
7+
8+
class Cache(dict):
9+
10+
# Initializes the reference cache with a fixed number of sets
11+
def __init__(self, cache=None, num_sets=None, num_index_bits=0):
12+
13+
# A list of recently ordered addresses, ordered from least-recently
14+
# used to most
15+
self.recently_used_addrs = []
16+
17+
if cache is not None:
18+
self.update(cache)
19+
else:
20+
for i in range(num_sets):
21+
index = BinaryAddress(
22+
word_addr=WordAddress(i), num_addr_bits=num_index_bits)
23+
self[index] = []
24+
25+
# Every time we see an address, place it at the top of the
26+
# list of recently-seen addresses
27+
def mark_ref_as_last_seen(self, ref):
28+
29+
# The index and tag (not the offset) uniquely identify each address
30+
addr_id = (ref.index, ref.tag)
31+
if addr_id in self.recently_used_addrs:
32+
self.recently_used_addrs.remove(addr_id)
33+
self.recently_used_addrs.append(addr_id)
34+
35+
# Returns True if a block at the given index and tag exists in the cache,
36+
# indicating a hit; returns False otherwise, indicating a miss
37+
def is_hit(self, addr_index, addr_tag):
38+
39+
# Ensure that indexless fully associative caches are accessed correctly
40+
if addr_index is None:
41+
blocks = self['0']
42+
elif addr_index in self:
43+
blocks = self[addr_index]
44+
else:
45+
return False
46+
47+
for block in blocks:
48+
if block['tag'] == addr_tag:
49+
return True
50+
51+
return False
52+
53+
# Adds the given entry to the cache at the given index
54+
def set_block(self, replacement_policy,
55+
num_blocks_per_set, addr_index, new_entry):
56+
57+
# Place all cache entries in a single set if cache is fully associative
58+
if addr_index is None:
59+
blocks = self['0']
60+
else:
61+
blocks = self[addr_index]
62+
# Replace MRU or LRU entry if number of blocks in set exceeds the limit
63+
if len(blocks) == num_blocks_per_set:
64+
# Iterate through the recently-used entries in reverse order for
65+
# MRU
66+
if replacement_policy == 'mru':
67+
recently_used_addrs = reversed(self.recently_used_addrs)
68+
else:
69+
recently_used_addrs = self.recently_used_addrs
70+
# Replace the first matching entry with the entry to add
71+
for recent_index, recent_tag in recently_used_addrs:
72+
for i, block in enumerate(blocks):
73+
if (recent_index == addr_index and
74+
block['tag'] == recent_tag):
75+
blocks[i] = new_entry
76+
return
77+
else:
78+
blocks.append(new_entry)
79+
80+
# Simulate the cache by reading the given address references into it
81+
def read_refs(self, num_blocks_per_set,
82+
num_words_per_block, replacement_policy, refs):
83+
84+
for ref in refs:
85+
self.mark_ref_as_last_seen(ref)
86+
87+
# Record if the reference is already in the cache or not
88+
if self.is_hit(ref.index, ref.tag):
89+
# Give emphasis to hits in contrast to misses
90+
ref.cache_status = ReferenceCacheStatus.hit
91+
else:
92+
ref.cache_status = ReferenceCacheStatus.miss
93+
self.set_block(
94+
replacement_policy=replacement_policy,
95+
num_blocks_per_set=num_blocks_per_set,
96+
addr_index=ref.index,
97+
new_entry=ref.get_cache_entry(num_words_per_block))

cachesimulator/reference.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
3+
from collections import OrderedDict
4+
from enum import Enum
5+
6+
from cachesimulator.bin_addr import BinaryAddress
7+
from cachesimulator.word_addr import WordAddress
8+
9+
10+
# An address reference consisting of the address and all of its components
11+
class Reference(object):
12+
13+
def __init__(self, word_addr, num_addr_bits,
14+
num_offset_bits, num_index_bits, num_tag_bits):
15+
self.word_addr = WordAddress(word_addr)
16+
self.bin_addr = BinaryAddress(
17+
word_addr=self.word_addr, num_addr_bits=num_addr_bits)
18+
self.offset = self.bin_addr.get_offset(num_offset_bits)
19+
self.index = self.bin_addr.get_index(num_offset_bits, num_index_bits)
20+
self.tag = self.bin_addr.get_tag(num_tag_bits)
21+
self.cache_status = None
22+
23+
def __str__(self):
24+
return str(OrderedDict(sorted(self.__dict__.items())))
25+
26+
__repr__ = __str__
27+
28+
# Return a lightweight entry to store in the cache
29+
def get_cache_entry(self, num_words_per_block):
30+
return {
31+
'tag': self.tag,
32+
'data': self.word_addr.get_consecutive_words(
33+
num_words_per_block)
34+
}
35+
36+
37+
# An enum representing the cache status of a reference (i.e. hit or miss)
38+
class ReferenceCacheStatus(Enum):
39+
40+
miss = 0
41+
hit = 1
42+
43+
# Define how reference statuses are displayed in simulation results
44+
def __str__(self):
45+
if self.value == ReferenceCacheStatus.hit.value:
46+
return 'HIT'
47+
else:
48+
return 'miss'
49+
50+
__repr__ = __str__

0 commit comments

Comments
 (0)