|
| 1 | +#include "../cbp.hpp" |
| 2 | +#include "../harcom.hpp" |
| 3 | +#include "common.hpp" |
| 4 | + |
| 5 | +#include <array> |
| 6 | + |
| 7 | +using namespace hcm; |
| 8 | + |
| 9 | + |
| 10 | +template< |
| 11 | + u64 LOGLB = 6, // 64B fetch block |
| 12 | + u64 NTABLES = 8, // number of tables |
| 13 | + u64 MAXHIST = 100, // maximum global history length |
| 14 | + u64 MINHIST = 2, // minimum global history length |
| 15 | + u64 WBITS = 4, // signed 4-bit weight (-8 to 7) |
| 16 | + u64 LOGTABLE = 13, // 32KB hashed perceptron for P2 |
| 17 | + u64 LOGP1 = 14, // 4KB gshare for P1 |
| 18 | + u64 GHIST1 = 6 // P1 gshare history length |
| 19 | +> |
| 20 | +struct hashed_perceptron : predictor { |
| 21 | + static_assert(LOGLB >= 2); |
| 22 | + static constexpr u64 LOGLINEINST = LOGLB - 2; // 4B instruction |
| 23 | + |
| 24 | + static constexpr u64 LINEINST = 1ull << LOGLINEINST; |
| 25 | + static constexpr u64 NUMHIST = NTABLES - 1; // number of tables using history |
| 26 | + |
| 27 | + static constexpr u64 YBITS = WBITS + std::bit_width(NTABLES-1); |
| 28 | + static constexpr u64 TCBITS = 4; // corresponds to SPEED = 16 |
| 29 | + static constexpr u64 THETABITS = YBITS + TCBITS; |
| 30 | + static constexpr u64 PATHBITS = 6; |
| 31 | + |
| 32 | + static_assert(LOGP1 >= LOGLINEINST); |
| 33 | + static constexpr u64 index1_bits = LOGP1 - LOGLINEINST; |
| 34 | + static_assert(LOGTABLE >= LOGLINEINST); |
| 35 | + static constexpr u64 index2_bits = LOGTABLE - LOGLINEINST; |
| 36 | + |
| 37 | + geometric_folds<NUMHIST, MINHIST, MAXHIST, index2_bits> gfolds; |
| 38 | + reg<1> true_block = 1; |
| 39 | + |
| 40 | + // ---- for P1 (gshare) ---- |
| 41 | + reg<GHIST1> global_history1; |
| 42 | + reg<index1_bits> index1; |
| 43 | + arr<reg<1>, LINEINST> readp1; |
| 44 | + reg<LINEINST> p1; |
| 45 | + |
| 46 | + // ---- for P2 (hashed perceptron) ---- |
| 47 | + // based on ChampSim's implementation |
| 48 | + // https://github.com/ChampSim/ChampSim/blob/29b7b368f761fce1309103c17280c847ada44c1e/branch/hashed_perceptron.bpred |
| 49 | + arr<reg<index2_bits>, NTABLES> index2; |
| 50 | + arr<reg<WBITS, i64>, NTABLES> readw[LINEINST]; |
| 51 | + arr<reg<YBITS, i64>, LINEINST> yout; |
| 52 | + reg<LINEINST> p2; |
| 53 | + // dynamic update threshold |
| 54 | + // packed counter representing (theta << TCBITS) + tc |
| 55 | + reg<THETABITS, i64> theta_and_tc = 10 << TCBITS; |
| 56 | + |
| 57 | + // ---- simulation artifacts ---- |
| 58 | + u64 num_branch = 0; |
| 59 | + u64 block_size = 0; |
| 60 | + arr<reg<LOGLINEINST>, LINEINST> branch_offset; |
| 61 | + arr<reg<1>, LINEINST> branch_dir; |
| 62 | + reg<LINEINST> block_entry; |
| 63 | + |
| 64 | + // ---- RAMs ---- |
| 65 | + // P2 weight tables |
| 66 | + ram<val<WBITS, i64>, (1 << index2_bits)> wtable[NTABLES][LINEINST] {{"P2 weight"}}; |
| 67 | + |
| 68 | + // P1 prediction bits |
| 69 | + ram<val<1>, (1 << index1_bits)> table1_pred[LINEINST] {"P1 pred"}; |
| 70 | + // P1 hysteresis bits |
| 71 | + zone UPDATE_ONLY; |
| 72 | + ram<val<1>, (1 << index1_bits)> table1_hyst[LINEINST] {"P1 hyst"}; |
| 73 | + |
| 74 | + void new_block(val<64> inst_pc) |
| 75 | + { |
| 76 | + val<LOGLINEINST> offset = inst_pc.fo1() >> 2; |
| 77 | + block_entry = offset.fo1().decode().concat(); |
| 78 | + block_entry.fanout(hard<4*LINEINST>{}); |
| 79 | + block_size = 1; |
| 80 | + } |
| 81 | + |
| 82 | + // P1 gshare |
| 83 | + val<1> predict1(val<64> inst_pc) |
| 84 | + { |
| 85 | + inst_pc.fanout(hard<2>{}); |
| 86 | + new_block(inst_pc); |
| 87 | + |
| 88 | + // index1 = PC ^ ghist1 |
| 89 | + val<std::max(index1_bits, GHIST1)> lineaddr = inst_pc >> LOGLB; |
| 90 | + global_history1.fanout(hard<2>{}); |
| 91 | + if constexpr (GHIST1 <= index1_bits) { |
| 92 | + index1 = lineaddr.fo1() ^ (val<index1_bits>{global_history1} << (index1_bits - GHIST1)); |
| 93 | + } else { |
| 94 | + index1 = global_history1.make_array(val<index1_bits>{}).append(lineaddr.fo1()).fold_xor(); |
| 95 | + } |
| 96 | + index1.fanout(hard<LINEINST+1>{}); |
| 97 | + |
| 98 | + // read prediction bits |
| 99 | + for (u64 offset=0; offset<LINEINST; offset++) { |
| 100 | + readp1[offset] = table1_pred[offset].read(index1); |
| 101 | + } |
| 102 | + readp1.fanout(hard<2>{}); |
| 103 | + p1 = readp1.concat(); |
| 104 | + p1.fanout(hard<LINEINST>{}); |
| 105 | + |
| 106 | + // prediction for the first instruction |
| 107 | + return (block_entry & p1) != hard<0>{}; |
| 108 | + } |
| 109 | + |
| 110 | + val<1> reuse_predict1([[maybe_unused]] val<64> inst_pc) |
| 111 | + { |
| 112 | + // prediction for subsequent instructions |
| 113 | + return ((block_entry << block_size) & p1) != hard<0>{}; |
| 114 | + } |
| 115 | + |
| 116 | + // P2 hashed perceptron |
| 117 | + val<1> predict2(val<64> inst_pc) |
| 118 | + { |
| 119 | + val<index2_bits> lineaddr = inst_pc.fo1() >> LOGLB; |
| 120 | + lineaddr.fanout(hard<NTABLES>{}); |
| 121 | + gfolds.fanout(hard<2>{}); |
| 122 | + |
| 123 | + // index2 = PC ^ folded_history |
| 124 | + for (u64 i=0; i<NTABLES; i++) { |
| 125 | + if (i == 0) { |
| 126 | + index2[i] = lineaddr; |
| 127 | + } else { |
| 128 | + index2[i] = lineaddr ^ gfolds.template get<0>(i-1); |
| 129 | + } |
| 130 | + } |
| 131 | + index2.fanout(hard<2>{}); |
| 132 | + |
| 133 | + // read weights, then summed up |
| 134 | + for (u64 i=0; i<NTABLES; i++) { |
| 135 | + auto dindex2 = index2[i].distribute(wtable[i]); |
| 136 | + for (u64 offset=0; offset<LINEINST; offset++) { |
| 137 | + readw[offset][i] = wtable[i][offset].read(dindex2[offset].fo1()); |
| 138 | + } |
| 139 | + } |
| 140 | + for (u64 offset=0; offset<LINEINST; offset++) { |
| 141 | + readw[offset].fanout(hard<2>{}); |
| 142 | + yout[offset] = readw[offset].fold_add(); |
| 143 | + } |
| 144 | + yout.fanout(hard<2>{}); |
| 145 | + |
| 146 | + // per-offset predictions |
| 147 | + arr<val<1>, LINEINST> p2bits = [&](u64 offset) { |
| 148 | + auto [sign_bit, rest] = split<1, YBITS-1>(yout[offset]); |
| 149 | + return sign_bit.fo1(); |
| 150 | + }; |
| 151 | + p2 = p2bits.fo1().concat(); |
| 152 | + p2.fanout(hard<LINEINST>{}); |
| 153 | + |
| 154 | + // prediction for the first instruction |
| 155 | + val<1> taken = (block_entry & p2) != hard<0>{}; |
| 156 | + |
| 157 | + // determine block termination |
| 158 | + reuse_prediction(~val<1>{block_entry >> (LINEINST - 1)}); |
| 159 | + return taken.fo1(); |
| 160 | + } |
| 161 | + |
| 162 | + val<1> reuse_predict2([[maybe_unused]] val<64> inst_pc) |
| 163 | + { |
| 164 | + // prediction for subsequent instructions |
| 165 | + val<1> taken = ((block_entry << block_size) & p2) != hard<0>{}; |
| 166 | + |
| 167 | + // determine block termination |
| 168 | + reuse_prediction(~val<1>{block_entry >> (LINEINST - 1 - block_size)}); |
| 169 | + block_size++; |
| 170 | + return taken.fo1(); |
| 171 | + } |
| 172 | + |
| 173 | + // update interface |
| 174 | + void update_condbr(val<64> branch_pc, val<1> taken, [[maybe_unused]] val<64> next_pc) |
| 175 | + { |
| 176 | + assert(num_branch < LINEINST); |
| 177 | + branch_offset[num_branch] = branch_pc.fo1() >> 2; |
| 178 | + branch_dir[num_branch] = taken.fo1(); |
| 179 | + num_branch++; |
| 180 | + } |
| 181 | + |
| 182 | + void update_cycle(instruction_info &block_end_info) |
| 183 | + { |
| 184 | + val<1> &mispredict = block_end_info.is_mispredict; |
| 185 | + val<64> &next_pc = block_end_info.next_pc; |
| 186 | + |
| 187 | + // updates for all conditional branches in the predicted block |
| 188 | + if (num_branch == 0) { |
| 189 | + // no conditional branch in this block |
| 190 | + val<1> line_end = block_entry >> (LINEINST - block_size); |
| 191 | + // update global history if previous block ended on a mispredicted not-taken branch |
| 192 | + // (we are still in the same line, this is the last chunk) |
| 193 | + // or if the block ends before the line boundary (unconditional jump) |
| 194 | + true_block.fanout(hard<2>{}); |
| 195 | + val<1> actual_block = ~(true_block & line_end.fo1()); |
| 196 | + actual_block.fanout(hard<MAXHIST+NUMHIST+3>{}); |
| 197 | + execute_if(actual_block, [&](){ |
| 198 | + next_pc.fanout(hard<2>{}); |
| 199 | + global_history1 = (global_history1 << 1) ^ val<GHIST1>{next_pc >> 2}; |
| 200 | + gfolds.update(val<PATHBITS>{next_pc >> 2}); |
| 201 | + true_block = 1; |
| 202 | + }); |
| 203 | + return; // stop here |
| 204 | + } |
| 205 | + |
| 206 | + branch_dir.fanout(hard<2>{}); |
| 207 | + branch_offset.fanout(hard<LINEINST>{}); |
| 208 | + index1.fanout(hard<LINEINST*3>{}); |
| 209 | + index2.fanout(hard<LINEINST>{}); |
| 210 | + yout.fanout(hard<3>{}); |
| 211 | + theta_and_tc.fanout(hard<2>{}); |
| 212 | + p2.fanout(hard<2>{}); |
| 213 | + |
| 214 | + // masks mapping executed branches to offset |
| 215 | + u64 update_valid = (u64(1) << num_branch) - 1; |
| 216 | + arr<val<LINEINST>, LINEINST> update_mask = [&](u64 offset){ |
| 217 | + arr<val<1>, LINEINST> match_offset = [&](u64 i){ return branch_offset[i] == offset; }; |
| 218 | + return match_offset.fo1().concat() & update_valid; |
| 219 | + }; |
| 220 | + update_mask.fanout(hard<2>{}); |
| 221 | + |
| 222 | + // Is there a branch instruction at the offset? |
| 223 | + arr<val<1>, LINEINST> is_branch = [&](u64 offset){ |
| 224 | + return update_mask[offset] != hard<0>{}; |
| 225 | + }; |
| 226 | + is_branch.fanout(hard<3>{}); |
| 227 | + val<LINEINST> branch_mask = is_branch.concat(); |
| 228 | + branch_mask.fanout(hard<5>{}); |
| 229 | + |
| 230 | + // is the outcome of the branch at the offset taken? |
| 231 | + val<LINEINST> actualdirs = branch_dir.concat(); |
| 232 | + actualdirs.fanout(hard<LINEINST>{}); |
| 233 | + arr<val<1>, LINEINST> branch_taken = [&](u64 offset){ |
| 234 | + return (actualdirs & update_mask[offset]) != hard<0>{}; |
| 235 | + }; |
| 236 | + branch_taken.fanout(hard<NTABLES+1>{}); |
| 237 | + |
| 238 | + // is the P2 (=final) prediction correct? |
| 239 | + auto p2_split = p2.make_array(val<1>{}); |
| 240 | + p2_split.fanout(hard<3>{}); |
| 241 | + arr<val<1>, LINEINST> correct = [&](u64 offset){ |
| 242 | + return p2_split[offset] == branch_taken[offset]; |
| 243 | + }; |
| 244 | + val<LINEINST> correct_mask = correct.fo1().concat(); |
| 245 | + correct_mask.fanout(hard<3>{}); |
| 246 | + |
| 247 | + // is the prediction weak? |
| 248 | + auto [theta, tc] = split<YBITS, TCBITS>(theta_and_tc); |
| 249 | + theta.fanout(hard<LINEINST>{}); |
| 250 | + arr<val<1>, LINEINST> weak = [&](u64 offset){ |
| 251 | + auto absy = select(yout[offset] < hard<0>{}, -yout[offset], yout[offset]); |
| 252 | + return absy.fo1() < theta; |
| 253 | + }; |
| 254 | + val<LINEINST> weak_mask = weak.fo1().concat(); |
| 255 | + weak_mask.fanout(hard<2>{}); |
| 256 | + |
| 257 | + // perceptrons are updated if the prediction is wrong or weak |
| 258 | + val<LINEINST> train_mask = branch_mask & (~correct_mask | weak_mask); |
| 259 | + train_mask.fanout(hard<2>{}); |
| 260 | + arr<val<1>,LINEINST> train = train_mask.make_array(val<1>{}); |
| 261 | + |
| 262 | + // ---- P1 (gshare) update ---- |
| 263 | + // did P1 and P2 disagree? |
| 264 | + val<LINEINST> disagree_mask = (p1 ^ p2) & branch_mask; |
| 265 | + disagree_mask.fanout(hard<2>{}); |
| 266 | + arr<val<1>,LINEINST> disagree = disagree_mask.make_array(val<1>{}); |
| 267 | + disagree.fanout(hard<2>{}); |
| 268 | + |
| 269 | + // read P1 hysteresis if P1 and P2 disagree |
| 270 | + arr<val<1>, LINEINST> p1_weak = [&](u64 offset) -> val<1> { |
| 271 | + return execute_if(disagree[offset], [&](){ |
| 272 | + return ~table1_hyst[offset].read(index1); // hyst=0 means weak |
| 273 | + }); |
| 274 | + }; |
| 275 | + |
| 276 | + val<1> extra_cycle = (train_mask != hard<0>{}) | (disagree_mask != hard<0>{}); |
| 277 | + need_extra_cycle(extra_cycle.fo1()); |
| 278 | + |
| 279 | + // update P1 prediction if P1 and P2 disagree and hysteresis bit is weak |
| 280 | + for (u64 offset=0; offset<LINEINST; offset++) { |
| 281 | + execute_if(p1_weak[offset].fo1(), [&](){ |
| 282 | + table1_pred[offset].write(index1, p2_split[offset]); |
| 283 | + }); |
| 284 | + } |
| 285 | + // update P1 hysteresis for executed branches |
| 286 | + for (u64 offset=0; offset<LINEINST; offset++) { |
| 287 | + execute_if(is_branch[offset], [&](){ |
| 288 | + table1_hyst[offset].write(index1, ~disagree[offset]); |
| 289 | + }); |
| 290 | + } |
| 291 | + |
| 292 | + // ---- P2 (hashed perceptron) update ---- |
| 293 | + // weight update |
| 294 | + for (u64 offset=0; offset<LINEINST; offset++) { |
| 295 | + execute_if(train[offset].fo1(), [&](){ |
| 296 | + for (u64 i=0; i<NTABLES; i++) { |
| 297 | + wtable[i][offset].write(index2[i], update_ctr(readw[offset][i], ~branch_taken[offset])); |
| 298 | + } |
| 299 | + }); |
| 300 | + } |
| 301 | + |
| 302 | + // update packed counter |
| 303 | + // decrement on weak & correct |
| 304 | + // increment on mispredict |
| 305 | + theta_and_tc = theta_and_tc - (branch_mask & weak_mask & correct_mask).ones() + (branch_mask & ~correct_mask).ones(); |
| 306 | + |
| 307 | + // ---- update global history ---- |
| 308 | + val<1> line_end = block_entry >> (LINEINST - block_size); |
| 309 | + true_block = ~mispredict.fo1() | branch_dir[num_branch-1] | line_end.fo1(); |
| 310 | + true_block.fanout(hard<MAXHIST+NUMHIST+2>{}); |
| 311 | + execute_if(true_block, [&](){ |
| 312 | + next_pc.fanout(hard<2>{}); |
| 313 | + global_history1 = (global_history1 << 1) ^ val<GHIST1>{next_pc >> 2}; |
| 314 | + gfolds.update(val<PATHBITS>{next_pc >> 2}); |
| 315 | + }); |
| 316 | + |
| 317 | + num_branch = 0; |
| 318 | + } |
| 319 | +}; |
0 commit comments