#include "abstract_hardware_model.h" #include "cuda-sim/memory.h" #include unsigned mem_access_t::next_access_uid = 0; unsigned warp_inst_t::sm_next_uid = 0; void move_warp( warp_inst_t *&dst, warp_inst_t *&src ) { assert( dst->empty() ); warp_inst_t* temp = dst; dst = src; src = temp; src->clear(); } gpgpu_t::gpgpu_t() { m_global_mem = new memory_space_impl<8192>("global",64*1024); m_param_mem = new memory_space_impl<8192>("param",64*1024); m_tex_mem = new memory_space_impl<8192>("tex",64*1024); m_surf_mem = new memory_space_impl<8192>("surf",64*1024); 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); } 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 = 0; if( m_config->gpgpu_coalesce_arch == 13 ){ warp_parts = 2; // 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); } } else abort(); bank_func = &core_config::null_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 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 *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) { // 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; } } } } } }