// Copyright (c) 2009-2021, Tor M. Aamodt, Inderpreet Singh, Timothy Rogers, // Vijay Kandiah, Nikos Hardavellas, Mahmoud Khairy, Junrui Pan, Timothy G. // Rogers The University of British Columbia, Northwestern University, Purdue // University All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this // list of conditions and the following disclaimer; // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution; // 3. Neither the names of The University of British Columbia, Northwestern // University nor the names of their contributors may be used to // endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "abstract_hardware_model.h" #include #include #include #include #include "../libcuda/gpgpu_context.h" #include "cuda-sim/cuda-sim.h" #include "cuda-sim/memory.h" #include "cuda-sim/ptx-stats.h" #include "cuda-sim/ptx_ir.h" #include "gpgpu-sim/gpu-sim.h" #include "gpgpusim_entrypoint.h" #include "option_parser.h" // Forward declarations needed by compute_unified_shared_cycles below. new_addr_type line_size_based_tag_func(new_addr_type address, new_addr_type line_size); // MEMCO v3 Model B diagnostic globals: count how often the unified shared // bank-conflict computation fired and the cumulative delta vs the per-set // max model. Printed at gpgpu_sim termination. unsigned long long g_memcov3_shared_unified_invocations = 0; unsigned long long g_memcov3_shared_unified_cycles_unified = 0; unsigned long long g_memcov3_shared_unified_cycles_perset_max = 0; void warp_inst_t::compute_unified_shared_cycles() { static const bool enabled = (getenv("MEMCOV3_SHARED_UNIFIED") != NULL); if (!enabled) return; if (empty()) return; if (op != LOAD_OP && op != STORE_OP) return; if (!m_per_scalar_thread_valid) return; // Determine whether any shared work is present in this composite. bool primary_shared = (space.get_type() == shared_space || space.get_type() == sstarr_space); bool any_shared_set = false; for (unsigned s = 0; s < m_simd_sets.size(); s++) { if (!m_simd_sets[s].valid || m_simd_sets[s].source_inst == NULL) continue; enum _memory_space_t sp = m_simd_sets[s].source_inst->space.get_type(); if (sp == shared_space || sp == sstarr_space) { any_shared_set = true; break; } } if (!primary_shared && !any_shared_set) return; // Build a unified bank → word → count map across all participating // active lanes (primary + shared-source coissuer sets). No subwarp // subdivision: Model B assumes a single bank arbiter sees all lanes // concurrently. Bank conflicts are counted across the union. std::map > bank_accs; if (primary_shared) { for (unsigned t = 0; t < m_config->warp_size; t++) { if (!active(t)) continue; new_addr_type addr = m_per_scalar_thread[t].memreqaddr[0]; unsigned bank = m_config->shmem_bank_func(addr); new_addr_type word = line_size_based_tag_func(addr, m_config->WORD_SIZE); bank_accs[bank][word]++; } } for (unsigned s = 0; s < m_simd_sets.size(); s++) { const simd_set_info &si = m_simd_sets[s]; if (!si.valid || si.source_inst == NULL) continue; enum _memory_space_t sp = si.source_inst->space.get_type(); if (sp != shared_space && sp != sstarr_space) continue; if (si.per_thread_addrs.empty()) continue; for (unsigned lane = 0; lane < si.per_thread_addrs.size(); lane++) { if (!si.set_active_mask.test(lane)) continue; new_addr_type addr = si.per_thread_addrs[lane].memreqaddr[0]; unsigned bank = m_config->shmem_bank_func(addr); new_addr_type word = line_size_based_tag_func(addr, m_config->WORD_SIZE); bank_accs[bank][word]++; } } // Find the bank with the most distinct words — that's the unified // bank-conflict cycle count for this composite. unsigned unified_cycles = 0; for (std::map >::iterator b = bank_accs.begin(); b != bank_accs.end(); ++b) { if (b->second.size() > unified_cycles) unified_cycles = b->second.size(); } if (unified_cycles == 0) return; // no active lanes after all // Compute the per-set-max model's value for diagnostic comparison. unsigned perset_max = 0; if (primary_shared && cycles > perset_max) perset_max = cycles; for (unsigned s = 0; s < m_simd_sets.size(); s++) { const simd_set_info &si = m_simd_sets[s]; if (!si.valid || si.source_inst == NULL) continue; enum _memory_space_t sp = si.source_inst->space.get_type(); if (sp != shared_space && sp != sstarr_space) continue; if (si.cycles > perset_max) perset_max = si.cycles; } g_memcov3_shared_unified_invocations++; g_memcov3_shared_unified_cycles_unified += unified_cycles; g_memcov3_shared_unified_cycles_perset_max += perset_max; set_unified_shared_cycles(unified_cycles); // Zero out the per-set + primary cycles so other paths (e.g. Stage 6 // early-release in ldst_unit::cycle, which checks has_dispatch_delay // per set) see "drained" — we'll consume the unified counter in // shared_cycle instead. This avoids per-set cycles staying stuck // nonzero (since shared_cycle no longer decrements them). if (primary_shared) cycles = 0; for (unsigned s = 0; s < m_simd_sets.size(); s++) { simd_set_info &si = m_simd_sets[s]; if (!si.valid || si.source_inst == NULL) continue; enum _memory_space_t sp = si.source_inst->space.get_type(); if (sp == shared_space || sp == sstarr_space) si.cycles = 0; } } void mem_access_t::init(gpgpu_context *ctx) { gpgpu_ctx = ctx; m_uid = ++(gpgpu_ctx->sm_next_access_uid); m_addr = 0; m_req_size = 0; m_source_wid = (unsigned)-1; m_source_split_id = (unsigned)-1; m_source_list.clear(); } void warp_inst_t::issue(const active_mask_t &mask, unsigned warp_id, unsigned long long cycle, int dynamic_warp_id, int sch_id, unsigned long long streamID) { m_warp_active_mask = mask; m_warp_issued_mask = mask; m_uid = ++(m_config->gpgpu_ctx->warp_inst_sm_next_uid); m_streamID = streamID; m_warp_id = warp_id; m_dynamic_warp_id = dynamic_warp_id; issue_cycle = cycle; cycles = initiation_interval; m_cache_hit = false; m_empty = false; m_scheduler_id = sch_id; } void warp_inst_t::compute_simd_sets(unsigned num_sets, unsigned set_width) { m_simd_sets.resize(num_sets); for (unsigned s = 0; s < num_sets; s++) { simd_set_info &info = m_simd_sets[s]; info.set_id = s; info.warp_id = m_warp_id; info.set_active_mask.reset(); info.active_mask_in_warp.reset(); info.num_active_threads = 0; for (unsigned lane = 0; lane < set_width; lane++) { unsigned tid = s * set_width + lane; info.thread_ids[lane] = tid; if (m_warp_active_mask.test(tid)) { info.set_active_mask.set(lane); info.active_mask_in_warp.set(tid); info.num_active_threads++; } } info.valid = (info.num_active_threads > 0); } } void warp_inst_t::compute_simd_sets_compacted(unsigned num_sets, unsigned set_width, unsigned start_set) { m_simd_sets.resize(num_sets); // Initialize all sets as invalid for (unsigned s = 0; s < num_sets; s++) { simd_set_info &info = m_simd_sets[s]; info.set_id = s; info.warp_id = m_warp_id; info.set_active_mask.reset(); info.active_mask_in_warp.reset(); info.num_active_threads = 0; info.valid = false; info.source_inst = NULL; for (unsigned i = 0; i < MAX_WARP_SIZE; i++) info.thread_ids[i] = 0; } // Collect active thread IDs std::vector active_tids; for (unsigned t = 0; t < m_config->warp_size; t++) { if (m_warp_active_mask.test(t)) active_tids.push_back(t); } // Pack active threads contiguously into sets starting at start_set unsigned tid_idx = 0; for (unsigned s = start_set; s < num_sets && tid_idx < active_tids.size(); s++) { simd_set_info &info = m_simd_sets[s]; for (unsigned lane = 0; lane < set_width && tid_idx < active_tids.size(); lane++) { unsigned tid = active_tids[tid_idx]; info.thread_ids[lane] = tid; info.set_active_mask.set(lane); info.active_mask_in_warp.set(tid); info.num_active_threads++; tid_idx++; } info.valid = true; } } void warp_inst_t::compute_simd_sets_xor_static(unsigned num_sets, unsigned set_width, unsigned start_set) { // start_set is unused for XOR-static: the lane permutation is fully // determined by warp_id. Accepted for signature compatibility with // compute_simd_sets_compacted. (void)start_set; m_simd_sets.resize(num_sets); for (unsigned s = 0; s < num_sets; s++) { simd_set_info &info = m_simd_sets[s]; info.set_id = s; info.warp_id = m_warp_id; info.set_active_mask.reset(); info.active_mask_in_warp.reset(); info.num_active_threads = 0; } // xor_key = reverse of the low log2(warp_size) bits of m_warp_id unsigned warp_size = m_config->warp_size; unsigned bits = 0; while ((1u << bits) < warp_size) bits++; unsigned xor_key = 0; for (unsigned i = 0; i < bits; i++) { if (m_warp_id & (1u << i)) xor_key |= (1u << (bits - 1 - i)); } // Physical lane P runs logical thread T = P ^ xor_key. for (unsigned P = 0; P < warp_size; P++) { unsigned T = P ^ xor_key; unsigned s = P / set_width; unsigned lane = P % set_width; simd_set_info &info = m_simd_sets[s]; info.thread_ids[lane] = T; if (m_warp_active_mask.test(T)) { info.set_active_mask.set(lane); info.active_mask_in_warp.set(T); info.num_active_threads++; } } for (unsigned s = 0; s < num_sets; s++) { m_simd_sets[s].valid = (m_simd_sets[s].num_active_threads > 0); } } unsigned warp_inst_t::num_active_simd_sets() const { unsigned count = 0; for (unsigned i = 0; i < m_simd_sets.size(); i++) { if (m_simd_sets[i].valid) count++; } return count; } void warp_inst_t::set_source_inst_on_sets(const inst_t *src) { for (unsigned s = 0; s < m_simd_sets.size(); s++) { if (m_simd_sets[s].valid) { m_simd_sets[s].source_inst = src; } } } void warp_inst_t::set_split_id_on_sets(unsigned split_id) { for (unsigned s = 0; s < m_simd_sets.size(); s++) { if (m_simd_sets[s].valid) { m_simd_sets[s].split_id = split_id; } } } void warp_inst_t::merge_simd_sets( const std::vector &other_sets) { assert(m_simd_sets.size() == other_sets.size()); for (unsigned s = 0; s < other_sets.size(); s++) { if (other_sets[s].valid && !m_simd_sets[s].valid) { m_simd_sets[s] = other_sets[s]; } } } bool warp_inst_t::simd_sets_overlap(const std::vector &a, const std::vector &b) { assert(a.size() == b.size()); for (unsigned s = 0; s < a.size(); s++) { if (a[s].valid && b[s].valid) return true; } return false; } checkpoint::checkpoint() { struct stat st = {0}; if (stat("checkpoint_files", &st) == -1) { mkdir("checkpoint_files", 0777); } } void checkpoint::load_global_mem(class memory_space *temp_mem, char *f1name) { FILE *fp2 = fopen(f1name, "r"); assert(fp2 != NULL); char line[128]; /* or other suitable maximum line size */ unsigned int offset = 0; while (fgets(line, sizeof line, fp2) != NULL) /* read a line */ { unsigned int index; char *pch; pch = strtok(line, " "); if (pch[0] == 'g' || pch[0] == 's' || pch[0] == 'l') { pch = strtok(NULL, " "); std::stringstream ss; ss << std::hex << pch; ss >> index; offset = 0; } else { unsigned int data; std::stringstream ss; ss << std::hex << pch; ss >> data; temp_mem->write_only(offset, index, 4, &data); offset = offset + 4; } // fputs ( line, stdout ); /* write the line */ } fclose(fp2); } void checkpoint::store_global_mem(class memory_space *mem, char *fname, char *format) { FILE *fp3 = fopen(fname, "w"); assert(fp3 != NULL); mem->print(format, fp3); fclose(fp3); } 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(); } void gpgpu_functional_sim_config::reg_options(class OptionParser *opp) { option_parser_register(opp, "-gpgpu_ptx_use_cuobjdump", OPT_BOOL, &m_ptx_use_cuobjdump, "Use cuobjdump to extract ptx and sass from binaries", #if (CUDART_VERSION >= 4000) "1" #else "0" #endif ); option_parser_register(opp, "-gpgpu_experimental_lib_support", OPT_BOOL, &m_experimental_lib_support, "Try to extract code from cuda libraries [Broken " "because of unknown cudaGetExportTable]", "0"); option_parser_register(opp, "-checkpoint_option", OPT_INT32, &checkpoint_option, " checkpointing flag (0 = no checkpoint)", "0"); option_parser_register( opp, "-checkpoint_kernel", OPT_INT32, &checkpoint_kernel, " checkpointing during execution of which kernel (1- 1st kernel)", "1"); option_parser_register( opp, "-checkpoint_CTA", OPT_INT32, &checkpoint_CTA, " checkpointing after # of CTA (< less than total CTA)", "0"); option_parser_register(opp, "-resume_option", OPT_INT32, &resume_option, " resume flag (0 = no resume)", "0"); option_parser_register(opp, "-resume_kernel", OPT_INT32, &resume_kernel, " Resume from which kernel (1= 1st kernel)", "0"); option_parser_register(opp, "-resume_CTA", OPT_INT32, &resume_CTA, " resume from which CTA ", "0"); option_parser_register(opp, "-checkpoint_CTA_t", OPT_INT32, &checkpoint_CTA_t, " resume from which CTA ", "0"); option_parser_register(opp, "-checkpoint_insn_Y", OPT_INT32, &checkpoint_insn_Y, " resume from which CTA ", "0"); option_parser_register( opp, "-gpgpu_ptx_convert_to_ptxplus", OPT_BOOL, &m_ptx_convert_to_ptxplus, "Convert SASS (native ISA) to ptxplus and run ptxplus", "0"); option_parser_register(opp, "-gpgpu_ptx_force_max_capability", OPT_UINT32, &m_ptx_force_max_capability, "Force maximum compute capability", "0"); option_parser_register( opp, "-gpgpu_ptx_inst_debug_to_file", OPT_BOOL, &g_ptx_inst_debug_to_file, "Dump executed instructions' debug information to file", "0"); option_parser_register( opp, "-gpgpu_ptx_inst_debug_file", OPT_CSTR, &g_ptx_inst_debug_file, "Executed instructions' debug output file", "inst_debug.txt"); option_parser_register(opp, "-gpgpu_ptx_inst_debug_thread_uid", OPT_INT32, &g_ptx_inst_debug_thread_uid, "Thread UID for executed instructions' debug output", "1"); } void gpgpu_functional_sim_config::ptx_set_tex_cache_linesize( unsigned linesize) { m_texcache_linesize = linesize; } gpgpu_t::gpgpu_t(const gpgpu_functional_sim_config &config, gpgpu_context *ctx) : m_function_model_config(config) { gpgpu_ctx = ctx; m_global_mem = new memory_space_impl<8192>("global", 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; checkpoint_option = m_function_model_config.get_checkpoint_option(); checkpoint_kernel = m_function_model_config.get_checkpoint_kernel(); checkpoint_CTA = m_function_model_config.get_checkpoint_CTA(); resume_option = m_function_model_config.get_resume_option(); resume_kernel = m_function_model_config.get_resume_kernel(); resume_CTA = m_function_model_config.get_resume_CTA(); checkpoint_CTA_t = m_function_model_config.get_checkpoint_CTA_t(); checkpoint_insn_Y = m_function_model_config.get_checkpoint_insn_Y(); // initialize texture mappings to empty m_NameToTextureInfo.clear(); m_NameToCudaArray.clear(); m_TextureRefToName.clear(); m_NameToAttribute.clear(); if (m_function_model_config.get_ptx_inst_debug_to_file() != 0) ptx_inst_debug_file = fopen(m_function_model_config.get_ptx_inst_debug_file(), "w"); gpu_sim_cycle = 0; gpu_tot_sim_cycle = 0; } new_addr_type line_size_based_tag_func(new_addr_type address, new_addr_type line_size) { // gives the tag for an address based on a given line size return address & ~(line_size - 1); } const char *mem_access_type_str(enum mem_access_type access_type) { #define MA_TUP_BEGIN(X) static const char *access_type_str[] = { #define MA_TUP(X) #X #define MA_TUP_END(X) \ } \ ; MEM_ACCESS_TYPE_TUP_DEF #undef MA_TUP_BEGIN #undef MA_TUP #undef MA_TUP_END assert(access_type < NUM_MEM_ACCESS_TYPE); return access_type_str[access_type]; } void warp_inst_t::clear_active(const active_mask_t &inactive) { active_mask_t test = m_warp_active_mask; test &= inactive; assert(test == inactive); // verify threads being disabled were active m_warp_active_mask &= ~inactive; } void warp_inst_t::set_not_active(unsigned lane_id) { m_warp_active_mask.reset(lane_id); } void warp_inst_t::set_active(const active_mask_t &active) { m_warp_active_mask = active; if (m_isatomic) { for (unsigned i = 0; i < m_config->warp_size; i++) { if (!m_warp_active_mask.test(i)) { m_per_scalar_thread[i].callback.function = NULL; m_per_scalar_thread[i].callback.instruction = NULL; m_per_scalar_thread[i].callback.thread = NULL; } } } } void warp_inst_t::do_atomic(bool forceDo) { do_atomic(m_warp_active_mask, forceDo); } void warp_inst_t::do_atomic(const active_mask_t &access_mask, bool forceDo) { assert(m_isatomic && (!m_empty || forceDo)); if (!should_do_atomic) return; for (unsigned i = 0; i < m_config->warp_size; i++) { if (access_mask.test(i)) { dram_callback_t &cb = m_per_scalar_thread[i].callback; if (cb.thread) cb.function(cb.instruction, cb.thread); } } } void warp_inst_t::broadcast_barrier_reduction( const active_mask_t &access_mask) { for (unsigned i = 0; i < m_config->warp_size; i++) { if (access_mask.test(i)) { dram_callback_t &cb = m_per_scalar_thread[i].callback; if (cb.thread) { cb.function(cb.instruction, cb.thread); } } } } void warp_inst_t::generate_mem_accesses() { if (empty() || op == MEMORY_BARRIER_OP || m_mem_accesses_created) return; if (!((op == LOAD_OP) || (op == TENSOR_CORE_LOAD_OP) || (op == STORE_OP) || (op == TENSOR_CORE_STORE_OP))) return; if (m_warp_active_mask.count() == 0) return; // predicated off const size_t starting_queue_size = m_accessq.size(); assert(is_load() || is_store()); // if((space.get_type() != tex_space) && (space.get_type() != const_space)) assert(m_per_scalar_thread_valid); // need address information per thread bool is_write = is_store(); mem_access_type access_type; switch (space.get_type()) { case const_space: case param_space_kernel: access_type = CONST_ACC_R; break; case tex_space: access_type = TEXTURE_ACC_R; break; case global_space: access_type = is_write ? GLOBAL_ACC_W : GLOBAL_ACC_R; break; case local_space: case param_space_local: access_type = is_write ? LOCAL_ACC_W : LOCAL_ACC_R; break; case shared_space: break; case sstarr_space: break; default: assert(0); break; } // Calculate memory accesses generated by this warp new_addr_type cache_block_size = 0; // in bytes switch (space.get_type()) { case shared_space: case sstarr_space: { unsigned subwarp_size = m_config->warp_size / m_config->mem_warp_parts; unsigned total_accesses = 0; for (unsigned subwarp = 0; subwarp < m_config->mem_warp_parts; subwarp++) { // data structures used per part warp std::map > bank_accs; // bank -> word address -> access count // step 1: compute accesses to words in banks for (unsigned thread = subwarp * subwarp_size; thread < (subwarp + 1) * subwarp_size; thread++) { if (!active(thread)) continue; new_addr_type addr = m_per_scalar_thread[thread].memreqaddr[0]; // FIXME: deferred allocation of shared memory should not accumulate // across kernel launches assert( addr < m_config->gpgpu_shmem_size ); unsigned bank = m_config->shmem_bank_func(addr); new_addr_type word = line_size_based_tag_func(addr, m_config->WORD_SIZE); bank_accs[bank][word]++; } if (m_config->shmem_limited_broadcast) { // step 2: look for and select a broadcast bank/word if one occurs bool broadcast_detected = false; new_addr_type broadcast_word = (new_addr_type)-1; unsigned broadcast_bank = (unsigned)-1; std::map >::iterator b; for (b = bank_accs.begin(); b != bank_accs.end(); b++) { unsigned bank = b->first; std::map &access_set = b->second; std::map::iterator w; for (w = access_set.begin(); w != access_set.end(); ++w) { if (w->second > 1) { // found a broadcast broadcast_detected = true; broadcast_bank = bank; broadcast_word = w->first; break; } } if (broadcast_detected) break; } // step 3: figure out max bank accesses performed, taking account of // broadcast case unsigned max_bank_accesses = 0; for (b = bank_accs.begin(); b != bank_accs.end(); b++) { unsigned bank_accesses = 0; std::map &access_set = b->second; std::map::iterator w; for (w = access_set.begin(); w != access_set.end(); ++w) bank_accesses += w->second; if (broadcast_detected && broadcast_bank == b->first) { for (w = access_set.begin(); w != access_set.end(); ++w) { if (w->first == broadcast_word) { unsigned n = w->second; assert(n > 1); // or this wasn't a broadcast assert(bank_accesses >= (n - 1)); bank_accesses -= (n - 1); break; } } } if (bank_accesses > max_bank_accesses) max_bank_accesses = bank_accesses; } // step 4: accumulate total_accesses += max_bank_accesses; } else { // step 2: look for the bank with the maximum number of access to // different words unsigned max_bank_accesses = 0; std::map >::iterator b; for (b = bank_accs.begin(); b != bank_accs.end(); b++) { max_bank_accesses = std::max(max_bank_accesses, (unsigned)b->second.size()); } // step 3: accumulate total_accesses += max_bank_accesses; } } assert(total_accesses > 0 && total_accesses <= m_config->warp_size); cycles = total_accesses; // shared memory conflicts modeled as larger // initiation interval m_config->gpgpu_ctx->stats->ptx_file_line_stats_add_smem_bank_conflict( pc, total_accesses); break; } case tex_space: cache_block_size = m_config->gpgpu_cache_texl1_linesize; break; case const_space: case param_space_kernel: cache_block_size = m_config->gpgpu_cache_constl1_linesize; break; case global_space: case local_space: case param_space_local: if (m_config->gpgpu_coalesce_arch >= 13) { if (isatomic()) memory_coalescing_arch_atomic(is_write, access_type); else memory_coalescing_arch(is_write, access_type); } else abort(); break; default: abort(); } if (cache_block_size) { assert(m_accessq.empty()); mem_access_byte_mask_t byte_mask; std::map accesses; // block address -> set of thread offsets in warp std::map::iterator a; for (unsigned thread = 0; thread < m_config->warp_size; thread++) { if (!active(thread)) continue; new_addr_type addr = m_per_scalar_thread[thread].memreqaddr[0]; new_addr_type block_address = line_size_based_tag_func(addr, cache_block_size); accesses[block_address].set(thread); unsigned idx = addr - block_address; for (unsigned i = 0; i < data_size; i++) byte_mask.set(idx + i); } for (a = accesses.begin(); a != accesses.end(); ++a) m_accessq.push_back(mem_access_t( access_type, a->first, cache_block_size, is_write, a->second, byte_mask, mem_access_sector_mask_t(), m_config->gpgpu_ctx)); } if (space.get_type() == global_space) { m_config->gpgpu_ctx->stats->ptx_file_line_stats_add_uncoalesced_gmem( pc, m_accessq.size() - starting_queue_size); } m_mem_accesses_created = true; } void warp_inst_t::memory_coalescing_arch(bool is_write, mem_access_type access_type) { // see the CUDA manual where it discusses coalescing rules before reading this unsigned segment_size = 0; unsigned warp_parts = m_config->mem_warp_parts; bool sector_segment_size = false; if (m_config->gpgpu_coalesce_arch >= 20 && m_config->gpgpu_coalesce_arch < 39) { // Fermi and Kepler, L1 is normal and L2 is sector if (m_config->gmem_skip_L1D || cache_op == CACHE_GLOBAL) sector_segment_size = true; else sector_segment_size = false; } else if (m_config->gpgpu_coalesce_arch >= 40) { // Maxwell, Pascal and Volta, L1 and L2 are sectors // all requests should be 32 bytes sector_segment_size = true; } switch (data_size) { case 1: segment_size = 32; break; case 2: segment_size = sector_segment_size ? 32 : 64; break; case 4: case 8: case 16: segment_size = sector_segment_size ? 32 : 128; break; } unsigned subwarp_size = m_config->warp_size / warp_parts; for (unsigned subwarp = 0; subwarp < warp_parts; subwarp++) { std::map subwarp_transactions; // step 1: find all transactions generated by this subwarp for (unsigned thread = subwarp * subwarp_size; thread < subwarp_size * (subwarp + 1); thread++) { if (!active(thread)) continue; unsigned data_size_coales = data_size; unsigned num_accesses = 1; if (space.get_type() == local_space || space.get_type() == param_space_local) { // Local memory accesses >4B were split into 4B chunks if (data_size >= 4) { data_size_coales = 4; num_accesses = data_size / 4; } // Otherwise keep the same data_size for sub-4B access to local memory } assert(num_accesses <= MAX_ACCESSES_PER_INSN_PER_THREAD); // for(unsigned access=0; access::iterator t; for (t = subwarp_transactions.begin(); t != subwarp_transactions.end(); t++) { new_addr_type addr = t->first; const transaction_info &info = t->second; memory_coalescing_arch_reduce_and_send(is_write, access_type, info, addr, segment_size); } } } void warp_inst_t::memory_coalescing_arch_atomic(bool is_write, mem_access_type access_type) { assert(space.get_type() == global_space); // Atomics allowed only for global memory // see the CUDA manual where it discusses coalescing rules before reading this unsigned segment_size = 0; unsigned warp_parts = m_config->mem_warp_parts; bool sector_segment_size = false; if (m_config->gpgpu_coalesce_arch >= 20 && m_config->gpgpu_coalesce_arch < 39) { // Fermi and Kepler, L1 is normal and L2 is sector if (m_config->gmem_skip_L1D || cache_op == CACHE_GLOBAL) sector_segment_size = true; else sector_segment_size = false; } else if (m_config->gpgpu_coalesce_arch >= 40) { // Maxwell, Pascal and Volta, L1 and L2 are sectors // all requests should be 32 bytes sector_segment_size = true; } switch (data_size) { case 1: segment_size = 32; break; case 2: segment_size = sector_segment_size ? 32 : 64; break; case 4: case 8: case 16: segment_size = sector_segment_size ? 32 : 128; break; } unsigned subwarp_size = m_config->warp_size / warp_parts; for (unsigned subwarp = 0; subwarp < warp_parts; subwarp++) { std::map > subwarp_transactions; // each block addr maps to a list of transactions // step 1: find all transactions generated by this subwarp for (unsigned thread = subwarp * subwarp_size; thread < subwarp_size * (subwarp + 1); thread++) { if (!active(thread)) continue; new_addr_type addr = m_per_scalar_thread[thread].memreqaddr[0]; new_addr_type block_address = line_size_based_tag_func(addr, segment_size); unsigned chunk = (addr & 127) / 32; // which 32-byte chunk within in a 128-byte chunk // does this thread access? // can only write to one segment assert(block_address == line_size_based_tag_func(addr + data_size - 1, segment_size)); // Find a transaction that does not conflict with this thread's accesses bool new_transaction = true; std::list::iterator it; transaction_info *info; for (it = subwarp_transactions[block_address].begin(); it != subwarp_transactions[block_address].end(); it++) { unsigned idx = (addr & 127); if (not it->test_bytes(idx, idx + data_size - 1)) { new_transaction = false; info = &(*it); break; } } if (new_transaction) { // Need a new transaction subwarp_transactions[block_address].push_back(transaction_info()); info = &subwarp_transactions[block_address].back(); } assert(info); info->chunks.set(chunk); info->active.set(thread); unsigned idx = (addr & 127); for (unsigned i = 0; i < data_size; i++) { assert(!info->bytes.test(idx + i)); info->bytes.set(idx + i); } } // step 2: reduce each transaction size, if possible std::map >::iterator t_list; for (t_list = subwarp_transactions.begin(); t_list != subwarp_transactions.end(); t_list++) { // For each block addr new_addr_type addr = t_list->first; const std::list &transaction_list = t_list->second; std::list::const_iterator t; for (t = transaction_list.begin(); t != transaction_list.end(); t++) { // For each transaction const transaction_info &info = *t; memory_coalescing_arch_reduce_and_send(is_write, access_type, info, addr, segment_size); } } } } void warp_inst_t::memory_coalescing_arch_reduce_and_send( bool is_write, mem_access_type access_type, const transaction_info &info, new_addr_type addr, unsigned segment_size) { assert((addr & (segment_size - 1)) == 0); const std::bitset<4> &q = info.chunks; assert(q.count() >= 1); std::bitset<2> h; // halves (used to check if 64 byte segment can be // compressed into a single 32 byte segment) unsigned size = segment_size; if (segment_size == 128) { bool lower_half_used = q[0] || q[1]; bool upper_half_used = q[2] || q[3]; if (lower_half_used && !upper_half_used) { // only lower 64 bytes used size = 64; if (q[0]) h.set(0); if (q[1]) h.set(1); } else if ((!lower_half_used) && upper_half_used) { // only upper 64 bytes used addr = addr + 64; size = 64; if (q[2]) h.set(0); if (q[3]) h.set(1); } else { assert(lower_half_used && upper_half_used); } } else if (segment_size == 64) { // need to set halves if ((addr % 128) == 0) { if (q[0]) h.set(0); if (q[1]) h.set(1); } else { assert((addr % 128) == 64); if (q[2]) h.set(0); if (q[3]) h.set(1); } } if (size == 64) { bool lower_half_used = h[0]; bool upper_half_used = h[1]; if (lower_half_used && !upper_half_used) { size = 32; } else if ((!lower_half_used) && upper_half_used) { addr = addr + 32; size = 32; } else { assert(lower_half_used && upper_half_used); } } m_accessq.push_back(mem_access_t(access_type, addr, size, is_write, info.active, info.bytes, info.chunks, m_config->gpgpu_ctx)); } void warp_inst_t::completed(unsigned long long cycle) const { unsigned long long latency = cycle - issue_cycle; assert(latency <= cycle); // underflow detection m_config->gpgpu_ctx->stats->ptx_file_line_stats_add_latency( pc, latency * active_count()); } kernel_info_t::kernel_info_t(dim3 gridDim, dim3 blockDim, class function_info *entry, unsigned long long streamID) { m_kernel_entry = entry; m_grid_dim = gridDim; m_block_dim = blockDim; m_next_cta.x = 0; m_next_cta.y = 0; m_next_cta.z = 0; m_next_tid = m_next_cta; m_num_cores_running = 0; m_uid = (entry->gpgpu_ctx->kernel_info_m_next_uid)++; m_streamID = streamID; m_param_mem = new memory_space_impl<8192>("param", 64 * 1024); // Jin: parent and child kernel management for CDP m_parent_kernel = NULL; // Jin: launch latency management m_launch_latency = entry->gpgpu_ctx->device_runtime->g_kernel_launch_latency; m_kernel_TB_latency = entry->gpgpu_ctx->device_runtime->g_kernel_launch_latency + num_blocks() * entry->gpgpu_ctx->device_runtime->g_TB_launch_latency; cache_config_set = false; } /*A snapshot of the texture mappings needs to be stored in the kernel's info as kernels should use the texture bindings seen at the time of launch and textures can be bound/unbound asynchronously with respect to streams. */ kernel_info_t::kernel_info_t( dim3 gridDim, dim3 blockDim, class function_info *entry, std::map nameToCudaArray, std::map nameToTextureInfo) { m_kernel_entry = entry; m_grid_dim = gridDim; m_block_dim = blockDim; m_next_cta.x = 0; m_next_cta.y = 0; m_next_cta.z = 0; m_next_tid = m_next_cta; m_num_cores_running = 0; m_uid = (entry->gpgpu_ctx->kernel_info_m_next_uid)++; m_param_mem = new memory_space_impl<8192>("param", 64 * 1024); // Jin: parent and child kernel management for CDP m_parent_kernel = NULL; // Jin: launch latency management m_launch_latency = entry->gpgpu_ctx->device_runtime->g_kernel_launch_latency; m_kernel_TB_latency = entry->gpgpu_ctx->device_runtime->g_kernel_launch_latency + num_blocks() * entry->gpgpu_ctx->device_runtime->g_TB_launch_latency; cache_config_set = false; m_NameToCudaArray = nameToCudaArray; m_NameToTextureInfo = nameToTextureInfo; } kernel_info_t::~kernel_info_t() { assert(m_active_threads.empty()); destroy_cta_streams(); delete m_param_mem; } std::string kernel_info_t::name() const { return m_kernel_entry->get_name(); } // Jin: parent and child kernel management for CDP void kernel_info_t::set_parent(kernel_info_t *parent, dim3 parent_ctaid, dim3 parent_tid) { m_parent_kernel = parent; m_parent_ctaid = parent_ctaid; m_parent_tid = parent_tid; parent->set_child(this); } void kernel_info_t::set_child(kernel_info_t *child) { m_child_kernels.push_back(child); } void kernel_info_t::remove_child(kernel_info_t *child) { assert(std::find(m_child_kernels.begin(), m_child_kernels.end(), child) != m_child_kernels.end()); m_child_kernels.remove(child); } bool kernel_info_t::is_finished() { if (done() && children_all_finished()) return true; else return false; } bool kernel_info_t::children_all_finished() { if (!m_child_kernels.empty()) return false; return true; } void kernel_info_t::notify_parent_finished() { if (m_parent_kernel) { m_kernel_entry->gpgpu_ctx->device_runtime->g_total_param_size -= ((m_kernel_entry->get_args_aligned_size() + 255) / 256 * 256); m_parent_kernel->remove_child(this); m_kernel_entry->gpgpu_ctx->the_gpgpusim->g_stream_manager ->register_finished_kernel(m_parent_kernel->get_uid()); } } CUstream_st *kernel_info_t::create_stream_cta(dim3 ctaid) { assert(get_default_stream_cta(ctaid)); CUstream_st *stream = new CUstream_st(); m_kernel_entry->gpgpu_ctx->the_gpgpusim->g_stream_manager->add_stream(stream); assert(m_cta_streams.find(ctaid) != m_cta_streams.end()); assert(m_cta_streams[ctaid].size() >= 1); // must have default stream m_cta_streams[ctaid].push_back(stream); return stream; } CUstream_st *kernel_info_t::get_default_stream_cta(dim3 ctaid) { if (m_cta_streams.find(ctaid) != m_cta_streams.end()) { assert(m_cta_streams[ctaid].size() >= 1); // already created, must have default stream return *(m_cta_streams[ctaid].begin()); } else { m_cta_streams[ctaid] = std::list(); CUstream_st *stream = new CUstream_st(); m_kernel_entry->gpgpu_ctx->the_gpgpusim->g_stream_manager->add_stream( stream); m_cta_streams[ctaid].push_back(stream); return stream; } } bool kernel_info_t::cta_has_stream(dim3 ctaid, CUstream_st *stream) { if (m_cta_streams.find(ctaid) == m_cta_streams.end()) return false; std::list &stream_list = m_cta_streams[ctaid]; if (std::find(stream_list.begin(), stream_list.end(), stream) == stream_list.end()) return false; else return true; } void kernel_info_t::print_parent_info() { if (m_parent_kernel) { printf("Parent %d: \'%s\', Block (%d, %d, %d), Thread (%d, %d, %d)\n", m_parent_kernel->get_uid(), m_parent_kernel->name().c_str(), m_parent_ctaid.x, m_parent_ctaid.y, m_parent_ctaid.z, m_parent_tid.x, m_parent_tid.y, m_parent_tid.z); } } void kernel_info_t::destroy_cta_streams() { printf("Destroy streams for kernel %d: ", get_uid()); size_t stream_size = 0; for (auto s = m_cta_streams.begin(); s != m_cta_streams.end(); s++) { stream_size += s->second.size(); for (auto ss = s->second.begin(); ss != s->second.end(); ss++) m_kernel_entry->gpgpu_ctx->the_gpgpusim->g_stream_manager->destroy_stream( *ss); s->second.clear(); } printf("size %lu\n", stream_size); m_cta_streams.clear(); } simt_stack::simt_stack(unsigned wid, unsigned warpSize, class gpgpu_sim *gpu) { m_warp_id = wid; m_warp_size = warpSize; m_gpu = gpu; reset(); } void simt_stack::reset() { m_stack.clear(); } void simt_stack::launch(address_type start_pc, const simt_mask_t &active_mask) { reset(); simt_stack_entry new_stack_entry; new_stack_entry.m_pc = start_pc; new_stack_entry.m_calldepth = 1; new_stack_entry.m_active_mask = active_mask; new_stack_entry.m_type = STACK_ENTRY_TYPE_NORMAL; m_stack.push_back(new_stack_entry); } void simt_stack::resume(char *fname) { reset(); FILE *fp2 = fopen(fname, "r"); assert(fp2 != NULL); char line[200]; /* or other suitable maximum line size */ while (fgets(line, sizeof line, fp2) != NULL) /* read a line */ { simt_stack_entry new_stack_entry; char *pch; pch = strtok(line, " "); for (unsigned j = 0; j < m_warp_size; j++) { if (pch[0] == '1') new_stack_entry.m_active_mask.set(j); else new_stack_entry.m_active_mask.reset(j); pch = strtok(NULL, " "); } new_stack_entry.m_pc = atoi(pch); pch = strtok(NULL, " "); new_stack_entry.m_calldepth = atoi(pch); pch = strtok(NULL, " "); new_stack_entry.m_recvg_pc = atoi(pch); pch = strtok(NULL, " "); new_stack_entry.m_branch_div_cycle = atoi(pch); pch = strtok(NULL, " "); if (pch[0] == '0') new_stack_entry.m_type = STACK_ENTRY_TYPE_NORMAL; else new_stack_entry.m_type = STACK_ENTRY_TYPE_CALL; m_stack.push_back(new_stack_entry); } fclose(fp2); } const simt_mask_t &simt_stack::get_active_mask() const { assert(m_stack.size() > 0); return m_stack.back().m_active_mask; } void simt_stack::get_pdom_stack_top_info(unsigned *pc, unsigned *rpc) const { assert(m_stack.size() > 0); *pc = m_stack.back().m_pc; *rpc = m_stack.back().m_recvg_pc; } unsigned simt_stack::get_rp() const { assert(m_stack.size() > 0); return m_stack.back().m_recvg_pc; } void simt_stack::print(FILE *fout) const { for (unsigned k = 0; k < m_stack.size(); k++) { simt_stack_entry stack_entry = m_stack[k]; if (k == 0) { fprintf(fout, "w%02d %1u ", m_warp_id, k); } else { fprintf(fout, " %1u ", k); } for (unsigned j = 0; j < m_warp_size; j++) fprintf(fout, "%c", (stack_entry.m_active_mask.test(j) ? '1' : '0')); fprintf(fout, " pc: 0x%03llx", stack_entry.m_pc); if (stack_entry.m_recvg_pc == (unsigned)-1) { fprintf(fout, " rp: ---- tp: %s cd: %2u ", (stack_entry.m_type == STACK_ENTRY_TYPE_CALL ? "C" : "N"), stack_entry.m_calldepth); } else { fprintf(fout, " rp: %4llu tp: %s cd: %2u ", stack_entry.m_recvg_pc, (stack_entry.m_type == STACK_ENTRY_TYPE_CALL ? "C" : "N"), stack_entry.m_calldepth); } if (stack_entry.m_branch_div_cycle != 0) { fprintf(fout, " bd@%6u ", (unsigned)stack_entry.m_branch_div_cycle); } else { fprintf(fout, " "); } m_gpu->gpgpu_ctx->func_sim->ptx_print_insn(stack_entry.m_pc, fout); fprintf(fout, "\n"); } } void simt_stack::print_checkpoint(FILE *fout) const { for (unsigned k = 0; k < m_stack.size(); k++) { simt_stack_entry stack_entry = m_stack[k]; for (unsigned j = 0; j < m_warp_size; j++) fprintf(fout, "%c ", (stack_entry.m_active_mask.test(j) ? '1' : '0')); fprintf(fout, "%llu %d %llu %lld %d ", stack_entry.m_pc, stack_entry.m_calldepth, stack_entry.m_recvg_pc, stack_entry.m_branch_div_cycle, stack_entry.m_type); fprintf(fout, "%d %d\n", m_warp_id, m_warp_size); } } void simt_stack::update(simt_mask_t &thread_done, addr_vector_t &next_pc, address_type recvg_pc, op_type next_inst_op, unsigned next_inst_size, address_type next_inst_pc) { assert(m_stack.size() > 0); assert(next_pc.size() == m_warp_size); simt_mask_t top_active_mask = m_stack.back().m_active_mask; address_type top_recvg_pc = m_stack.back().m_recvg_pc; address_type top_pc = m_stack.back().m_pc; // the pc of the instruction just executed stack_entry_type top_type = m_stack.back().m_type; assert(top_pc == next_inst_pc); assert(top_active_mask.any()); const address_type null_pc = -1; bool warp_diverged = false; address_type new_recvg_pc = null_pc; unsigned num_divergent_paths = 0; std::map divergent_paths; while (top_active_mask.any()) { // extract a group of threads with the same next PC among the active threads // in the warp address_type tmp_next_pc = null_pc; simt_mask_t tmp_active_mask; for (int i = m_warp_size - 1; i >= 0; i--) { if (top_active_mask.test(i)) { // is this thread active? if (thread_done.test(i)) { top_active_mask.reset(i); // remove completed thread from active mask } else if (tmp_next_pc == null_pc) { tmp_next_pc = next_pc[i]; tmp_active_mask.set(i); top_active_mask.reset(i); } else if (tmp_next_pc == next_pc[i]) { tmp_active_mask.set(i); top_active_mask.reset(i); } } } if (tmp_next_pc == null_pc) { assert(!top_active_mask.any()); // all threads done continue; } divergent_paths[tmp_next_pc] = tmp_active_mask; num_divergent_paths++; } address_type not_taken_pc = next_inst_pc + next_inst_size; assert(num_divergent_paths <= 2); for (unsigned i = 0; i < num_divergent_paths; i++) { address_type tmp_next_pc = null_pc; simt_mask_t tmp_active_mask; tmp_active_mask.reset(); if (divergent_paths.find(not_taken_pc) != divergent_paths.end()) { assert(i == 0); tmp_next_pc = not_taken_pc; tmp_active_mask = divergent_paths[tmp_next_pc]; divergent_paths.erase(tmp_next_pc); } else { std::map::iterator it = divergent_paths.begin(); tmp_next_pc = it->first; tmp_active_mask = divergent_paths[tmp_next_pc]; divergent_paths.erase(tmp_next_pc); } // HANDLE THE SPECIAL CASES FIRST if (next_inst_op == CALL_OPS) { // Since call is not a divergent instruction, all threads should have // executed a call instruction assert(num_divergent_paths == 1); simt_stack_entry new_stack_entry; new_stack_entry.m_pc = tmp_next_pc; new_stack_entry.m_active_mask = tmp_active_mask; new_stack_entry.m_branch_div_cycle = m_gpu->gpu_sim_cycle + m_gpu->gpu_tot_sim_cycle; new_stack_entry.m_type = STACK_ENTRY_TYPE_CALL; m_stack.push_back(new_stack_entry); return; } else if (next_inst_op == RET_OPS && top_type == STACK_ENTRY_TYPE_CALL) { // pop the CALL Entry assert(num_divergent_paths == 1); m_stack.pop_back(); assert(m_stack.size() > 0); m_stack.back().m_pc = tmp_next_pc; // set the PC of the stack top entry // to return PC from the call stack; // Check if the New top of the stack is reconverging if (tmp_next_pc == m_stack.back().m_recvg_pc && m_stack.back().m_type != STACK_ENTRY_TYPE_CALL) { assert(m_stack.back().m_type == STACK_ENTRY_TYPE_NORMAL); m_stack.pop_back(); } return; } // discard the new entry if its PC matches with reconvergence PC // that automatically reconverges the entry // If the top stack entry is CALL, dont reconverge. if (tmp_next_pc == top_recvg_pc && (top_type != STACK_ENTRY_TYPE_CALL)) continue; // this new entry is not converging // if this entry does not include thread from the warp, divergence occurs if ((num_divergent_paths > 1) && !warp_diverged) { warp_diverged = true; // modify the existing top entry into a reconvergence entry in the pdom // stack new_recvg_pc = recvg_pc; if (new_recvg_pc != top_recvg_pc) { m_stack.back().m_pc = new_recvg_pc; m_stack.back().m_branch_div_cycle = m_gpu->gpu_sim_cycle + m_gpu->gpu_tot_sim_cycle; m_stack.push_back(simt_stack_entry()); } } // discard the new entry if its PC matches with reconvergence PC if (warp_diverged && tmp_next_pc == new_recvg_pc) continue; // update the current top of pdom stack m_stack.back().m_pc = tmp_next_pc; m_stack.back().m_active_mask = tmp_active_mask; if (warp_diverged) { m_stack.back().m_calldepth = 0; m_stack.back().m_recvg_pc = new_recvg_pc; } else { m_stack.back().m_recvg_pc = top_recvg_pc; } m_stack.push_back(simt_stack_entry()); } assert(m_stack.size() > 0); m_stack.pop_back(); if (warp_diverged) { m_gpu->gpgpu_ctx->stats->ptx_file_line_stats_add_warp_divergence(top_pc, 1); } } void core_t::execute_warp_inst_t(warp_inst_t &inst, unsigned warpId) { for (unsigned t = 0; t < m_warp_size; t++) { if (inst.active(t)) { if (warpId == (unsigned(-1))) warpId = inst.warp_id(); unsigned tid = m_warp_size * warpId + t; m_thread[tid]->ptx_exec_inst(inst, t); // virtual function checkExecutionStatusAndUpdate(inst, t, tid); } } } bool core_t::ptx_thread_done(unsigned hw_thread_id) const { return ((m_thread[hw_thread_id] == NULL) || m_thread[hw_thread_id]->is_done()); } void core_t::updateSIMTStack(unsigned warpId, warp_inst_t *inst) { simt_mask_t thread_done; addr_vector_t next_pc; unsigned wtid = warpId * m_warp_size; for (unsigned i = 0; i < m_warp_size; i++) { if (ptx_thread_done(wtid + i)) { thread_done.set(i); next_pc.push_back((address_type)-1); } else { if (inst->reconvergence_pc == RECONVERGE_RETURN_PC) inst->reconvergence_pc = get_return_pc(m_thread[wtid + i]); next_pc.push_back(m_thread[wtid + i]->get_pc()); } } m_simt_stack[warpId]->update(thread_done, next_pc, inst->reconvergence_pc, inst->op, inst->isize, inst->pc); } //! Get the warp to be executed using the data taken form the SIMT stack warp_inst_t core_t::getExecuteWarp(unsigned warpId) { unsigned pc, rpc; if (m_gpu->simd_model() == POST_DOMINATOR) { m_simt_stack[warpId]->get_pdom_stack_top_info(&pc, &rpc); warp_inst_t wi = *(m_gpu->gpgpu_ctx->ptx_fetch_inst(pc)); wi.set_active(m_simt_stack[warpId]->get_active_mask()); return wi; } else { m_simt_tables[warpId]->get_pdom_active_split_info(&pc, &rpc); warp_inst_t wi = *(m_gpu->gpgpu_ctx->ptx_fetch_inst(pc)); wi.set_active(m_simt_tables[warpId]->get_active_mask()); return wi; } } void core_t::deleteSIMTStack() { if (m_simt_stack) { for (unsigned i = 0; i < m_warp_count; ++i) delete m_simt_stack[i]; delete[] m_simt_stack; m_simt_stack = NULL; } } void core_t::initilizeSIMTStack(unsigned warp_count, unsigned warp_size) { m_simt_stack = new simt_stack *[warp_count]; for (unsigned i = 0; i < warp_count; ++i) m_simt_stack[i] = new simt_stack(i, warp_size, m_gpu); m_warp_size = warp_size; m_warp_count = warp_count; } void core_t::get_pdom_stack_top_info(unsigned warpId, unsigned *pc, unsigned *rpc) const { if (m_gpu->simd_model() == POST_DOMINATOR) { m_simt_stack[warpId]->get_pdom_stack_top_info(pc, rpc); } else { m_simt_tables[warpId]->get_pdom_active_split_info(pc, rpc); } } // ── ITS (AWARE Reconvergence) implementations ─────────────────────────────── #define MAX_VIRTUAL_ST_ENTRIES 32 #define MAX_VIRTUAL_RT_ENTRIES 32 // ── simt_splits_table ──────────────────────────────────────────────────────── simt_splits_table::simt_splits_table(unsigned wid, unsigned warpSize, const shader_core_config *config, const struct memory_config *mem_config, simt_tables *simt_table) { m_warp_id = wid; m_warp_size = warpSize; m_active_split = (unsigned)-1; m_num_entries = 0; m_num_physical_entries = 0; m_num_transient_entries = 0; m_max_st_size = config->num_st_entries; m_warp_size = config->warp_size; m_config = config; m_mem_config = mem_config; m_response_st_entry = -1; m_spill_st_entry = warp_inst_t(config); m_fill_st_entry = warp_inst_t(config); m_spill_st_entry.clear(); m_fill_st_entry.clear(); m_simt_tables = simt_table; m_pending_recvg_entry = simt_splits_table_entry(); reset(); } void simt_splits_table::reset() { m_splits_table.clear(); for (unsigned i = 0; i < MAX_VIRTUAL_ST_ENTRIES; i++) m_splits_table[i] = simt_splits_table_entry(); while (!m_fifo_queue.empty()) m_fifo_queue.pop_front(); while (!m_invalid_entries.empty()) m_invalid_entries.pop(); for (int i = MAX_VIRTUAL_ST_ENTRIES - 1; i >= 0; i--) m_invalid_entries.push(i); for (int i = m_warp_size; i >= 0; i--) m_available_v_id.push(i); } void simt_splits_table::launch(address_type start_pc, const simt_mask_t &active_mask) { reset(); assert(!m_splits_table[0].m_valid); m_splits_table[0].m_pc = start_pc; m_splits_table[0].m_calldepth = 1; m_splits_table[0].m_active_mask = active_mask; m_splits_table[0].m_type = SPLITS_TABLE_TYPE_CALL; m_splits_table[0].m_valid = true; assert(m_invalid_entries.top() == 0); m_invalid_entries.pop(); m_active_split = 0; m_num_entries = 1; m_num_physical_entries = 1; assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_ST_ENTRIES); unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; m_fifo_queue.push_back( fifo_entry(0, gpgpusim_total_cycles, m_fifo_queue.size())); } const simt_mask_t &simt_splits_table::get_active_mask() { assert(m_splits_table.find(m_active_split) != m_splits_table.end()); assert(m_splits_table[m_active_split].m_valid); assert(m_splits_table[m_active_split].m_active_mask.any()); return m_splits_table[m_active_split].m_active_mask; } const simt_mask_t &simt_splits_table::get_active_mask(unsigned num) { assert(m_splits_table.find(num) != m_splits_table.end()); return m_splits_table[num].m_active_mask; } // SIMD lane partitioning: secondary split access and targeted operations bool simt_splits_table::get_secondary_split_info(unsigned *pc, unsigned *rpc, unsigned *split_id, simt_mask_t *mask) const { if (m_fifo_queue.size() < 2) return false; auto it = m_fifo_queue.begin(); ++it; // second entry unsigned sec_entry = it->m_st_entry; auto st_it = m_splits_table.find(sec_entry); if (st_it == m_splits_table.end() || !st_it->second.m_valid) return false; if (st_it->second.m_blocked || st_it->second.m_virtual) return false; *pc = st_it->second.m_pc; *rpc = st_it->second.m_recvg_pc; *split_id = sec_entry; *mask = st_it->second.m_active_mask; return true; } bool simt_splits_table::is_split_valid(unsigned split_id) const { auto it = m_splits_table.find(split_id); return it != m_splits_table.end() && it->second.m_valid; } void simt_splits_table::get_split_info(unsigned split_id, unsigned *pc, simt_mask_t *mask) const { auto it = m_splits_table.find(split_id); assert(it != m_splits_table.end() && it->second.m_valid); *pc = it->second.m_pc; *mask = it->second.m_active_mask; } void simt_splits_table::update_pc_for_split(unsigned split_id, address_type new_pc) { assert(m_splits_table.find(split_id) != m_splits_table.end()); assert(m_splits_table[split_id].m_valid); static const bool dbg_pc_enabled = (getenv("MEMCO_DBG_PC") != NULL); if (dbg_pc_enabled && m_warp_id == 0) { fprintf(stderr, "[SPLIT_PC_UPDATE] cycle=%llu warp=%u split_id=%u " "old_pc=0x%lx new_pc=0x%lx\n", (unsigned long long)(GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle), m_warp_id, split_id, (unsigned long)m_splits_table[split_id].m_pc, (unsigned long)new_pc); fflush(stderr); } m_splits_table[split_id].m_pc = new_pc; } bool simt_splits_table::move_split_to_front(unsigned split_id) { if (m_fifo_queue.empty()) return false; if (m_fifo_queue.front().m_st_entry == split_id) { m_active_split = split_id; return true; // already at front } // Find and remove from current position fifo_entry target; bool found = false; for (auto it = m_fifo_queue.begin(); it != m_fifo_queue.end(); ++it) { if (it->m_st_entry == (int)split_id) { target = *it; m_fifo_queue.erase(it); found = true; break; } } if (!found) return false; // Insert at front and make active m_fifo_queue.push_front(target); m_active_split = split_id; return true; } void simt_splits_table::get_pdom_splits_entry_info(unsigned num, unsigned *pc, unsigned *rpc) { assert((m_splits_table.find(num) != m_splits_table.end()) && m_splits_table[0].m_valid); *pc = m_splits_table[num].m_pc; *rpc = m_splits_table[num].m_recvg_pc; } void simt_splits_table::get_pdom_active_split_info(unsigned *pc, unsigned *rpc) { assert(m_splits_table.find(m_active_split) != m_splits_table.end()); assert(m_splits_table[m_active_split].m_valid); *pc = m_splits_table[m_active_split].m_pc; *rpc = m_splits_table[m_active_split].m_recvg_pc; } unsigned simt_splits_table::get_rpc(unsigned num) { assert(m_splits_table.find(num) != m_splits_table.end()); return m_splits_table[num].m_recvg_pc; } void simt_splits_table::set_shader(shader_core_ctx *shader) { m_shader = shader; } bool simt_splits_table::is_virtualized() { return m_splits_table[m_active_split].m_virtual; } unsigned simt_splits_table::address_to_entry(warp_inst_t inst) { if (!inst.empty()) { unsigned wid = inst.warp_id(); address_type addr = inst.pc; unsigned entry = (addr - BRU_VIR_START - (wid * m_config->warp_size) * MAX_BRU_VIR_PER_SPLIT) / (MAX_BRU_VIR_PER_SPLIT); return entry; } return (unsigned)-1; } bool simt_splits_table::blocked() { return m_splits_table[m_active_split].m_blocked; } bool simt_splits_table::push_to_st_response_fifo(unsigned entry) { if (m_response_st_entry == -1) { m_response_st_entry = entry; return true; } return false; } unsigned simt_splits_table::get_replacement_candidate() { unsigned entry = (unsigned)-1; for (unsigned i = m_fifo_queue.size() - 1; i >= 0; i--) { fifo_entry replacement_candidate = m_fifo_queue[i]; entry = replacement_candidate.m_st_entry; if (!m_splits_table[entry].m_virtual) break; } assert(entry != (unsigned)-1); return entry; } bool simt_splits_table::spill_st_entry() { if (!m_spill_st_entry.empty()) return false; assert(m_spill_st_entry.empty()); unsigned entry_to_replace = get_replacement_candidate(); assert(!m_splits_table[entry_to_replace].m_virtual); m_splits_table[entry_to_replace].m_virtual = true; m_num_physical_entries--; assert(m_num_physical_entries != (unsigned)-1); address_type pc = (m_warp_id * m_warp_size + entry_to_replace) * MAX_BRU_VIR_PER_SPLIT; address_type ppc = pc + BRU_VIR_START; unsigned nbytes = 12; unsigned offset_in_block = pc & (m_config->m_L1D_config.get_line_sz() - 1); if ((offset_in_block + nbytes) > m_config->m_L1I_config.get_line_sz()) nbytes = (m_config->m_L1D_config.get_line_sz() - offset_in_block); mem_access_t acc(BRU_ST_SPILL, ppc, nbytes, true, GPGPU_Context()); m_spill_st_entry.space = memory_space_t(local_space); m_spill_st_entry.cache_op = CACHE_WRITE_BACK; m_spill_st_entry.op = STORE_OP; m_spill_st_entry.mem_op = NOT_TEX; m_spill_st_entry.memory_op = bru_st_spill_request; m_spill_st_entry.pc = ppc; m_spill_st_entry.occupy(); m_spill_st_entry.set_warp_id(m_warp_id); m_spill_st_entry.set_active(0); m_spill_st_entry.inject_mem_acccesses(acc); return true; } bool simt_splits_table::fill_st_entry(unsigned entry) { if (!m_fill_st_entry.empty()) return false; address_type pc = (m_warp_id * m_warp_size + entry) * MAX_BRU_VIR_PER_SPLIT; address_type ppc = pc + BRU_VIR_START; unsigned nbytes = 12; unsigned offset_in_block = pc & (m_config->m_L1D_config.get_line_sz() - 1); if ((offset_in_block + nbytes) > m_config->m_L1I_config.get_line_sz()) nbytes = (m_config->m_L1D_config.get_line_sz() - offset_in_block); mem_access_t acc(BRU_ST_FILL, ppc, nbytes, false, GPGPU_Context()); m_fill_st_entry.space = memory_space_t(local_space); m_fill_st_entry.cache_op = CACHE_ALL; m_fill_st_entry.op = LOAD_OP; m_fill_st_entry.mem_op = NOT_TEX; m_fill_st_entry.memory_op = bru_st_fill_request; m_fill_st_entry.pc = ppc; m_fill_st_entry.inject_mem_acccesses(acc); m_fill_st_entry.occupy(); m_fill_st_entry.set_warp_id(m_warp_id); m_fill_st_entry.set_active(0); m_splits_table[entry].m_transient = true; m_num_transient_entries++; return true; } void simt_splits_table::cycle() { if (!m_spill_st_entry.empty()) { enum mem_stage_stall_type rc_fail = NO_RC_FAIL; mem_stage_access_type type; bool done = m_shader->memory_cycle(m_spill_st_entry, rc_fail, type); if (done) { m_spill_st_entry.clear(); m_spill_st_entry.clear_pending_mem_requests(); } } if (!m_fill_st_entry.empty()) { enum mem_stage_stall_type rc_fail = NO_RC_FAIL; mem_stage_access_type type; bool done = m_shader->memory_cycle(m_fill_st_entry, rc_fail, type); if (done) { m_fill_st_entry.clear(); m_fill_st_entry.clear_pending_mem_requests(); } } if (m_response_st_entry != -1) { if (m_num_physical_entries == m_max_st_size) { bool spilled = spill_st_entry(); if (spilled) { assert(m_num_physical_entries < m_max_st_size); m_splits_table[m_response_st_entry].m_virtual = false; m_splits_table[m_response_st_entry].m_transient = false; m_num_transient_entries--; m_num_physical_entries++; m_response_st_entry = -1; } } else { assert(m_num_physical_entries < m_max_st_size); m_splits_table[m_response_st_entry].m_virtual = false; m_splits_table[m_response_st_entry].m_transient = false; m_num_transient_entries--; m_num_physical_entries++; m_response_st_entry = -1; } } if (m_pending_recvg_entry.m_valid) { unsigned entry = insert_new_entry(m_pending_recvg_entry); if (entry != (unsigned)-1) m_pending_recvg_entry.m_valid = false; } if (m_splits_table[m_active_split].m_blocked || (!m_splits_table[m_active_split].m_valid && m_fifo_queue.size() > 0)) push_back(); if (m_splits_table[m_active_split].m_virtual && !m_splits_table[m_active_split].m_transient) fill_st_entry(m_active_split); } unsigned simt_splits_table::insert_new_entry(simt_splits_table_entry entry, bool recvged) { unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; if (recvged) { if (m_num_physical_entries == m_max_st_size) { assert(!m_pending_recvg_entry.m_valid); m_pending_recvg_entry = entry; m_pending_recvg_entry.m_valid = true; m_pending_recvg_entry.m_virtual = false; m_pending_recvg_entry.m_branch_div_cycle = gpgpusim_total_cycles; return (unsigned)-1; } } else { if (m_num_physical_entries == m_max_st_size) { bool spilled = spill_st_entry(); if (!spilled) return (unsigned)-1; } } assert(m_num_physical_entries < m_max_st_size); unsigned entry_num = m_invalid_entries.top(); m_invalid_entries.pop(); assert(!m_splits_table[entry_num].m_valid); AWARE_DPRINTF("Inserting new entry to Splits Table: Entry %d\tPC %d\tRPC %d\n", entry_num, entry.m_pc, entry.m_recvg_pc); m_splits_table[entry_num].m_pc = entry.m_pc; m_splits_table[entry_num].m_recvg_pc = entry.m_recvg_pc; m_splits_table[entry_num].m_recvg_entry = entry.m_recvg_entry; m_splits_table[entry_num].m_active_mask = entry.m_active_mask; m_splits_table[entry_num].m_valid = true; m_splits_table[entry_num].m_virtual = false; m_splits_table[entry_num].m_type = entry.m_type; m_splits_table[entry_num].m_branch_div_cycle = entry.m_branch_div_cycle; m_num_entries++; m_num_physical_entries++; assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_ST_ENTRIES); assert(entry_num != (unsigned)-1); m_fifo_queue.push_back( fifo_entry(entry_num, gpgpusim_total_cycles, m_fifo_queue.size())); static const bool dbg_pc_enabled = (getenv("MEMCO_DBG_PC") != NULL); if (dbg_pc_enabled && m_warp_id == 0) { fprintf(stderr, "[SPLIT_INSERT-A] cycle=%llu warp=%u split_id=%u pc=0x%lx mask=%s\n", gpgpusim_total_cycles, m_warp_id, entry_num, (unsigned long)entry.m_pc, entry.m_active_mask.to_string().c_str()); fflush(stderr); } return entry_num; } unsigned simt_splits_table::insert_new_entry( address_type pc, address_type rpc, unsigned rpc_entry, const simt_mask_t &tmp_active_mask, splits_table_entry_type type, bool recvged) { unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; if (recvged) { if (m_num_physical_entries == m_max_st_size) { assert(!m_pending_recvg_entry.m_valid); m_pending_recvg_entry.m_valid = true; m_pending_recvg_entry.m_pc = pc; m_pending_recvg_entry.m_recvg_pc = rpc; m_pending_recvg_entry.m_recvg_entry = rpc_entry; m_pending_recvg_entry.m_active_mask = tmp_active_mask; m_pending_recvg_entry.m_virtual = false; m_pending_recvg_entry.m_type = type; m_pending_recvg_entry.m_branch_div_cycle = gpgpusim_total_cycles; return (unsigned)-1; } } else { if (m_num_physical_entries == m_max_st_size) { bool spilled = spill_st_entry(); if (!spilled) return (unsigned)-1; } } unsigned entry = m_invalid_entries.top(); m_invalid_entries.pop(); assert(!m_splits_table[entry].m_valid); AWARE_DPRINTF("Inserting new entry to Splits Table: Entry %d\tPC %d\tRPC %d\n", entry, pc, rpc); m_splits_table[entry].m_pc = pc; m_splits_table[entry].m_recvg_pc = rpc; m_splits_table[entry].m_recvg_entry = rpc_entry; m_splits_table[entry].m_active_mask = tmp_active_mask; m_splits_table[entry].m_valid = true; m_splits_table[entry].m_type = type; m_num_entries++; m_num_physical_entries++; assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_ST_ENTRIES); assert(entry != (unsigned)-1); m_fifo_queue.push_back( fifo_entry(entry, gpgpusim_total_cycles, m_fifo_queue.size())); static const bool dbg_pc_enabled = (getenv("MEMCO_DBG_PC") != NULL); if (dbg_pc_enabled && m_warp_id == 0) { fprintf(stderr, "[SPLIT_INSERT] cycle=%llu warp=%u split_id=%u pc=0x%lx mask=%s\n", gpgpusim_total_cycles, m_warp_id, entry, (unsigned long)pc, tmp_active_mask.to_string().c_str()); fflush(stderr); } return entry; } unsigned simt_splits_table::insert_new_entry( address_type pc, address_type rpc, unsigned rpc_entry, const simt_mask_t &tmp_active_mask, splits_table_entry_type type, bool call_ret, bool recvged) { unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; if (recvged) { if (m_num_physical_entries == m_max_st_size) { assert(!m_pending_recvg_entry.m_valid); m_pending_recvg_entry.m_valid = true; m_pending_recvg_entry.m_pc = pc; m_pending_recvg_entry.m_recvg_pc = rpc; m_pending_recvg_entry.m_recvg_entry = rpc_entry; m_pending_recvg_entry.m_active_mask = tmp_active_mask; m_pending_recvg_entry.m_virtual = false; m_pending_recvg_entry.m_type = type; m_pending_recvg_entry.m_branch_div_cycle = gpgpusim_total_cycles; return (unsigned)-1; } } else { if (m_num_physical_entries == m_max_st_size) { bool spilled = spill_st_entry(); if (!spilled) return (unsigned)-1; } } unsigned entry = m_invalid_entries.top(); m_invalid_entries.pop(); assert(!m_splits_table[entry].m_valid); m_splits_table[entry].m_pc = pc; m_splits_table[entry].m_recvg_pc = rpc; m_splits_table[entry].m_recvg_entry = rpc_entry; m_splits_table[entry].m_active_mask = tmp_active_mask; m_splits_table[entry].m_valid = true; m_splits_table[entry].m_type = type; m_num_entries++; m_num_physical_entries++; assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_ST_ENTRIES); assert(entry != (unsigned)-1); AWARE_DPRINTF("Inserting new entry to Splits Table: Entry %d\tPC %d\tRPC %d\n", entry, pc, rpc); // call_ret=true: push to front for LIFO ordering m_fifo_queue.push_front( fifo_entry(entry, gpgpusim_total_cycles, m_fifo_queue.size())); static const bool dbg_pc_enabled = (getenv("MEMCO_DBG_PC") != NULL); if (dbg_pc_enabled && m_warp_id == 0) { fprintf(stderr, "[SPLIT_INSERT-C] cycle=%llu warp=%u split_id=%u pc=0x%lx mask=%s\n", gpgpusim_total_cycles, m_warp_id, entry, (unsigned long)pc, tmp_active_mask.to_string().c_str()); fflush(stderr); } return entry; } void simt_splits_table::push_back() { unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; fifo_entry cur_active_split = m_fifo_queue.front(); assert(cur_active_split.m_st_entry == m_active_split); m_fifo_queue.pop_front(); cur_active_split.update_insertion_cycle(gpgpusim_total_cycles, m_fifo_queue.size()); m_fifo_queue.push_back(cur_active_split); fifo_entry new_active_split = m_fifo_queue.front(); m_active_split = new_active_split.m_st_entry; AWARE_DPRINTF("Push current active split %d to back; New active split %d\n", cur_active_split.m_st_entry, m_active_split); if (m_splits_table[m_active_split].m_virtual) { if (!m_splits_table[m_active_split].m_transient && !m_splits_table[m_active_split].m_blocked) fill_st_entry(m_active_split); } } void simt_splits_table::push_back_once() { // stub: same as push_back for now push_back(); } void simt_splits_table::update_active_entry() { if (m_fifo_queue.size() > 0) { fifo_entry new_active_entry = m_fifo_queue.front(); m_active_split = new_active_entry.m_st_entry; if (new_active_entry.m_blocked || m_splits_table[m_active_split].m_virtual) push_back(); assert(m_num_entries > 0); assert(m_splits_table.find(m_active_split) != m_splits_table.end()); assert(m_splits_table[m_active_split].m_valid); assert(m_splits_table[m_active_split].m_active_mask.any()); } } void simt_splits_table::invalidate() { if (!m_splits_table[m_active_split].m_valid) return; assert(m_splits_table.find(m_active_split) != m_splits_table.end()); assert(m_splits_table[m_active_split].m_valid); assert(m_fifo_queue.front().m_st_entry == m_active_split); assert(!m_splits_table[m_active_split].m_virtual); m_splits_table[m_active_split].m_valid = false; m_fifo_queue.pop_front(); m_num_entries--; m_num_physical_entries--; assert(m_num_physical_entries != (unsigned)-1); m_invalid_entries.push(m_active_split); assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_ST_ENTRIES); } void simt_splits_table::update_pc(address_type new_pc) { assert(m_splits_table.find(m_active_split) != m_splits_table.end()); assert(m_splits_table[m_active_split].m_valid); assert(m_fifo_queue.front().m_st_entry == m_active_split); m_splits_table[m_active_split].m_pc = new_pc; } void simt_splits_table::set_to_blocked() { m_splits_table[m_active_split].m_blocked = true; } void simt_splits_table::unset_blocked() { m_splits_table[m_active_split].m_blocked = false; } void simt_splits_table::unset_blocked(unsigned entry) { m_splits_table[entry].m_blocked = false; } void simt_splits_table::release_blocked() { for (unsigned i = 0; i < MAX_VIRTUAL_ST_ENTRIES; i++) if (m_splits_table[i].m_valid) unset_blocked(i); for (unsigned i = 0; i < m_fifo_queue.size(); i++) m_fifo_queue[i].m_blocked = false; } bool simt_splits_table::is_blocked_or_virtual() { bool blocked = true; for (unsigned i = 0; i < MAX_VIRTUAL_ST_ENTRIES; i++) if (m_splits_table[i].m_valid) blocked &= (m_splits_table[i].m_blocked || m_splits_table[i].m_virtual); return (blocked && m_num_entries > 0); } bool simt_splits_table::is_virtual() { bool virtualized = true; for (unsigned i = 0; i < MAX_VIRTUAL_ST_ENTRIES; i++) if (m_splits_table[i].m_valid) virtualized &= (m_splits_table[i].m_virtual); return (virtualized && m_num_entries > 0); } bool simt_splits_table::is_blocked() { bool blocked = true; for (unsigned i = 0; i < MAX_VIRTUAL_ST_ENTRIES; i++) if (m_splits_table[i].m_valid) blocked &= (m_splits_table[i].m_blocked); return (blocked && m_num_entries > 0); } bool simt_splits_table::split_reaches_barrier(address_type pc) { m_fifo_queue.front().m_blocked = true; set_to_blocked(); update_pc(pc); return is_blocked(); } unsigned simt_splits_table::get_rpc() { assert(m_splits_table.find(m_active_split) != m_splits_table.end()); return m_splits_table[m_active_split].m_recvg_pc; } unsigned simt_splits_table::get_rpc_entry() { assert(m_splits_table.find(m_active_split) != m_splits_table.end()); return m_splits_table[m_active_split].m_recvg_entry; } unsigned simt_splits_table::get_pc() { assert(m_splits_table.find(m_active_split) != m_splits_table.end()); return m_splits_table[m_active_split].m_pc; } splits_table_entry_type simt_splits_table::get_type() { assert(m_splits_table.find(m_active_split) != m_splits_table.end()); return m_splits_table[m_active_split].m_type; } bool simt_splits_table::valid() { assert(m_splits_table.find(m_active_split) != m_splits_table.end()); return m_splits_table[m_active_split].m_valid; } unsigned simt_splits_table::check_simt_splits_table() { unsigned count = 0; for (unsigned i = 0; i < MAX_VIRTUAL_ST_ENTRIES; i++) if (m_splits_table[i].m_valid) for (unsigned j = 0; j < m_warp_size; j++) if (m_splits_table[i].m_active_mask.test(j)) count++; return count; } void simt_splits_table::print(FILE *fout) { printf("max of physical entries = %u\n", m_max_st_size); printf("num of physical entries = %u\n", m_num_physical_entries); printf("isBlocked? %u\n", is_blocked()); if (!m_fifo_queue.empty()) { fprintf(fout, "fifo- f: %02d\n", m_fifo_queue.front().m_st_entry); fprintf(fout, "fifo- b: %02d\n", m_fifo_queue.back().m_st_entry); fprintf(fout, "fifo-sz: %02lu\n", m_fifo_queue.size()); } printf("active entry = %u\n", m_active_split); printf("Pending Recvg Entry valid: %u\n", m_pending_recvg_entry.m_valid); printf("Response Entry: %d\n", m_response_st_entry); for (unsigned k = 0; k < MAX_VIRTUAL_ST_ENTRIES; k++) { simt_splits_table_entry &ste = m_splits_table[k]; if (!ste.m_valid) continue; if (ste.m_virtual) printf("Virtual: "); if (ste.m_transient) printf("Transient: "); if (ste.m_blocked) printf("Blocked: "); fprintf(fout, " %1u ", k); for (unsigned j = 0; j < m_warp_size; j++) fprintf(fout, "%c", (ste.m_active_mask.test(j) ? '1' : '0')); fprintf(fout, " pc: 0x%03x", ste.m_pc); if (ste.m_recvg_pc == (unsigned)-1) fprintf(fout, " rp: ---- %s\n", (ste.m_valid ? " V " : " N ")); else fprintf(fout, " rp: 0x%03x %s\n", ste.m_recvg_pc, (ste.m_valid ? " V " : " N ")); } } // ── simt_reconvergence_table ───────────────────────────────────────────────── simt_reconvergence_table::simt_reconvergence_table( unsigned wid, unsigned warpSize, const shader_core_config *config, const struct memory_config *mem_config, simt_tables *simt_table) { m_warp_id = wid; m_warp_size = warpSize; m_active_reconvergence = (unsigned)-1; m_num_entries = 0; m_num_physical_entries = 0; m_num_transient_entries = 0; m_max_rec_size = config->num_rec_entries; m_simt_tables = simt_table; m_spill_rec_entry = warp_inst_t(config); m_fill_rec_entry = warp_inst_t(config); m_pending_update_entry = simt_reconvergence_table_entry(); m_response_rec_entry = -1; m_config = config; m_mem_config = mem_config; reset(); assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_RT_ENTRIES); } void simt_reconvergence_table::reset() { m_num_entries = 0; m_num_physical_entries = 0; m_recvg_table.clear(); while (!m_invalid_entries.empty()) m_invalid_entries.pop(); for (int i = 0; i < MAX_VIRTUAL_RT_ENTRIES; i++) m_recvg_table[i] = simt_reconvergence_table_entry(); for (int i = MAX_VIRTUAL_RT_ENTRIES - 1; i >= 0; i--) m_invalid_entries.push(i); assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_RT_ENTRIES); } bool simt_reconvergence_table::push_to_rt_response_fifo(unsigned entry) { if (m_response_rec_entry == -1) { m_response_rec_entry = entry; return true; } return false; } unsigned simt_reconvergence_table::get_replacement_candidate() { unsigned oldest_index = (unsigned)-1; unsigned long long oldest_update = 0; for (int i = 0; i < MAX_VIRTUAL_RT_ENTRIES; i++) { if (m_recvg_table[i].m_valid && !m_recvg_table[i].m_virtual) { if (oldest_update == 0) { oldest_index = i; oldest_update = m_recvg_table[i].m_branch_rec_cycle; } else if (m_recvg_table[i].m_branch_rec_cycle > oldest_update) { oldest_index = i; oldest_update = m_recvg_table[i].m_branch_rec_cycle; } } } assert(oldest_index != (unsigned)-1); return oldest_index; } bool simt_reconvergence_table::fill_rec_entry(unsigned entry) { if (!m_fill_rec_entry.empty()) return false; address_type pc = (m_warp_id * m_warp_size + entry) * MAX_BRU_VIR_PER_SPLIT + (MAX_BRU_VIR_PER_SPLIT / 2); address_type ppc = pc + BRU_VIR_START; unsigned nbytes = 16; unsigned offset_in_block = pc & (m_config->m_L1D_config.get_line_sz() - 1); if ((offset_in_block + nbytes) > m_config->m_L1I_config.get_line_sz()) nbytes = (m_config->m_L1D_config.get_line_sz() - offset_in_block); mem_access_t acc(BRU_RT_FILL, ppc, nbytes, false, GPGPU_Context()); m_fill_rec_entry.space = memory_space_t(local_space); m_fill_rec_entry.cache_op = CACHE_ALL; m_fill_rec_entry.op = LOAD_OP; m_fill_rec_entry.mem_op = NOT_TEX; m_fill_rec_entry.memory_op = bru_rt_fill_request; m_fill_rec_entry.pc = ppc; m_fill_rec_entry.inject_mem_acccesses(acc); m_fill_rec_entry.occupy(); m_fill_rec_entry.set_warp_id(m_warp_id); m_fill_rec_entry.set_active(0); m_recvg_table[entry].m_transient = true; m_num_transient_entries++; return true; } bool simt_reconvergence_table::spill_rec_entry() { if (!m_spill_rec_entry.empty()) return false; assert(m_spill_rec_entry.empty()); unsigned entry_to_replace = get_replacement_candidate(); m_recvg_table[entry_to_replace].m_virtual = true; m_num_physical_entries--; assert(m_num_physical_entries != (unsigned)-1); address_type pc = (m_warp_id * m_warp_size + entry_to_replace) * MAX_BRU_VIR_PER_SPLIT + (MAX_BRU_VIR_PER_SPLIT / 2); address_type ppc = pc + BRU_VIR_START; unsigned nbytes = 16; unsigned offset_in_block = pc & (m_config->m_L1D_config.get_line_sz() - 1); if ((offset_in_block + nbytes) > m_config->m_L1I_config.get_line_sz()) nbytes = (m_config->m_L1D_config.get_line_sz() - offset_in_block); mem_access_t acc(BRU_RT_SPILL, ppc, nbytes, true, GPGPU_Context()); m_spill_rec_entry.space = memory_space_t(local_space); m_spill_rec_entry.cache_op = CACHE_WRITE_BACK; m_spill_rec_entry.op = STORE_OP; m_spill_rec_entry.mem_op = NOT_TEX; m_spill_rec_entry.memory_op = bru_rt_spill_request; m_spill_rec_entry.pc = ppc; m_spill_rec_entry.occupy(); m_spill_rec_entry.set_warp_id(m_warp_id); m_spill_rec_entry.set_active(0); m_spill_rec_entry.inject_mem_acccesses(acc); return true; } void simt_reconvergence_table::cycle() { if (m_pending_update_entry.m_valid && !m_pending_update_entry.m_transient) { bool sent = fill_rec_entry(m_pending_update_entry.m_recvg_entry); if (sent) m_pending_update_entry.m_transient = true; } if (!m_spill_rec_entry.empty()) { enum mem_stage_stall_type rc_fail = NO_RC_FAIL; mem_stage_access_type type; bool done = m_shader->memory_cycle(m_spill_rec_entry, rc_fail, type); if (done) { m_spill_rec_entry.clear(); m_spill_rec_entry.clear_pending_mem_requests(); } } if (!m_fill_rec_entry.empty()) { enum mem_stage_stall_type rc_fail = NO_RC_FAIL; mem_stage_access_type type; bool done = m_shader->memory_cycle(m_fill_rec_entry, rc_fail, type); if (done) { m_fill_rec_entry.clear(); m_fill_rec_entry.clear_pending_mem_requests(); } } if (m_response_rec_entry != -1) { if (m_pending_update_entry.m_recvg_entry == (unsigned)m_response_rec_entry) { assert(m_pending_update_entry.m_valid); simt_mask_t test = m_recvg_table[m_response_rec_entry].m_pending_mask & ~m_pending_update_entry.m_active_mask; bool converged = !test.any(); bool spacefound = !m_simt_tables->is_pending_reconvergence() || m_simt_tables->st_space_available(); if (m_num_physical_entries == m_max_rec_size) { bool spilled = spill_rec_entry(); if (spilled && (!converged || (converged && spacefound))) { m_recvg_table[m_response_rec_entry].m_virtual = false; m_recvg_table[m_response_rec_entry].m_transient = false; m_recvg_table[m_response_rec_entry].m_pending_mask = m_recvg_table[m_response_rec_entry].m_pending_mask & ~m_pending_update_entry.m_active_mask; m_recvg_table[m_response_rec_entry].m_branch_rec_cycle = m_pending_update_entry.m_branch_rec_cycle; m_pending_update_entry.m_valid = false; m_num_transient_entries--; m_num_physical_entries++; if (converged) { simt_mask_t active_mask = get_active_mask(m_response_rec_entry); address_type pc = get_pc(m_response_rec_entry); address_type rpc = get_rpc(m_response_rec_entry); unsigned rpc_entry = get_rpc_entry(m_response_rec_entry); splits_table_entry_type type = get_type(m_response_rec_entry); invalidate(m_response_rec_entry); m_simt_tables->insert_st_entry(pc, rpc, rpc_entry, active_mask, type, true); } m_response_rec_entry = -1; } } else { if (!converged || (converged && spacefound)) { m_recvg_table[m_response_rec_entry].m_virtual = false; m_recvg_table[m_response_rec_entry].m_transient = false; m_recvg_table[m_response_rec_entry].m_pending_mask = m_recvg_table[m_response_rec_entry].m_pending_mask & ~m_pending_update_entry.m_active_mask; m_recvg_table[m_response_rec_entry].m_branch_rec_cycle = m_pending_update_entry.m_branch_rec_cycle; m_pending_update_entry.m_valid = false; m_num_transient_entries--; m_num_physical_entries++; if (converged) { simt_mask_t active_mask = get_active_mask(m_response_rec_entry); address_type pc = get_pc(m_response_rec_entry); address_type rpc = get_rpc(m_response_rec_entry); unsigned rpc_entry = get_rpc_entry(m_response_rec_entry); splits_table_entry_type type = get_type(m_response_rec_entry); invalidate(m_response_rec_entry); m_simt_tables->insert_st_entry(pc, rpc, rpc_entry, active_mask, type, true); } m_response_rec_entry = -1; } } } else { unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; unsigned long long diff = gpgpusim_total_cycles - m_recvg_table[m_response_rec_entry].m_branch_rec_cycle; if (diff > m_config->rec_time_out) { const simt_mask_t &reconverged_mask = (~m_recvg_table[m_response_rec_entry].m_pending_mask) & (m_recvg_table[m_response_rec_entry].m_active_mask); address_type pc = m_recvg_table[m_response_rec_entry].m_pc; address_type rpc = m_recvg_table[m_response_rec_entry].m_recvg_pc; unsigned rpc_entry = m_recvg_table[m_response_rec_entry].m_recvg_entry; splits_table_entry_type type = m_recvg_table[m_response_rec_entry].m_type; m_simt_tables->insert_st_entry(pc, rpc, rpc_entry, reconverged_mask, type, true); update_masks_upon_time_out(m_response_rec_entry, reconverged_mask); set_rec_cycle(m_response_rec_entry, gpgpusim_total_cycles); } } } } unsigned simt_reconvergence_table::insert_new_entry( address_type pc, address_type rpc, unsigned rpc_entry, const simt_mask_t &tmp_active_mask, splits_table_entry_type type) { unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; if (m_num_physical_entries == m_max_rec_size) { bool spilled = spill_rec_entry(); assert(spilled); } assert(tmp_active_mask.any()); int entry_num = m_invalid_entries.top(); m_invalid_entries.pop(); assert(!m_recvg_table[entry_num].m_valid); m_recvg_table[entry_num].m_pc = pc; m_recvg_table[entry_num].m_recvg_pc = rpc; m_recvg_table[entry_num].m_recvg_entry = rpc_entry; m_recvg_table[entry_num].m_active_mask = tmp_active_mask; m_recvg_table[entry_num].m_pending_mask = tmp_active_mask; m_recvg_table[entry_num].m_valid = true; m_recvg_table[entry_num].m_branch_rec_cycle = gpgpusim_total_cycles; m_recvg_table[entry_num].m_type = type; m_num_entries++; m_num_physical_entries++; assert(entry_num != (unsigned)-1); AWARE_DPRINTF("Inserting new entry to Splits Table: Entry %d\tPC %d\tRPC %d\n", entry_num, pc, rpc); return entry_num; } void simt_reconvergence_table::update_masks_upon_time_out( unsigned recvg_entry, const simt_mask_t &reconverged_mask) { m_recvg_table[recvg_entry].m_active_mask = m_recvg_table[recvg_entry].m_pending_mask; } unsigned simt_reconvergence_table::address_to_entry(warp_inst_t inst) { if (!inst.empty()) { unsigned wid = inst.warp_id(); address_type addr = inst.pc; unsigned entry = (addr - BRU_VIR_START - (wid * m_config->warp_size) * MAX_BRU_VIR_PER_SPLIT - MAX_BRU_VIR_PER_SPLIT / 2) / (MAX_BRU_VIR_PER_SPLIT); return entry; } return (unsigned)-1; } bool simt_reconvergence_table::update_pending_mask( unsigned recvg_entry, address_type recvg_pc, const simt_mask_t &tmp_active_mask, bool &suspended) { unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; assert(m_recvg_table[recvg_entry].m_pc == recvg_pc); assert(m_recvg_table[recvg_entry].m_valid); assert(m_recvg_table[recvg_entry].m_pending_mask.any()); if (m_recvg_table[recvg_entry].m_virtual) { suspended = true; assert(!m_pending_update_entry.m_valid); if (!m_recvg_table[recvg_entry].m_transient) { m_pending_update_entry.m_valid = true; m_pending_update_entry.m_active_mask = tmp_active_mask; m_pending_update_entry.m_branch_rec_cycle = gpgpusim_total_cycles; m_pending_update_entry.m_recvg_entry = recvg_entry; m_pending_update_entry.m_transient = false; } } else { m_recvg_table[recvg_entry].m_branch_rec_cycle = gpgpusim_total_cycles; m_recvg_table[recvg_entry].m_pending_mask = m_recvg_table[recvg_entry].m_pending_mask & ~tmp_active_mask; if (!m_recvg_table[recvg_entry].m_pending_mask.any()) { invalidate(recvg_entry); return true; } } return false; } const simt_mask_t &simt_reconvergence_table::get_active_mask() { assert(m_recvg_table.find(m_active_reconvergence) != m_recvg_table.end()); return m_recvg_table[m_active_reconvergence].m_active_mask; } const simt_mask_t &simt_reconvergence_table::get_active_mask(unsigned num) { assert(m_recvg_table.find(num) != m_recvg_table.end()); return m_recvg_table[num].m_active_mask; } simt_reconvergence_table_entry simt_reconvergence_table::get_recvg_entry( unsigned num) { return m_recvg_table[num]; } void simt_reconvergence_table::get_recvg_entry_info(unsigned num, unsigned *pc, unsigned *rpc) { assert(m_recvg_table.find(num) != m_recvg_table.end()); *pc = m_recvg_table[num].m_pc; *rpc = m_recvg_table[num].m_recvg_pc; } void simt_reconvergence_table::get_active_recvg_info(unsigned *pc, unsigned *rpc) { assert(m_recvg_table.find(m_active_reconvergence) != m_recvg_table.end()); *pc = m_recvg_table[m_active_reconvergence].m_pc; *rpc = m_recvg_table[m_active_reconvergence].m_recvg_pc; } unsigned simt_reconvergence_table::get_rpc_entry() { assert(m_recvg_table.find(m_active_reconvergence) != m_recvg_table.end()); return m_recvg_table[m_active_reconvergence].m_recvg_entry; } unsigned simt_reconvergence_table::get_rpc_entry(unsigned num) { assert(m_recvg_table.find(num) != m_recvg_table.end()); return m_recvg_table[num].m_recvg_entry; } splits_table_entry_type simt_reconvergence_table::get_type(unsigned num) { assert(m_recvg_table.find(num) != m_recvg_table.end()); return m_recvg_table[num].m_type; } splits_table_entry_type simt_reconvergence_table::get_type() { assert(m_recvg_table.find(m_active_reconvergence) != m_recvg_table.end()); return m_recvg_table[m_active_reconvergence].m_type; } unsigned simt_reconvergence_table::get_rpc() { assert(m_recvg_table.find(m_active_reconvergence) != m_recvg_table.end()); return m_recvg_table[m_active_reconvergence].m_recvg_pc; } unsigned simt_reconvergence_table::get_rpc(unsigned num) { assert(m_recvg_table.find(num) != m_recvg_table.end()); return m_recvg_table[num].m_recvg_pc; } unsigned simt_reconvergence_table::get_pc() { assert(m_recvg_table.find(m_active_reconvergence) != m_recvg_table.end()); return m_recvg_table[m_active_reconvergence].m_pc; } unsigned simt_reconvergence_table::get_pc(unsigned num) { assert(m_recvg_table.find(num) != m_recvg_table.end()); return m_recvg_table[num].m_pc; } void simt_reconvergence_table::invalidate() { assert(m_recvg_table.find(m_active_reconvergence) != m_recvg_table.end() && m_recvg_table[m_active_reconvergence].m_valid); m_recvg_table[m_active_reconvergence].m_valid = false; m_invalid_entries.push(m_active_reconvergence); m_num_entries--; m_num_physical_entries--; assert(m_num_physical_entries != (unsigned)-1); assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_RT_ENTRIES); } void simt_reconvergence_table::invalidate(unsigned num) { assert(m_recvg_table.find(num) != m_recvg_table.end() && m_recvg_table[num].m_valid); m_recvg_table[num].m_valid = false; m_invalid_entries.push(num); m_num_entries--; m_num_physical_entries--; assert(m_num_physical_entries != (unsigned)-1); assert((m_num_entries + m_invalid_entries.size()) == MAX_VIRTUAL_RT_ENTRIES); } void simt_reconvergence_table::set_rec_cycle(unsigned rec_entry, unsigned long long time) { m_recvg_table[rec_entry].m_branch_rec_cycle = time; } unsigned simt_reconvergence_table::check_simt_reconvergence_table() { unsigned count = 0; for (unsigned i = 0; i < MAX_VIRTUAL_RT_ENTRIES; i++) if (m_recvg_table[i].m_valid) for (unsigned j = 0; j < m_warp_size; j++) if (!m_recvg_table[i].m_pending_mask.test(j) && m_recvg_table[i].m_active_mask.test(j)) count++; return count; } void simt_reconvergence_table::print(FILE *fout) { printf("max of physical entries=%u\n", m_max_rec_size); printf("num of physical entries=%u\n", m_num_physical_entries); for (unsigned k = 0; k < MAX_VIRTUAL_RT_ENTRIES; k++) { simt_reconvergence_table_entry &rte = m_recvg_table[k]; if (!rte.m_valid) continue; fprintf(fout, " %1u ", k); for (unsigned j = 0; j < m_warp_size; j++) fprintf(fout, "%c", (rte.m_active_mask.test(j) ? '1' : '0')); fprintf(fout, " "); for (unsigned j = 0; j < m_warp_size; j++) fprintf(fout, "%c", (rte.m_pending_mask.test(j) ? '1' : '0')); fprintf(fout, " pc: 0x%03x", rte.m_pc); if (rte.m_recvg_pc == (unsigned)-1) fprintf(fout, " rp: ---- %s\n", (rte.m_valid ? " V " : " N ")); else fprintf(fout, " rp: 0x%03x %s\n", rte.m_recvg_pc, (rte.m_valid ? " V " : " N ")); } } // ── simt_tables ────────────────────────────────────────────────────────────── simt_tables::simt_tables(unsigned wid, unsigned warpSize, const shader_core_config *config, const memory_config *mem_config) { m_warp_id = wid; m_warp_size = warpSize; m_simt_splits_table = new simt_splits_table(wid, warpSize, config, mem_config, this); m_simt_recvg_table = new simt_reconvergence_table(wid, warpSize, config, mem_config, this); m_config = config; m_mem_config = mem_config; m_shader = NULL; } void simt_tables::reset() { m_simt_splits_table->reset(); m_simt_recvg_table->reset(); m_simt_splits_table->release_blocked(); } void simt_tables::launch(address_type start_pc, const simt_mask_t &active_mask) { m_simt_splits_table->launch(start_pc, active_mask); } void simt_tables::check_simt_tables() { unsigned running = m_simt_splits_table->check_simt_splits_table(); unsigned converged = m_simt_recvg_table->check_simt_reconvergence_table(); if (running + converged > 32) { printf("running=%u\n", running); printf("converged=%u\n", converged); abort(); } } void simt_tables::check_time_out() { unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; for (unsigned k = 0; k < m_config->num_rec_entries; k++) { simt_reconvergence_table_entry recvg_table_entry = m_simt_recvg_table->get_recvg_entry(k); if (!recvg_table_entry.m_valid) continue; const simt_mask_t &reconverged_mask = (~recvg_table_entry.m_pending_mask) & (recvg_table_entry.m_active_mask); unsigned long long diff = gpgpusim_total_cycles - recvg_table_entry.m_branch_rec_cycle; if (diff > (unsigned long long)m_config->rec_time_out) { if (recvg_table_entry.m_virtual) { m_simt_recvg_table->fill_rec_entry(k); } else { if (reconverged_mask.any()) { address_type pc = recvg_table_entry.m_pc; address_type rpc = recvg_table_entry.m_recvg_pc; unsigned rpc_entry = recvg_table_entry.m_recvg_entry; splits_table_entry_type type = recvg_table_entry.m_type; m_simt_splits_table->insert_new_entry(pc, rpc, rpc_entry, reconverged_mask, type, true); m_simt_recvg_table->update_masks_upon_time_out(k, reconverged_mask); m_simt_recvg_table->set_rec_cycle(k, gpgpusim_total_cycles); } } } } } void simt_tables::update(simt_mask_t &thread_done, addr_vector_t &next_pc, address_type recvg_pc, op_type next_inst_op, unsigned next_inst_size, address_type next_inst_pc, bool predicated) { check_simt_tables(); simt_mask_t top_active_mask = m_simt_splits_table->get_active_mask(); simt_mask_t top_active_mask_keep = top_active_mask; address_type top_recvg_pc = m_simt_splits_table->get_rpc(); unsigned top_recvg_entry = m_simt_splits_table->get_rpc_entry(); address_type top_pc = m_simt_splits_table->get_pc(); if (top_pc != next_inst_pc) { static const bool dbg_pc_enabled = (getenv("MEMCO_DBG_PC") != NULL); if (dbg_pc_enabled) { fprintf(stderr, "[SIMT_UPDATE_MISMATCH] cycle=%llu warp=%u top_pc=0x%lx " "next_inst_pc=0x%lx top_mask=%s active_split=%u\n", (unsigned long long)(GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle), m_warp_id, (unsigned long)top_pc, (unsigned long)next_inst_pc, top_active_mask.to_string().c_str(), m_simt_splits_table->get_active_split_id()); fflush(stderr); } } assert(top_pc == next_inst_pc); assert(top_active_mask.any()); const address_type null_pc = -1; bool warp_diverged = false; address_type new_recvg_pc = null_pc; unsigned num_divergent_paths = 0; unsigned new_recvg_entry = top_recvg_entry; splits_table_entry_type top_type = m_simt_splits_table->get_type(); std::map divergent_paths; bool do_invalidate = false; while (top_active_mask.any()) { address_type tmp_next_pc = null_pc; simt_mask_t tmp_active_mask; for (int i = m_warp_size - 1; i >= 0; i--) { if (top_active_mask.test(i)) { if (thread_done.test(i)) { top_active_mask.reset(i); } else if (tmp_next_pc == null_pc) { tmp_next_pc = next_pc[i]; tmp_active_mask.set(i); top_active_mask.reset(i); } else if (tmp_next_pc == next_pc[i]) { tmp_active_mask.set(i); top_active_mask.reset(i); } } } if (tmp_next_pc == null_pc) { assert(!top_active_mask.any()); continue; } divergent_paths[tmp_next_pc] = tmp_active_mask; num_divergent_paths++; } address_type not_taken_pc = next_inst_pc + next_inst_size; assert(num_divergent_paths <= 2); for (unsigned i = 0; i < num_divergent_paths; i++) { address_type tmp_next_pc = null_pc; simt_mask_t tmp_active_mask; tmp_active_mask.reset(); if (divergent_paths.find(not_taken_pc) != divergent_paths.end()) { assert(i == 0); tmp_next_pc = not_taken_pc; tmp_active_mask = divergent_paths[tmp_next_pc]; divergent_paths.erase(tmp_next_pc); } else { std::map::iterator it = divergent_paths.begin(); tmp_next_pc = it->first; tmp_active_mask = divergent_paths[tmp_next_pc]; divergent_paths.erase(tmp_next_pc); } unsigned long long gpgpusim_total_cycles = GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_sim_cycle + GPGPU_Context()->the_gpgpusim->g_the_gpu->gpu_tot_sim_cycle; if (!predicated) { if (next_inst_op == CALL_OPS) { assert(num_divergent_paths == 1); new_recvg_entry = m_simt_recvg_table->insert_new_entry( top_pc, top_recvg_pc, top_recvg_entry, top_active_mask_keep, top_type); assert(new_recvg_entry != (unsigned)-1); m_simt_splits_table->invalidate(); m_simt_splits_table->update_active_entry(); simt_splits_table_entry new_st_entry; new_st_entry.m_pc = tmp_next_pc; new_st_entry.m_recvg_entry = new_recvg_entry; new_st_entry.m_active_mask = tmp_active_mask; new_st_entry.m_branch_div_cycle = gpgpusim_total_cycles; new_st_entry.m_type = SPLITS_TABLE_TYPE_CALL; m_simt_splits_table->insert_new_entry(new_st_entry); return; } else if (next_inst_op == RET_OPS && top_type == SPLITS_TABLE_TYPE_CALL) { assert(num_divergent_paths == 1); top_recvg_entry = m_simt_splits_table->get_rpc_entry(); m_simt_splits_table->invalidate(); simt_mask_t active_mask = m_simt_recvg_table->get_active_mask(new_recvg_entry); address_type pc = m_simt_recvg_table->get_pc(new_recvg_entry); address_type rpc = m_simt_recvg_table->get_rpc(new_recvg_entry); unsigned rpc_entry = m_simt_recvg_table->get_rpc_entry(top_recvg_entry); splits_table_entry_type type = m_simt_recvg_table->get_type(new_recvg_entry); m_simt_splits_table->insert_new_entry(pc, rpc, rpc_entry, active_mask, type, true, true); m_simt_splits_table->update_active_entry(); top_recvg_pc = m_simt_splits_table->get_rpc(); bool suspended = false; m_simt_recvg_table->update_pending_mask(top_recvg_entry, pc, active_mask, suspended); top_recvg_entry = m_simt_splits_table->get_rpc_entry(); } } if (tmp_next_pc == top_recvg_pc) { bool suspended = false; bool reconverged = m_simt_recvg_table->update_pending_mask( top_recvg_entry, top_recvg_pc, tmp_active_mask, suspended); if (reconverged && !suspended) { simt_mask_t active_mask = m_simt_recvg_table->get_active_mask(top_recvg_entry); address_type pc = m_simt_recvg_table->get_pc(top_recvg_entry); address_type rpc = m_simt_recvg_table->get_rpc(top_recvg_entry); unsigned rpc_entry = m_simt_recvg_table->get_rpc_entry(top_recvg_entry); splits_table_entry_type type = m_simt_recvg_table->get_type(top_recvg_entry); m_simt_splits_table->insert_new_entry(pc, rpc, rpc_entry, active_mask, type, true); } if (num_divergent_paths == 1) { if (!do_invalidate) { m_simt_splits_table->invalidate(); do_invalidate = true; } } continue; } if ((num_divergent_paths > 1) && !warp_diverged) { warp_diverged = true; new_recvg_pc = recvg_pc; if (new_recvg_pc != top_recvg_pc) { new_recvg_entry = m_simt_recvg_table->insert_new_entry( new_recvg_pc, top_recvg_pc, top_recvg_entry, top_active_mask_keep, top_type); } } if (warp_diverged && tmp_next_pc == new_recvg_pc) { bool suspended = false; bool reconverged = m_simt_recvg_table->update_pending_mask( new_recvg_entry, new_recvg_pc, tmp_active_mask, suspended); if (reconverged && !suspended) { simt_mask_t active_mask = m_simt_recvg_table->get_active_mask(new_recvg_entry); address_type pc = m_simt_recvg_table->get_pc(new_recvg_entry); address_type rpc = m_simt_recvg_table->get_rpc(new_recvg_entry); unsigned rpc_entry = m_simt_recvg_table->get_rpc_entry(new_recvg_entry); splits_table_entry_type type = m_simt_recvg_table->get_type(new_recvg_entry); m_simt_splits_table->insert_new_entry(pc, rpc, rpc_entry, active_mask, type); } continue; } if (warp_diverged) { if (!do_invalidate) { m_simt_splits_table->invalidate(); do_invalidate = true; } m_simt_splits_table->insert_new_entry(tmp_next_pc, new_recvg_pc, new_recvg_entry, tmp_active_mask, top_type); } else { m_simt_splits_table->update_pc(tmp_next_pc); if (tmp_next_pc != not_taken_pc) m_simt_splits_table->push_back(); } } if (do_invalidate) m_simt_splits_table->update_active_entry(); check_simt_tables(); m_shader->update_st_size(m_simt_splits_table->num_entries()); m_shader->update_rt_size(m_simt_recvg_table->num_entries()); } const simt_mask_t &simt_tables::get_active_mask() { return m_simt_splits_table->get_active_mask(); } void simt_tables::get_pdom_active_split_info(unsigned *pc, unsigned *rpc) { m_simt_splits_table->get_pdom_active_split_info(pc, rpc); } unsigned simt_tables::get_rp() { return m_simt_splits_table->get_rpc(); } bool simt_tables::split_reaches_barrier(address_type pc) { return m_simt_splits_table->split_reaches_barrier(pc); } void simt_tables::release_barrier() { m_simt_splits_table->release_blocked(); } bool simt_tables::is_virtualized() { return m_simt_splits_table->is_virtualized(); } bool simt_tables::is_pending_reconvergence() { return m_simt_splits_table->is_pending_reconvergence() && m_simt_recvg_table->is_pending_update(); } bool simt_tables::st_space_available() { return m_simt_splits_table->st_space_available(); } bool simt_tables::blocked() { return m_simt_splits_table->blocked(); } bool simt_tables::is_blocked() { return m_simt_splits_table->is_blocked(); } bool simt_tables::valid() { return m_simt_splits_table->valid(); } bool simt_tables::push_to_rt_response_fifo(unsigned entry) { return m_simt_recvg_table->push_to_rt_response_fifo(entry); } bool simt_tables::push_to_st_response_fifo(unsigned entry) { return m_simt_splits_table->push_to_st_response_fifo(entry); } void simt_tables::set_shader(shader_core_ctx *shader) { m_shader = shader; m_simt_splits_table->set_shader(shader); m_simt_recvg_table->set_shader(shader); } void simt_tables::push_back() { m_simt_splits_table->push_back(); } void simt_tables::print(FILE *fp) { printf("w%02d\n", m_warp_id); printf("Splits Table:\n"); m_simt_splits_table->print(fp); printf("Reconvergence Table:\n"); m_simt_recvg_table->print(fp); } // ── core_t ITS methods ─────────────────────────────────────────────────────── void core_t::initilizeSIMTDivergenceStructures(unsigned warp_count, unsigned warp_size) { if (m_gpu->simd_model() == POST_DOMINATOR) { m_simt_stack = new simt_stack *[warp_count]; for (unsigned i = 0; i < warp_count; ++i) m_simt_stack[i] = new simt_stack(i, warp_size, m_gpu); } else { m_simt_tables = new simt_tables *[warp_count]; for (unsigned i = 0; i < warp_count; ++i) m_simt_tables[i] = new simt_tables(i, warp_size, m_gpu->getShaderCoreConfig(), m_gpu->getMemoryConfig()); } m_warp_size = warp_size; m_warp_count = warp_count; } void core_t::deleteSIMTDivergenceStructures() { if (m_simt_stack) { for (unsigned i = 0; i < m_warp_count; ++i) delete m_simt_stack[i]; delete[] m_simt_stack; m_simt_stack = NULL; } if (m_simt_tables) { for (unsigned i = 0; i < m_warp_count; ++i) delete m_simt_tables[i]; delete[] m_simt_tables; m_simt_tables = NULL; } } void core_t::updateSIMTDivergenceStructures(unsigned warpId, warp_inst_t *inst) { simt_mask_t thread_done; addr_vector_t next_pc; unsigned wtid = warpId * m_warp_size; for (unsigned i = 0; i < m_warp_size; i++) { if (ptx_thread_done(wtid + i)) { thread_done.set(i); next_pc.push_back((address_type)-1); } else { if (inst->reconvergence_pc == RECONVERGE_RETURN_PC) inst->reconvergence_pc = get_return_pc(m_thread[wtid + i]); next_pc.push_back(m_thread[wtid + i]->get_pc()); } } if (m_gpu->simd_model() == POST_DOMINATOR) { m_simt_stack[warpId]->update(thread_done, next_pc, inst->reconvergence_pc, inst->op, inst->isize, inst->pc); } else { AWARE_DPRINTF("Updating SIMT tables for Warp %d\n", warpId); m_simt_tables[warpId]->update(thread_done, next_pc, inst->reconvergence_pc, inst->op, inst->isize, inst->pc, inst->pred != -1); } } // ── End ITS implementations ──────────────────────────────────────────────────