#pragma once
#include <systemc.h>
#define TEMPLATE
//#define DEBUG
#define HIT_DELAY 1 // In case of cache hit, the delay time to generate output data in cycle
#define MISS_DELAY 100 // In case of cache miss, the delay time to generate output data in cycle
#ifndef TEMPLATE
#define ADDR_TYPE sc_uint<64>
#define DATA_TYPE sc_biguint<1024>
#else
// If a template class is used to create a SystemC module, The member functions must be declared in the header file.
// They can't be put in a separate .cpp file with current C++ compilers
template<class ADDR_TYPE, class DATA_TYPE>
#endif
SC_MODULE(Cache) {
// Cache ports
sc_in<bool> clk;
sc_in<bool> reset;
sc_in<ADDR_TYPE> addr; // Composed of four fields |tag|index|block_offset|byte_offset|
sc_in<bool> addr_valid;
sc_out<bool> addr_ready;
sc_out<DATA_TYPE> data;
sc_out<bool> data_valid; // ready = 1 if cache can serve other request.
sc_in<bool> data_ready;
// Bit widths of addr and data
int addr_width; // ADDR_TYPE will be sc_uint<addr_width>. Note that 'addr_width' must be specified in a constructor
int word_width; // DATA_TYPE will be sc_uint<data_width>. Note that 'data_width' must be specified in a constructor
// Size of each components, cache, cache block
int block_size; // Block size in word. That is, #words in a cache block. Note that 'block_size' must be specified in a constructor
int cache_size; // Cache size in byte. Note that 'cache_size' must be specified in a constructor
// Address fields
ADDR_TYPE tag; // Distinguishes cache blocks that have the same address of (index+block_offset+byte_offset).
// tag is declared as an object of ADDR_TYPE because it can be very long.
int index; // Selects a cache block
int block_offset; // Selects a word in a cache block
int byte_offset; // Selects a byte in a word
// Width of address fields
int byte_offset_width; // log2(#bytes in a word). That is, log2(word_width/8) in this class.
int block_offset_width; // log2(#words in a cache block). That is, log2(block_size) in this class.
int index_width; // log2(#cache blocks in a cache). That is, log2(cache_size/block_size) in this class.
int tag_width; // The remaining bits of addr excluding index, block_offset, and byte_offset.
// That is, tag_width = addr_width - index_width - block_offset_width - byte_offset_width.
// Counters for hit ratio calculation
int hit_count;
int miss_count;
int access_count;
// Cache containers
sc_uint<1>* valid;
ADDR_TYPE* tag_memory;
DATA_TYPE** cache_data;
// Main memory
int mem_size; // Memory size in kilo byte.
public:
void hit() {
for (int i = 0; i < HIT_DELAY; i++) { wait(); } // Delay HIT_DELAY cycles
hit_count++;
cout << "HIT: Cache hit at time " << sc_time_stamp() << endl;
}
void miss(ADDR_TYPE store_addr) {
cout << "MISS: Cache miss at time " << sc_time_stamp() << endl;
int delay = 0;
for (int i = 0; i < MISS_DELAY; i++) {
cout << "In the miss delay loop!!" << endl;
wait();
cout << "Waiting " << ++delay << " cycles at time " << sc_time_stamp() << endl;
} // Delay MISS_DELAY cycles
// Set valid flag and tag address for the current address
valid[index] = 1;
tag_memory[index] = tag;
miss_count++;
// Making the blcok offset and byte offset 0's
//ADDR_TYPE addr_tmp = addr.read() >> (block_offset_width + byte_offset_width); // Shift right to eliminate the block and byte offsets
ADDR_TYPE addr_tmp = store_addr >> (block_offset_width + byte_offset_width); // Shift right to eliminate the block and byte offsets
addr_tmp = addr_tmp << (block_offset_width + byte_offset_width); // Then, shift left to keep the index and tag addresses in their original position
// Cache block (or line) fill
for (int i = 0; i < block_size; i++) {
cache_data[index][i] = (DATA_TYPE)(addr_tmp + i); // We assume that the data in a given address in the main memory is simply the address. That is memory[addr] == addr.
// Note that the main memory can be modeled in detail later
}
}
float get_hit_ratio() {
return ((float)hit_count / access_count);
}
bool isOutOfAddressRange() {
return (addr.read().to_int() > (mem_size*1024 - 1));
}
void cache_operation()
{
// Bit indexes of each address fields
int msb_byte_offset = byte_offset_width - 1;
int msb_block_offset = block_offset_width + byte_offset_width - 1;
int msb_index = index_width + block_offset_width + byte_offset_width - 1;
int msb_tag = addr_width - 1;
int lsb_byte_offset = 0;
int lsb_block_offset = byte_offset_width;
int lsb_index = block_offset_width + byte_offset_width;
int lsb_tag = index_width + block_offset_width + byte_offset_width;
// Reset behaior
addr_ready.write(0);
data_valid.write(0);
data.write(0);
wait();
while (1) {
// Handshaking for addr
addr_ready.write(1); // Cache is ready to receive a new address
do {
cout << "[BEFORE] addr_valid from SOURCE is " << addr_valid.read() << endl;
wait();
cout << "[AFTER] addr_valid from SOURCE is " << addr_valid.read() << endl;
} while (!addr_valid.read()); // Wait until a valid adress is ready
addr_ready.write(0); // Cache is serving a new address. Until the service is done, cache is not ready to take a new address
ADDR_TYPE store_addr = addr.read();
// Check if the given address addr is out of range or not
if (isOutOfAddressRange()) {
cout << "The given address " << addr.read().to_string(SC_BIN) << " accesses out of main memory range." << endl;
exit(1);
}
// Extracts each address field from the current addr input port
tag = store_addr.range(msb_tag, lsb_index);
index = (int)store_addr.range(msb_index, lsb_index);
block_offset = (int)store_addr.range(msb_block_offset, lsb_block_offset);
byte_offset = (int)store_addr.range(msb_byte_offset, lsb_byte_offset);
//tag = addr.read().range(msb_tag, lsb_index);
//index = (int)addr.read().range(msb_index, lsb_index);
//block_offset = (int)addr.read().range(msb_block_offset, lsb_block_offset);
//byte_offset = (int)addr.read().range(msb_byte_offset, lsb_byte_offset);
// Counts the number of accesses
access_count++;
// Return data with a proper delay depending on HIT or MISS
if ((valid[index] == (sc_uint<1>)1) && (tag_memory[index] == tag)) { // If the valid bit is 1 and the tag address is identical, it's HIT. Otherwise, it's MISS.
// Cache hit
hit();
}
else {
miss(store_addr);
}
// Handshaking for data
data.write(cache_data[index][block_offset]);
data_valid.write(1); // Notify the master that a valid data is ready.
//cout << "Address=" << addr.read().to_string(SC_BIN_US)
cout << "Address=" << store_addr.to_string(SC_BIN_US)
<< " (Data=" << cache_data[index][block_offset].to_string(SC_BIN_US)
<< "). Hit ratio: " << get_hit_ratio() << endl;
do { wait(); } while (!data_ready.read()); // Wait until the new data is read by a master
data_valid.write(0);
}
}
#ifdef TEMPLATE
SC_HAS_PROCESS(Cache);
Cache(sc_module_name _name, int addrW, int wordW, int blockS, int cacheS, int memS) : sc_module(_name)
#else
SC_HAS_PROCESS(Cache);
Cache(sc_module_name _name, int addrW, int wordW, int blockS, int cacheS, int memS) : sc_module(_name)
#endif
{
addr_width = addrW; // Address bit width
word_width = wordW; // Word bit width
block_size = blockS; // # of words in a cache block
cache_size = cacheS * 1024; // Cache size in byte.
mem_size = memS*1024; // Memory size in kilo byte
// Address is composed of four fields - [ tag | index | block_offset | byte_offset ]
// tag : Tag address identifies the address in a main memory that the cache block selected by an index address blongs to
// index: Cache index address selects a cache block
// block_offset: Block offset address selects a word in a cache block
// byte_offset: Byte offset address selects a byte in a word
// Address field widths are computed in the following with error checking
// Every field width must be a power of 2.
byte_offset_width = (int)ceil(log2(word_width/8)); // Byte offset
if (pow(2, byte_offset_width) != word_width/8) {
cout << "Error: the word width " << wordW << " of cache " << name() << " is not a power of 2." << endl;
exit(1);
}
block_offset_width = (int)ceil(log2(block_size)); // Block offset
if (pow(2, block_offset_width) != block_size) {
cout << "Error: the block size " << blockS << " of cache " << name() << " is not a power of 2." << endl;
exit(1);
}
int cache_size_test = (int)ceil(log2(cacheS));
if (pow(2, cache_size_test) != cacheS) {
cout << "Error: the cache size " << cacheS << " of cache " << name() << " is not a power of 2." << endl;
exit(1);
}
int mem_size_test = (int)ceil(log2(memS));
if (pow(2, mem_size_test) != memS) {
cout << "Error: the size " << memS << "MB of main memory is not a power of 2." << endl;
exit(1);
}
else if ((mem_size * 1024) < cache_size) { // Main memory is smaller than the cache
cout << "Error: the size " << memS << "MB of main memory is smaller than that " << cacheS << "KB of the cache " << name() << endl;
exit(-1);
}
// Tag address width must be positive.
// Otherwise, the cache size is bigger than the main memory addressed by the address whose width is given by addrW.
tag_width = addrW - log2(cache_size);
if (tag_width <= 0) {
cout << "Error: the input address width " << addrW << " of cache " << name() << " is too small. Consequently, the tag address width is negative." << endl;
exit(-1);
}
index_width = addr_width - tag_width - block_offset_width - byte_offset_width;
int numBlocks = (int)pow(2, index_width); // Number of cache blocks
// Initialize hit and miss counter
hit_count = miss_count = access_count = 0;
#ifdef DEBUG
cout << endl << endl
<< "Cache is successfully created." << endl << endl
<< "Name : " << name() << endl
<< "Size in byte : " << cache_size << " bytes" << endl
<< "Address width : " << addr_width << "bits" << endl
<< "Word size : " << word_width << "bits" << endl
<< "Block size : " << blockS << "words" << endl
<< "Number of blocks : " << numBlocks << "blocks" << endl
<< "Main memory size : " << memS << "MB" << endl << endl
<< "Address fields" << endl
<< "Tag : " << addr_width - 1 << " to " << addr_width - tag_width << endl
<< "Index : " << addr_width - tag_width - 1 << " to " << addr_width - tag_width - index_width << endl
<< "Block offset : " << addr_width - tag_width - index_width - 1 << " to " << addr_width - tag_width - index_width - block_offset_width << endl
<< "Byte offset : " << addr_width - tag_width - index_width - block_offset_width - 1 << " to " << addr_width - tag_width - index_width - block_offset_width - byte_offset_width << endl;
#endif // DEBUG
// Allocate memories for the cache
valid = new sc_uint<1>[numBlocks]; // Initialized to 0.
tag_memory = new ADDR_TYPE[numBlocks]; // Initialized to all 0's.
cache_data = new DATA_TYPE * [numBlocks]; // Initialized to all 0's.
for (int i = 0; i < numBlocks; ++i) {
//cache_data[i] = new DATA_TYPE[block_offset_width];
cache_data[i] = new DATA_TYPE[block_size]; //HWC
}
// Simulation process registration
// SC_CTHREAD(cache_operation, clk.pos());
SC_THREAD(cache_operation);
sensitive << clk.pos();
reset_signal_is(reset, false);
}
}; // Cache
#pragma once
#include <systemc.h>
#include <math.h>
//#define TEMPLATE
//#define DEBUG
#define NUM_PATTERNS 100 // Number of input patterns
#ifndef TEMPLATE
#define ADDR_TYPE sc_uint<64>
#define DATA_TYPE sc_biguint<1024>
#else
// If a template class is used to create a SystemC module, The member functions must be declared in the header file.
// They can't be put in a separate .cpp file with current C++ compilers
template<class ADDR_TYPE, class DATA_TYPE>
#endif
SC_MODULE(CacheTB) {
// Cache ports
sc_in<bool> clk;
sc_out<bool> reset;
sc_out<ADDR_TYPE> addr; // Composed of four fields |tag|index|block_offset|byte_offset|
sc_out<bool> addr_valid;
sc_in<bool> addr_ready;
sc_in<DATA_TYPE> data;
sc_in<bool> data_valid;
sc_out<bool> data_ready;
int mem_size;
void source() {
// Reset behavior
addr.write(0);
addr_valid.write(0);
reset.write(0);
wait();
reset.write(1);
wait();
ADDR_TYPE random_addr;
for (int i = 0; i < NUM_PATTERNS; i++) {
random_addr = (ADDR_TYPE)(rand() % (mem_size * 1024));
cout << "[Source] Address " << random_addr.to_string(SC_BIN_US, false) << " is requested at time " << sc_time_stamp() << endl;
addr.write(random_addr);
addr_valid.write(1);
do {
cout << "[BEFORE] addr_ready from CACHE is " << addr_ready.read() << endl;
wait();
cout << "[AFTER] addr_ready from CACHE is " << addr_ready.read() << endl;
} while (!addr_ready.read()); // Wait until the cache is ready to take a new address
addr_valid.write(0);
}
sc_stop();
}
void sink() {
// Reset behavior
data_ready.write(0);
wait();
while (1) {
data_ready.write(1);
do { wait(); } while (!data_valid.read()); // Wait until a valid data is ready
data_ready.write(0);
cout << "[SINK] Data " << data.read().to_string(SC_BIN_US, false) << " is returned at time " << sc_time_stamp() << endl;
}
}
sc_event tmp;
SC_HAS_PROCESS(CacheTB);
CacheTB(sc_module_name _name, int memS):sc_module(_name) { // memS: main memory size in MB
mem_size = memS * 1024; // Main memory size in KB
// /*
SC_THREAD(source);
sensitive << clk.pos();
SC_THREAD(sink);
sensitive << clk.pos();
reset_signal_is(reset, false);
// */
/*
SC_CTHREAD(source, clk.pos());
SC_CTHREAD(sink, clk.pos());
*/
}
}; // CacheTB
#pragma once
//#include<systemc.h>
#include "Cache.h"
#include "CacheTB.h"
//#define TEMPLATE
//#define DEBUG
#ifndef TEMPLATE
#define ADDR_TYPE sc_uint<64>
#define DATA_TYPE sc_biguint<1024>
#else
// If a template class is used to create a SystemC module, The member functions must be declared in the header file.
// They can't be put in a separate .cpp file with current C++ compilers
template<class ADDR_TYPE, class DATA_TYPE>
#endif
SC_MODULE(System) {
// Instantiate modules
Cache<ADDR_TYPE, DATA_TYPE> *cache4KB;
CacheTB<ADDR_TYPE, DATA_TYPE> *cacheTB;
// Signals
//sc_clock sysClk("_systemClk", 5, SC_NS);
sc_clock sysClk;
sc_signal<bool> reset;
sc_signal<ADDR_TYPE> addr; // Composed of four fields |tag|index|block_offset|byte_offset|
sc_signal<bool> addr_valid;
sc_signal<bool> addr_ready;
sc_signal<DATA_TYPE> data;
sc_signal<bool> data_valid;
sc_signal<bool> data_ready;
SC_HAS_PROCESS(System);
System (sc_module_name _name, int addr_width, int word_width, int block_size, int cache_size, int mem_size) : sc_module(_name) {
cache4KB = new Cache<ADDR_TYPE, DATA_TYPE>("Cache4KB", addr_width, word_width, block_size, cache_size, mem_size);
cache4KB->clk(sysClk);
cache4KB->reset(reset);
cache4KB->addr(addr);
cache4KB->addr_valid(addr_valid);
cache4KB->addr_ready(addr_ready);
cache4KB->data(data);
cache4KB->data_valid(data_valid);
cache4KB->data_ready(data_ready);
cacheTB = new CacheTB<ADDR_TYPE, DATA_TYPE>("CacheTB", 1); // Main memory size is 1MB
cacheTB->clk(sysClk);
cacheTB->reset(reset);
cacheTB->addr(addr);
cacheTB->addr_valid(addr_valid);
cacheTB->addr_ready(addr_ready);
cacheTB->data(data);
cacheTB->data_valid(data_valid);
cacheTB->data_ready(data_ready);
}
}; // System
#include "System.h"
//#define DEBUG
int sc_main(int argc, char* argv[]) {
System<sc_uint<32>, sc_uint<32>> system("CacheSystem", 32, 32, 16, 4, 1);
// address width = 16 bits
// word width = 16 bits
// block size = 16 words
// cache size = 1 KB
// main memory size = 1 MB
sc_start();
return 0;
}