Source code for scalesim.memory.read_buffer_estimate_bw

import math
import numpy as np

from scalesim.memory.read_port import read_port


[docs] class ReadBufferEstimateBw: """ Class which service the estimate bandwidth mode in the read buffer """ def __init__(self): """ The constructor method for the class """ # Buffer parameters self.word_size = 1 self.active_buf_frac = 0.5 self.total_size_bytes = 1 self.total_size_elems = 1 self.active_buf_size = 1 self.prefetch_buf_size = 1 self.hit_latency = 1 # Backing buffer parameters self.backing_buffer = read_port() self.default_bandwidth = 1 self.prefetch_bandwidth = 1 # Access counts self.num_access = 0 # Trace matrix self.trace_matrix = np.ones((1, 1)) # Tracking variables self.num_items_per_set = -1 self.elems_current_set = 0 self.current_set_id = 0 self.read_buffer_set_start_id = -1 self.read_buffer_set_end_id = -1 self.prefetch_buffer_set_start_id = -1 self.prefetch_buffer_set_end_id = -1 self.last_prefetch_start_cycle = -2 self.last_prefetch_end_cycle = -1 self.first_request_rcvd_cycle = 0 # Internal data structures self.current_set = set() self.list_of_sets = [] self.num_sets_active_buffer = 1 self.num_sets_prefetch_buffer = 1 # Flags self.first_request_seen = False self.params_set_flag = False self.active_buffer_prefetch_done = False self.trace_valid = False #
[docs] def set_params(self, backing_buf_obj, total_size_bytes=1, word_size=1, active_buf_frac=0.9, hit_latency=1, backing_buf_default_bw=1): """ Method to set the ifmap/filter double buffered memory simulation parameters for estimate bandwidth mode. :param backing_buf_obj: Backing buffer object, by default is read_port :param total_size_bytes: Read buffer (SRAM) total size in bytes :param word_size: The word size of individual elements :param active_buf_frac: The active fraction of the double duffered ifmap/filter memory (serving the systolic array memory requests) :param hit_latency: Hit latency of the double duffered ifmap/filter memory :param backing_buf_bw: Bandwidth of the backing buffer for ifmap SRAM. The default backing buffer is a dummy one (read port). :return: None """ self.total_size_bytes = total_size_bytes self.word_size = word_size assert 0.5 <= active_buf_frac < 1, "Valid active buf frac [0.5,1)" self.active_buf_frac = round(active_buf_frac, 2) self.hit_latency = hit_latency self.backing_buffer = backing_buf_obj self.default_bandwidth = backing_buf_default_bw self.prefetch_bandwidth = self.default_bandwidth # Calculate these based on the values provided self.total_size_elems = math.floor(self.total_size_bytes / self.word_size) self.active_buf_size = int(math.ceil(self.total_size_elems * self.active_buf_frac)) self.prefetch_buf_size = self.total_size_elems - self.active_buf_size # self.num_items_per_set = math.floor(self.total_size_elems / 100) self.num_sets_active_buffer = int(self.active_buf_frac * 100) self.num_sets_prefetch_buffer = 100 - self.num_sets_active_buffer self.current_set = set() self.current_set_id = 0 self.list_of_sets = [] self.read_buffer_set_start_id = 0 self.read_buffer_set_end_id = self.num_sets_active_buffer - 1 self.last_prefetch_start_cycle = -2 self.last_prefetch_end_cycle = -1 # TODO: Check what the correct value is # self.params_set_flag = True
#
[docs] def service_reads(self, incoming_requests_arr_np, incoming_cycles_arr): """ Method to service read requests coming from systolic array in estimate bandwidth mode :param incoming_requests_arr_np: matrix containg address of the memory requsts made from systolic array :param incoming_cycles_arr: list containg cycles at which the memory requsts are made from systolic array :return: A list of out cycles when the requests are serviced """ assert self.params_set_flag, 'Parameters are not set yet' assert incoming_cycles_arr.shape[0] == incoming_requests_arr_np.shape[0], 'Incoming cycles and requests dont match' outcycles = incoming_cycles_arr + self.hit_latency # In estimate mode, operation is stall free. # Therefore its always a hit # The following to track requests and maintain proper state of the buffer for i in range(incoming_requests_arr_np.shape[0]): cycle = int(incoming_cycles_arr[i][0]) requests_this_cycle = incoming_requests_arr_np[i] if not self.first_request_seen: if max(requests_this_cycle) > -1: self.first_request_rcvd_cycle = cycle self.first_request_seen = True for addr in requests_this_cycle: if not addr == -1: self.manage_prefetches(cycle, addr) return outcycles
#
[docs] def manage_prefetches(self, cycle, addr): """ Method to manage prefetches in estimate bandwidth mode :param addr: address of the memory requst made from systolic array :param cycle: cycle at which the memory requst is made from systolic array :return: None """ # If this is a new address, otherwise its a hit if self.check_hit(addr): return if addr not in self.current_set: self.current_set.add(addr) self.elems_current_set += 1 if self.elems_current_set == self.num_items_per_set: self.list_of_sets += [self.current_set] self.current_set = set() self.elems_current_set = 0 self.current_set_id += 1 if self.current_set_id == self.read_buffer_set_end_id + 1: # This should be prefetched if not self.active_buffer_prefetch_done: self.prefetch_bandwidth = self.default_bandwidth self.last_prefetch_end_cycle = self.first_request_rcvd_cycle - 1 - self.backing_buffer.get_latency() cycles_needed = (self.num_sets_prefetch_buffer * self.num_items_per_set) \ / self.prefetch_bandwidth cycles_needed = math.ceil(cycles_needed) self.last_prefetch_start_cycle = self.last_prefetch_end_cycle - cycles_needed + 1 self.prefetch() self.prefetch_buffer_set_start_id =self.read_buffer_set_end_id + 1 self.prefetch_buffer_set_end_id = self.prefetch_buffer_set_start_id + \ self.num_sets_prefetch_buffer - 1 self.active_buffer_prefetch_done = True else: elems_to_prefetch = self.num_sets_prefetch_buffer * self.num_items_per_set cycles_needed = self.last_prefetch_end_cycle - self.last_prefetch_start_cycle + 1 self.prefetch_bandwidth = math.ceil(elems_to_prefetch / cycles_needed) self.prefetch() self.prefetch_buffer_set_start_id += self.num_sets_prefetch_buffer self.prefetch_buffer_set_end_id += self.num_sets_prefetch_buffer self.read_buffer_set_start_id += self.num_sets_prefetch_buffer self.read_buffer_set_end_id += self.num_sets_prefetch_buffer self.last_prefetch_start_cycle = self.last_prefetch_end_cycle +1 self.last_prefetch_end_cycle = cycle
#
[docs] def check_hit(self, addr): """ Method to check if the address is hit or miss in the active read buffer :param addr: Address of the incoming memory request :return: True if address is hit and false if miss """ assert self.params_set_flag, 'Parameters are not set yet' start_set_idx = self.read_buffer_set_start_id end_set_idx = min(self.current_set_id, self.read_buffer_set_end_id + 1) if start_set_idx == end_set_idx: return False for idx in range(start_set_idx, end_set_idx): if addr in self.list_of_sets[idx]: return True return False
#
[docs] def complete_all_prefetches(self): """ Method to complete all the prefetches in estimate bandwidth mode. Prefetch first the active buffer if not done before and then keep prefetching prefetch buffers. :return: None """ assert self.params_set_flag, 'Parameters are not set yet' current_set_elems = list(self.current_set) if len(current_set_elems) > 0: self.list_of_sets += [self.current_set] else: self.current_set_id -= 1 # If there are no elems in this set, dont consider it if not self.active_buffer_prefetch_done: self.prefetch_bandwidth = self.default_bandwidth self.last_prefetch_end_cycle = -1 - self.backing_buffer.get_latency() num_sets_to_prefetch = self.current_set_id + 1 self.num_sets_active_buffer = num_sets_to_prefetch cycles_needed = (num_sets_to_prefetch * self.num_items_per_set) \ / self.prefetch_bandwidth cycles_needed = math.ceil(cycles_needed) self.last_prefetch_start_cycle = self.last_prefetch_end_cycle - cycles_needed + 1 self.prefetch() self.active_buffer_prefetch_done = True else: num_sets_to_prefetch = self.current_set_id - self.prefetch_buffer_set_start_id + 1 self.prefetch_buffer_set_end_id = self.current_set_id elems_to_prefetch = num_sets_to_prefetch * self.num_items_per_set cycles_needed = self.last_prefetch_end_cycle - self.last_prefetch_start_cycle + 1 self.prefetch_bandwidth = math.ceil(elems_to_prefetch / cycles_needed) self.prefetch()
#
[docs] def prefetch(self): """ Method to do a new prefetch. In a new prefetch, some portion of the original data needs to be \ deleted to accomodate the prefetched data In this case we overwrite some data in the active buffer with the prefetched data \ and then create a new prefetch request :return: None """ assert self.params_set_flag, 'Parameters are not set yet' if not self.active_buffer_prefetch_done: start_set_idx = 0 end_set_idx = self.num_sets_active_buffer - 1 else: start_set_idx = self.prefetch_buffer_set_start_id end_set_idx = self.prefetch_buffer_set_end_id all_addresses = [] for idx in range(start_set_idx, end_set_idx + 1): this_set = self.list_of_sets[idx] all_addresses += list(this_set) self.num_access += len(all_addresses) cycles_needed = self.last_prefetch_end_cycle - self.last_prefetch_start_cycle + 1 max_prefetch_capacity = cycles_needed * self.prefetch_bandwidth delta = max_prefetch_capacity - len(all_addresses) if delta > 0: for _ in range(delta): all_addresses += [-1] prefetch_requests = np.asarray(all_addresses).reshape((cycles_needed, self.prefetch_bandwidth)) cycles_arr = np.zeros((cycles_needed,1)) for i in range(cycles_arr.shape[0]): cycles_arr[i][0] = self.last_prefetch_start_cycle + i response_cycles_arr = self.backing_buffer.service_reads(incoming_cycles_arr=cycles_arr, incoming_requests_arr_np=prefetch_requests) # Create / add elements to the trace matrix this_prefetch_traces = np.concatenate((response_cycles_arr, prefetch_requests), axis=1) if not self.trace_valid: self.trace_matrix = this_prefetch_traces self.trace_valid = True else: del_cols = self.trace_matrix.shape[1] - this_prefetch_traces.shape[1] if del_cols > 0: empty_cols = np.ones((this_prefetch_traces.shape[0], del_cols)) this_prefetch_traces = np.concatenate((this_prefetch_traces, empty_cols), axis=1) elif del_cols < 0: del_cols = int(-1 * del_cols) empty_cols = np.ones((self.trace_matrix.shape[0], del_cols)) self.trace_matrix = np.concatenate((self.trace_matrix, empty_cols), axis=1) self.trace_matrix = np.concatenate((self.trace_matrix, this_prefetch_traces), axis=0)
#
[docs] def get_latency(self): """ Method to get hit latency of the read estimate buffer. :return: Hit latency of the read estimate buffer """ assert self.params_set_flag, 'Parameters are not valid' return self.hit_latency
#
[docs] def get_trace_matrix(self): """ Method to get the read estimate buffer trace matrix. It contains addresses requsted by the systolic array and \ the cycles (first column) at which the requests are made. :return: Read estimate buffer trace matrix """ if not self.trace_valid: print('No trace has been generated yet') return return self.trace_matrix
#
[docs] def get_hit_latency(self): """ Method to get hit latency of the read estimate buffer. :return: Hit latency of the read estimate buffer """ return self.hit_latency
#
[docs] def get_num_accesses(self): """ Method to get number of accesses of the read estimate buffer if trace_valid flag is set. :return: Number of accesses of the read estimate buffer """ assert self.trace_valid, 'Traces not ready yet' return self.num_access
#
[docs] def get_external_access_start_stop_cycles(self): """ Method to get start and stop cycles of the read estimate buffer if trace_valid flag is set. :return: Start and stop cycles of the read estimate buffer """ assert self.trace_valid, 'Traces not ready yet' start_cycle = self.trace_matrix[0][0] end_cycle = self.trace_matrix[-1][0] return start_cycle, end_cycle
#
[docs] def print_trace(self, filename): """ Method to write the read estimate buffer trace matrix to a file. :param filename: Name of the trace file :return: None """ if not self.trace_valid: print('No trace has been generated yet') return np.savetxt(filename, self.trace_matrix, fmt='%s', delimiter=",")