summaryrefslogtreecommitdiff
path: root/src/abstract_hardware_model.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/abstract_hardware_model.cc')
-rw-r--r--src/abstract_hardware_model.cc178
1 files changed, 178 insertions, 0 deletions
diff --git a/src/abstract_hardware_model.cc b/src/abstract_hardware_model.cc
index 7b67df9..2134cfc 100644
--- a/src/abstract_hardware_model.cc
+++ b/src/abstract_hardware_model.cc
@@ -24,3 +24,181 @@ gpgpu_t::gpgpu_t()
m_dev_malloc=GLOBAL_HEAP_START;
}
+unsigned core_config::shmem_bank_func(address_type addr, unsigned) const
+{
+ return ((addr/WORD_SIZE) % gpgpu_n_shmem_bank);
+}
+
+unsigned core_config::dcache_bank_func(address_type add, unsigned line_size) const
+{
+ if (gpgpu_no_dl1) return 1; //no banks
+ else return (add / line_size) & (gpgpu_n_cache_bank - 1);
+}
+
+address_type null_tag_func(address_type address, unsigned line_size)
+{
+ return address; //no modification: each address is its own tag.
+}
+
+address_type line_size_based_tag_func(address_type address, unsigned line_size)
+{
+ //gives the tag for an address based on a given line size
+ return ((address) & (~((address_type)line_size - 1)));
+}
+
+void warp_inst_t::get_memory_access_list()
+{
+ // Calculates memory accesses generated by this warp
+ // Returns acesses which are "coalesced"
+ // Does not coalesce nor overlap bank accesses across warp "parts".
+
+ // This is called once per warp_inst_t when the warp_inst_t enters the memory stage.
+ // It produces the set of distinct memory accesses that need to be peformed.
+ // These accessess are then performed over multiple cycles (stalling the pipeline)
+ // if the accessses cannot be performed all at once.
+
+ // In hardware, these accesses would be created at the specific unit handling the type
+ // of memory access. We centralize the logic simply to reduce code duplication.
+
+ // Below, accesses are assigned an "order" based on when that access may be issued.
+ // Accesses with the same order number may occur at the same time: they are to different banks.
+ // Later, when the queue is processed it will evaluate accesses of as many orders as
+ // ports on that cache/shmem.
+ //
+ // Accesses are placed in accessq sorted so that accesses of the same order are adjacent.
+
+ typedef unsigned (core_config::*bank_func_t)(address_type add, unsigned line_size) const;
+ typedef address_type (*tag_func_t)(address_type add, unsigned line_size);
+ bank_func_t bank_func = NULL;
+ tag_func_t tag_func = NULL;
+ unsigned warp_parts = 0;
+ unsigned line_size = 0;
+ bool limit_broadcast = 0;
+ bool global_mem_access = false;
+
+ switch( space.get_type() ) {
+ case shared_space:
+ bank_func = &core_config::shmem_bank_func;
+ tag_func = null_tag_func;
+ warp_parts = m_config->gpgpu_shmem_pipe_speedup;
+ line_size = 1; //shared memory doesn't care about line_size, needs to be at least 1;
+ limit_broadcast = true; // limit broadcasts to single cycle.
+ break;
+ case tex_space:
+ bank_func = &core_config::null_bank_func;
+ tag_func = line_size_based_tag_func;
+ warp_parts = 1;
+ line_size = m_config->gpgpu_cache_texl1_linesize;
+ limit_broadcast = false;
+ break;
+ case const_space: case param_space_kernel:
+ bank_func = &core_config::null_bank_func;
+ tag_func = line_size_based_tag_func;
+ warp_parts = 1;
+ line_size = m_config->gpgpu_cache_constl1_linesize;
+ limit_broadcast = false;
+ break;
+ case global_space: case local_space: case param_space_local:
+ global_mem_access=true;
+ warp_parts = 1;
+ line_size = m_config->gpgpu_cache_dl1_linesize;
+ if( m_config->gpgpu_coalesce_arch == 13 ){
+ warp_parts = 2;
+ if( m_config->gpgpu_no_dl1 ) {
+ // line size is dependant on instruction;
+ switch (data_size) {
+ case 1: line_size = 32; break;
+ case 2: line_size = 64; break;
+ case 4: case 8: case 16: line_size = 128; break;
+ default: assert(0);
+ }
+ }
+ }
+ bank_func = &core_config::dcache_bank_func;
+ tag_func = line_size_based_tag_func;
+ limit_broadcast = false;
+ break;
+ default:
+ abort();
+ }
+
+ // bank_accs tracks bank accesses for sorting into generations;
+ // each entry is (bank #, number of accesses)
+ // the idea is that you can only access a bank a number of times each cycle equal to
+ // its number of ports in one cycle.
+ std::map<unsigned,unsigned> bank_accs;
+
+ // keep track of broadcasts with unique orders if limit_broadcast
+ // the normally calculated orders will never be greater than warp_size
+ unsigned broadcast_order = warp_size();
+ unsigned qbegin = get_accessq_size();
+ unsigned qpartbegin = qbegin;
+ unsigned mem_pipe_size = warp_size() / warp_parts;
+ for (unsigned part = 0; part < warp_size(); part += mem_pipe_size) {
+ for (unsigned i = part; i < part + mem_pipe_size; i++) {
+ if ( !active(i) )
+ continue;
+ new_addr_type addr = get_addr(i);
+ address_type lane_segment_address = tag_func(addr, line_size);
+ unsigned quarter = 0;
+ if( line_size>=4 )
+ quarter = (addr / (line_size/4)) & 3;
+ bool match = false;
+ if( !isatomic() ) { //atomics must have own request
+ for( unsigned j = qpartbegin; j <get_accessq_size(); j++ ) {
+ if (lane_segment_address == accessq(j).addr) {
+ accessq(j).quarter_count[quarter]++;
+ accessq(j).warp_indices.push_back(i);
+ if (limit_broadcast) // two threads access this address, so its a broadcast.
+ accessq(j).order = ++broadcast_order; //do broadcast in its own cycle.
+ match = true;
+ break;
+ }
+ }
+ }
+ if (!match) { // does not match a previous request by another thread, so need a new request
+ assert( space != undefined_space );
+ m_accessq.push_back( mem_access_t( lane_segment_address, line_size, quarter, i) );
+ // Determine Bank Conflicts:
+ unsigned bank = (m_config->*bank_func)(get_addr(i), line_size);
+ // ensure no concurrent bank access accross warp parts.
+ // ie. order will be less than part for all previous loads in previous parts, so:
+ if (bank_accs[bank] < part)
+ bank_accs[bank]=part;
+ accessq_back().order = bank_accs[bank];
+ bank_accs[bank]++;
+ }
+ }
+ qpartbegin = get_accessq_size(); //don't coalesce accross warp parts
+ }
+ //sort requests by order they will be processed in
+ std::stable_sort( m_accessq.begin()+qbegin,m_accessq.end());
+
+ if( global_mem_access ) {
+ // Now that we have the accesses, if we don't have a cache we can adjust request sizes to
+ // include only the data referenced by the threads
+ for (unsigned i = 0; i < get_accessq_size(); i++) {
+ if (m_config->gpgpu_coalesce_arch == 13 && m_config->gpgpu_no_dl1) {
+ // do coalescing here.
+ char* quarter_counts = accessq(i).quarter_count;
+ bool low = quarter_counts[0] or quarter_counts[1];
+ bool high = quarter_counts[2] or quarter_counts[3];
+ if (accessq(i).req_size == 128) {
+ if (low xor high) { //can reduce size
+ accessq(i).req_size = 64;
+ if (high) accessq(i).addr += 64;
+ low = quarter_counts[0] or quarter_counts[2]; //set low and high for next pass
+ high = quarter_counts[1] or quarter_counts[3];
+ }
+ }
+ if (accessq(i).req_size == 64) {
+ if (low xor high) { //can reduce size
+ accessq(i).req_size = 32;
+ if (high) accessq(i).addr += 32;
+ }
+ }
+ }
+ }
+ }
+}
+