summaryrefslogtreecommitdiff
path: root/src/abstract_hardware_model.cc
blob: 507e02ff40f68609c9020cba969194fbb60e2b65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#include "abstract_hardware_model.h"
#include "cuda-sim/memory.h"
#include <algorithm>

unsigned mem_access_t::next_access_uid = 0;   
unsigned warp_inst_t::sm_next_uid = 0;

void move_warp( warp_inst_t *&dst, warp_inst_t *&src )
{
   assert( dst->empty() );
   warp_inst_t* temp = dst;
   dst = src;
   src = temp;
   src->clear();
}

gpgpu_t::gpgpu_t()
{
   m_global_mem = new memory_space_impl<8192>("global",64*1024);
   m_param_mem = new memory_space_impl<8192>("param",64*1024);
   m_tex_mem = new memory_space_impl<8192>("tex",64*1024);
   m_surf_mem = new memory_space_impl<8192>("surf",64*1024);

   m_dev_malloc=GLOBAL_HEAP_START; 
}

unsigned core_config::shmem_bank_func(address_type addr, unsigned) const
{
   return ((addr/WORD_SIZE) % gpgpu_n_shmem_bank);
}

address_type null_tag_func(address_type address, unsigned line_size)
{
   return address; //no modification: each address is its own tag.
}

address_type line_size_based_tag_func(address_type address, unsigned line_size)
{
   //gives the tag for an address based on a given line size
   return ((address) & (~((address_type)line_size - 1)));
}

void warp_inst_t::get_memory_access_list()
{
   // Calculates memory accesses generated by this warp
   // Returns acesses which are "coalesced" 
   // Does not coalesce nor overlap bank accesses across warp "parts".

   // This is called once per warp_inst_t when the warp_inst_t enters the memory stage.
   // It produces the set of distinct memory accesses that need to be peformed.
   // These accessess are then performed over multiple cycles (stalling the pipeline) 
   // if the accessses cannot be performed all at once.
    
   // In hardware, these accesses would be created at the specific unit handling the type 
   // of memory access. We centralize the logic simply to reduce code duplication. 

   // Below, accesses are assigned an "order" based on when that access may be issued. 
   // Accesses with the same order number may occur at the same time: they are to different banks. 
   // Later, when the queue is processed it will evaluate accesses of as many orders as 
   // ports on that cache/shmem.
   //  
   // Accesses are placed in accessq sorted so that accesses of the same order are adjacent.

    typedef unsigned (core_config::*bank_func_t)(address_type add, unsigned line_size) const;
    typedef address_type (*tag_func_t)(address_type add, unsigned line_size);
    bank_func_t bank_func = NULL;
    tag_func_t tag_func = NULL;
    unsigned warp_parts = 0;
    unsigned line_size = 0;
    bool limit_broadcast = 0; 
    bool global_mem_access = false;

    switch( space.get_type() ) {
    case shared_space: 
        bank_func = &core_config::shmem_bank_func; 
        tag_func = null_tag_func;
        warp_parts = m_config->gpgpu_shmem_pipe_speedup; 
        line_size = 1; //shared memory doesn't care about line_size, needs to be at least 1;
        limit_broadcast = true; // limit broadcasts to single cycle. 
        break;
    case tex_space: 
        bank_func = &core_config::null_bank_func;
        tag_func = line_size_based_tag_func;
        warp_parts = 1;
        line_size = m_config->gpgpu_cache_texl1_linesize;
        limit_broadcast = false;
        break;
    case const_space:  case param_space_kernel: 
        bank_func = &core_config::null_bank_func; 
        tag_func = line_size_based_tag_func;
        warp_parts = 1;
        line_size = m_config->gpgpu_cache_constl1_linesize;
        limit_broadcast = false;
        break;
    case global_space: case local_space: case param_space_local: 
        global_mem_access=true;
        warp_parts = 1;
        line_size = 0;
        if( m_config->gpgpu_coalesce_arch == 13 ){
           warp_parts = 2;
           // line size is dependant on instruction;
           switch (data_size) {
           case 1: line_size = 32; break;
           case 2: line_size = 64; break;
           case 4: case 8: case 16: line_size = 128; break;
           default: assert(0);
           }
        } else abort();
        bank_func = &core_config::null_bank_func;
        tag_func = line_size_based_tag_func;
        limit_broadcast = false;
        break;
    default:
        abort();
    }

   // bank_accs tracks bank accesses for sorting into generations;
   // each entry is (bank #, number of accesses)
   // the idea is that you can only access a bank a number of times each cycle equal to 
   // its number of ports in one cycle. 
   std::map<unsigned,unsigned> bank_accs;

   // keep track of broadcasts with unique orders if limit_broadcast
   // the normally calculated orders will never be greater than warp_size
   unsigned broadcast_order =  warp_size();
   unsigned qbegin = get_accessq_size();
   unsigned qpartbegin = qbegin;
   unsigned mem_pipe_size = warp_size() / warp_parts;
   for (unsigned part = 0; part < warp_size(); part += mem_pipe_size) {
      for (unsigned i = part; i < part + mem_pipe_size; i++) {
         if ( !active(i) ) 
            continue;
         new_addr_type addr = get_addr(i);
         address_type lane_segment_address = tag_func(addr, line_size);
         unsigned quarter = 0;
         if( line_size>=4 )
            quarter = (addr / (line_size/4)) & 3;
         bool match = false;
         if( !isatomic() ) { //atomics must have own request
            for( unsigned j = qpartbegin; j <get_accessq_size(); j++ ) {
               if (lane_segment_address == accessq(j).addr) {
                  accessq(j).quarter_count[quarter]++;
                  accessq(j).warp_indices.push_back(i);
                  if (limit_broadcast) // two threads access this address, so its a broadcast. 
                     accessq(j).order = ++broadcast_order; //do broadcast in its own cycle.
                  match = true;
                  break;
               }
            }
         }
         if (!match) { // does not match a previous request by another thread, so need a new request
            assert( space != undefined_space );
            m_accessq.push_back( mem_access_t( lane_segment_address, line_size, quarter, i) );
            // Determine Bank Conflicts:
            unsigned bank = (m_config->*bank_func)(get_addr(i), line_size);
            // ensure no concurrent bank access accross warp parts. 
            // ie. order will be less than part for all previous loads in previous parts, so:
            if (bank_accs[bank] < part) 
               bank_accs[bank]=part; 
            accessq_back().order = bank_accs[bank];
            bank_accs[bank]++;
         }
      }
      qpartbegin = get_accessq_size(); //don't coalesce accross warp parts
   }
   //sort requests by order they will be processed in
   std::stable_sort( m_accessq.begin()+qbegin,m_accessq.end());

   if( global_mem_access ) {
       // Now that we have the accesses, if we don't have a cache we can adjust request sizes to 
       // include only the data referenced by the threads 
       for (unsigned i = 0; i < get_accessq_size(); i++) {
          if (m_config->gpgpu_coalesce_arch == 13) {
             // do coalescing here.
             char* quarter_counts = accessq(i).quarter_count;
             bool low = quarter_counts[0] or quarter_counts[1];
             bool high = quarter_counts[2] or quarter_counts[3];
             if (accessq(i).req_size == 128) {
                if (low xor high) { //can reduce size
                   accessq(i).req_size = 64;
                   if (high) accessq(i).addr += 64;
                   low = quarter_counts[0] or quarter_counts[2]; //set low and high for next pass
                   high = quarter_counts[1] or quarter_counts[3];
                }
             }
             if (accessq(i).req_size == 64) {
                if (low xor high) { //can reduce size
                   accessq(i).req_size = 32;
                   if (high) accessq(i).addr += 32;
                }
             }
          }
       }
   }
}