riscv-boom's ubtb

ubtb

This is the set-associative variant of the Micro BTB, as opposed to the fully-associative FAMicroBTB. It uses indexed addressing like a cache.

Structure:

  • 256 sets by default (configurable)
  • Each entry stores: tag, is_br, 2-bit counter, offset (signed)
  • Offset is stored instead of full target for space efficiency
  • Target = PC + slot_offset + stored_offset

Key Features:

  • SyncReadMem for meta and BTB storage (1-cycle read latency)
  • Write bypass to handle read-after-write hazards
  • Offset-based target calculation for reduced storage

参数

1
2
3
4
5
6
7
8
case class BoomMicroBTBParams(
nSets: Int = 256,
offsetSz: Int = 13
)

val nWrBypassEntries = 2
val btbEntrySz = offsetSz

基本组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MicroBTBEntry extends Bundle {
val offset = SInt(offsetSz.W)
}
class MicroBTBMeta extends Bundle {
val is_br = Bool()
val tag = UInt(tagSz.W)
val ctr = UInt(2.W)
}

val meta = SyncReadMem(nSets, Vec(bankWidth, UInt(btbMetaSz.W)))
val btb = SyncReadMem(nSets, Vec(bankWidth, UInt(btbEntrySz.W)))

// bypass
val wrbypass_idxs = Reg(Vec(nWrBypassEntries, UInt(log2Ceil(nSets).W)))
val wrbypass = Reg(Vec(nWrBypassEntries, Vec(bankWidth, new MicroBTBMeta)))
val wrbypass_enq_idx = RegInit(0.U(log2Ceil(nWrBypassEntries).W))

读取数据

1
2
3
4
5
6
7
8
9
val s1_req_rbtb  = VecInit(btb.read(s0_idx , s0_valid).map(_.asTypeOf(new MicroBTBEntry)))
val s1_req_rmeta = VecInit(meta.read(s0_idx, s0_valid).map(_.asTypeOf(new MicroBTBMeta)))
val s1_req_tag = s1_idx >> log2Ceil(nSets)


val s1_resp = Wire(Vec(bankWidth, Valid(UInt(vaddrBitsExtended.W))))
val s1_taken = Wire(Vec(bankWidth, Bool()))
val s1_is_br = Wire(Vec(bankWidth, Bool()))
val s1_is_jal = Wire(Vec(bankWidth, Bool()))

查询结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for (w <- 0 until bankWidth) {
s1_resp(w).valid := !doing_reset && s1_valid && s1_req_tag(tagSz-1,0) === s1_req_rmeta(w).tag
s1_resp(w).bits := (s1_pc.asSInt + (w << 1).S + s1_req_rbtb(w).offset).asUInt
s1_is_br(w) := !doing_reset && s1_resp(w).valid && s1_req_rmeta(w).is_br
s1_is_jal(w) := !doing_reset && s1_resp(w).valid && !s1_req_rmeta(w).is_br
s1_taken(w) := !doing_reset && (!s1_req_rmeta(w).is_br || s1_req_rmeta(w).ctr(1))
s1_meta.ctrs(w) := s1_req_rmeta(w).ctr
}

for (w <- 0 until bankWidth) {
io.resp.f1(w).predicted_pc := s1_resp(w)
io.resp.f1(w).is_br := s1_is_br(w)
io.resp.f1(w).is_jal := s1_is_jal(w)
io.resp.f1(w).taken := s1_taken(w)

io.resp.f2(w) := RegNext(io.resp.f1(w))
io.resp.f3(w) := RegNext(io.resp.f2(w))
}
io.f3_meta := RegNext(RegNext(s1_meta.asUInt))

更新

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
// 获取 nWrBypassEntries 的命中列表
val wrbypass_hits = VecInit((0 until nWrBypassEntries) map { i =>
!doing_reset &&
wrbypass_idxs(i) === s1_update_idx(log2Ceil(nSets)-1,0)
})
// 在列表中判断是否命中
val wrbypass_hit = wrbypass_hits.reduce(_||_)
// 判断命中的最领先的索引
val wrbypass_hit_idx = PriorityEncoder(wrbypass_hits)

val max_offset_value = (~(0.U)((offsetSz-1).W)).asSInt
val min_offset_value = Cat(1.B, (0.U)((offsetSz-1).W)).asSInt

// 可以看到这里就是在做逆运算求 offset
val new_offset_value = (s1_update.bits.target.asSInt -
(s1_update.bits.pc + (s1_update.bits.cfi_idx.bits << 1)).asSInt)
val s1_update_wbtb_data = Wire(new MicroBTBEntry)
s1_update_wbtb_data.offset := new_offset_value
val s1_update_wbtb_mask = (UIntToOH(s1_update_cfi_idx) &
Fill(bankWidth, s1_update.bits.cfi_idx.valid && s1_update.valid && s1_update.bits.cfi_taken && s1_update.bits.is_commit_update))

val s1_update_wmeta_mask = ((s1_update_wbtb_mask | s1_update.bits.br_mask) &
Fill(bankWidth, s1_update.valid && s1_update.bits.is_commit_update))
val s1_update_wmeta_data = Wire(Vec(bankWidth, new MicroBTBMeta))
for (w <- 0 until bankWidth) {
s1_update_wmeta_data(w).tag := s1_update_idx >> log2Ceil(nSets)
s1_update_wmeta_data(w).is_br := s1_update.bits.br_mask(w)

val was_taken = (s1_update.bits.cfi_idx.valid && s1_update.bits.cfi_idx.bits === w.U &&
(
(s1_update.bits.cfi_is_br && s1_update.bits.cfi_taken) ||
s1_update.bits.cfi_is_jal
)
)
val old_bim_value = Mux(wrbypass_hit, wrbypass(wrbypass_hit_idx)(w).ctr, s1_update_meta.ctrs(w))
s1_update_wmeta_data(w).ctr := bimWrite(old_bim_value, was_taken)
}

写 sram

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
btb.write(
Mux(doing_reset,
reset_idx,
s1_update_idx),
Mux(doing_reset,
VecInit(Seq.fill(bankWidth) { 0.U(btbEntrySz.W) }),
// data = [wbtb_data, wbtb_data, wbtb_data, wbtb_data]
VecInit(Seq.fill(bankWidth) { s1_update_wbtb_data.asUInt })),
Mux(doing_reset,
(~(0.U(bankWidth.W))),
s1_update_wbtb_mask).asBools
)

// def write(addr: UInt, data: Vec[T], mask: Seq[Bool]): Unit
// ──────────── ───────────── ──────────────
// 写入地址 写入数据 写入掩码
// (选择哪个Set) (每个slot的值) (哪些slot真正写入)
meta.write(
Mux(doing_reset,
reset_idx,
s1_update_idx),
Mux(doing_reset,
VecInit(Seq.fill(bankWidth) { 0.U(btbMetaSz.W) }),
VecInit(s1_update_wmeta_data.map(_.asUInt))),
Mux(doing_reset,
(~(0.U(bankWidth.W))),
s1_update_wmeta_mask).asBools
)

when (s1_update_wmeta_mask =/= 0.U) {
when (wrbypass_hit) {
wrbypass(wrbypass_hit_idx) := s1_update_wmeta_data
} .otherwise {
wrbypass(wrbypass_enq_idx) := s1_update_wmeta_data
wrbypass_idxs(wrbypass_enq_idx) := s1_update_idx
wrbypass_enq_idx := WrapInc(wrbypass_enq_idx, nWrBypassEntries)
}
}

框图说明

1. 整体架构

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ μBTB (Micro Branch Target Buffer) │
│ params: nSets=256 │
│ │
│ 特点: 直接映射 (Direct-Mapped), 无 Way 选择, 更快的访问 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 主存储结构 │ │
│ │ │ │
│ │ ┌────────────────────────────┐ ┌────────────────────────────┐ │ │
│ │ │ ubtb_meta │ │ ubtb_data │ │ │
│ │ │ [256 sets] │ │ [256 sets] │ │ │
│ │ │ │ │ │ │ │
│ │ │ ┌──────────────────────┐ │ │ ┌──────────────────────┐ │ │ │
│ │ │ │ Set 0: │ │ │ │ Set 0: │ │ │ │
│ │ │ │ [bankWidth entries] │ │ │ │ [bankWidth entries] │ │ │ │
│ │ │ │ ┌────┬────┬──────┐ │ │ │ │ ┌──────────────┐ │ │ │ │
│ │ │ │ │ctr │is_br│ tag │ │ │ │ │ │ offset │ │ │ │ │
│ │ │ │ │(2b)│(1b) │(tag)│ │ │ │ │ │ (13 bits) │ │ │ │ │
│ │ │ │ └────┴────┴──────┘ │ │ │ │ └──────────────┘ │ │ │ │
│ │ │ │ ... │ │ │ │ ... │ │ │ │
│ │ │ ├──────────────────────┤ │ │ ├──────────────────────┤ │ │ │
│ │ │ │ Set 1 │ │ │ │ Set 1 │ │ │ │
│ │ │ ├──────────────────────┤ │ │ ├──────────────────────┤ │ │ │
│ │ │ │ ... │ │ │ │ ... │ │ │ │
│ │ │ ├──────────────────────┤ │ │ ├──────────────────────┤ │ │ │
│ │ │ │ Set 255 │ │ │ │ Set 255 │ │ │ │
│ │ │ └──────────────────────┘ │ │ └──────────────────────┘ │ │ │
│ │ └────────────────────────────┘ └────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Write Bypass Buffer │ │
│ │ [nWrBypassEntries = 2 entries] │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Entry 0: idx + [bankWidth × MicroBTBMeta] │ │ │
│ │ ├──────────────────────────────────────────────────────────────────┤ │ │
│ │ │ Entry 1: idx + [bankWidth × MicroBTBMeta] │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 用途: 解决 SRAM 写后读 (RAW) 冲突,提供最新的计数器值 │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

2. 数据结构详解

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
┌─────────────────────────────────────────────────────────────────┐
│ MicroBTBEntry (数据项) │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ offset (13 bits) │ │
│ │ 分支目标的 PC 偏移量 (有符号) │ │
│ │ │ │
│ │ 注意: μBTB 没有 extended 位! │ │
│ │ 只能处理 ±4KB 范围内的跳转 │ │
│ │ 远距离跳转需要依赖 BTB │ │
│ └─────────────────────────────────────────────────────────┘ │
│ 共 13 bits │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ MicroBTBMeta (元数据) │
├─────────────────────────────────────────────────────────────────┤
│ ┌────────┬─────────┬──────────────────────────────────────┐ │
│ │ ctr │ is_br │ tag │ │
│ │ (2 bits)│ (1 bit) │ (tagSz bits) │ │
│ └────────┴─────────┴──────────────────────────────────────┘ │
│ │
│ 字段说明: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ctr (2-bit 饱和计数器): │ │
│ │ 00 = 强不跳转 (Strongly Not Taken) │ │
│ │ 01 = 弱不跳转 (Weakly Not Taken) │ │
│ │ 10 = 弱跳转 (Weakly Taken) │ │
│ │ 11 = 强跳转 (Strongly Taken) │ │
│ │ │ │
│ │ is_br: │ │
│ │ 0 = JAL (无条件跳转, 总是 taken) │ │
│ │ 1 = BR (条件分支, 根据 ctr 预测) │ │
│ │ │ │
│ │ tag: 地址高位, 用于验证是否命中 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ 共 (tagSz + 3) bits │
└─────────────────────────────────────────────────────────────────┘

3. 地址索引方式

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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
                            PC 地址结构
┌─────────────────────────────────────────────────────────────────┐
│ vaddrBitsExtended │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────┬──────────────┬──────────────────┬───┐ │
│ │ tag │ idx │ fetchWidth偏移 │ 0 │ │
│ │ (tagSz) │ log2(nSets) │ log2(fetchWidth)│ │ │
│ │ │ = 8 bits │ │ │ │
│ └───────────────────┴──────────────┴──────────────────┴───┘ │
│ │
│ 与 BTB 的区别: │
│ - μBTB: nSets=256, idx=8 bits │
│ - BTB: nSets=128, idx=7 bits │
│ - μBTB 更多 set,但没有 way (直接映射) │
│ │
└─────────────────────────────────────────────────────────────────┘

---
查询流程 (Lookup)

查询流程图


┌─────────────────────────────────────────────────────────────────┐
│ Stage 0 (S0): 发起读请求 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 输入: s0_idx (从 PC 提取的索引) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 并行读取 Meta 和 Data (直接映射,无需选择 Way): │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ meta.read │ │ btb.read │ │ │
│ │ │ (s0_idx) │ │ (s0_idx) │ │ │
│ │ └────────┬────────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ Vec[bankWidth] Vec[bankWidth] │ │
│ │ MicroBTBMeta MicroBTBEntry │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ Stage 1 (S1): Tag 比较 & 预测生成 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 输入: s1_req_rmeta, s1_req_rbtb (读出的数据) │
│ s1_req_tag (从 s1_idx 提取的 tag) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 对每个 bank slot (w = 0 to bankWidth-1): │ │
│ │ │ │
│ │ 1. Tag 比较 (判断命中): │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ hit = (s1_req_tag == s1_req_rmeta(w).tag) │ │ │
│ │ │ │ │ │
│ │ │ s1_resp(w).valid = !doing_reset && │ │ │
│ │ │ s1_valid && hit │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 2. 计算目标地址 (PC 相对): │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ target = s1_pc + (w << 1) + offset │ │ │
│ │ │ ↑ ↑ ↑ │ │ │
│ │ │ 基地址 slot偏移 分支偏移 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 3. 生成分支类型: │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ s1_is_br(w) = valid && is_br │ │ │
│ │ │ s1_is_jal(w) = valid && !is_br │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 4. 预测是否 Taken (使用 2-bit 计数器): │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ s1_taken(w) = !is_br || ctr[1] │ │ │
│ │ │ ↑ ↑ │ │ │
│ │ │ JAL总是taken BR看ctr最高位 │ │ │
│ │ │ │ │ │
│ │ │ ctr[1]=0 → Not Taken (00, 01) │ │ │
│ │ │ ctr[1]=1 → Taken (10, 11) │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ 输出响应 (F1/F2/F3) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ F1 (S1 周期,最快响应!): │ │
│ │ io.resp.f1(w).predicted_pc = s1_resp(w) │ │
│ │ io.resp.f1(w).is_br = s1_is_br(w) │ │
│ │ io.resp.f1(w).is_jal = s1_is_jal(w) │ │
│ │ io.resp.f1(w).taken = s1_taken(w) │ │
│ │ │ │
│ │ F2 (S2 周期): │ │
│ │ io.resp.f2(w) = RegNext(io.resp.f1(w)) │ │
│ │ │ │
│ │ F3 (S3 周期): │ │
│ │ io.resp.f3(w) = RegNext(io.resp.f2(w)) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 关键区别: μBTB 在 F1 就能给出预测结果! │
│ BTB 需要到 F2 才能给出结果 │
│ │
└─────────────────────────────────────────────────────────────────┘

---
更新流程 (Update)

更新流程图


┌─────────────────────────────────────────────────────────────────┐
│ 接收更新请求 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 输入: s1_update (来自后端的更新信息) │
│ - s1_update.bits.pc : 分支指令 PC │
│ - s1_update.bits.target : 实际跳转目标 │
│ - s1_update.bits.cfi_idx : 分支在 fetch block 中的位置 │
│ - s1_update.bits.cfi_taken : 分支是否 taken │
│ - s1_update.bits.br_mask : 哪些位置是分支指令 │
│ - s1_update.bits.cfi_is_br : CFI 是否是条件分支 │
│ - s1_update.bits.cfi_is_jal : CFI 是否是 JAL │
│ - s1_update.bits.meta.ctrs : 预测时的计数器值 │
│ │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ Write Bypass 检查 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 检查 Write Bypass Buffer 是否有更新的值: │ │
│ │ │ │
│ │ for i in 0..nWrBypassEntries: │ │
│ │ wrbypass_hits(i) = (wrbypass_idxs(i) == update_idx) │ │
│ │ │ │
│ │ wrbypass_hit = wrbypass_hits.reduce(OR) │ │
│ │ wrbypass_hit_idx = PriorityEncoder(wrbypass_hits) │ │
│ │ │ │
│ │ 用途: SRAM 有 1 周期延迟,bypass 提供最新写入的值 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ 计算更新数据 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ BTB Data 更新: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ new_offset = target - (pc + cfi_idx << 1) │ │
│ │ │ │
│ │ s1_update_wbtb_data.offset = new_offset │ │
│ │ │ │
│ │ 注意: μBTB 不检查 offset 是否超出范围! │ │
│ │ 远跳转会被截断,依赖 BTB 处理 │ │
│ │ │ │
│ │ 写入掩码: │ │
│ │ wbtb_mask = OH(cfi_idx) & cfi_valid & │ │
│ │ cfi_taken & is_commit_update │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Meta 更新 (包含计数器更新): │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ for each bank slot w: │ │
│ │ │ │
│ │ wmeta_data(w).tag = update_idx >> log2(nSets) │ │
│ │ wmeta_data(w).is_br = br_mask(w) │ │
│ │ │ │
│ │ // 判断该 slot 是否被 taken │ │
│ │ was_taken = cfi_idx == w && │ │
│ │ ((cfi_is_br && cfi_taken) || cfi_is_jal) │ │
│ │ │ │
│ │ // 获取旧的计数器值 (优先使用 bypass) │ │
│ │ old_ctr = wrbypass_hit ? │ │
│ │ wrbypass(hit_idx)(w).ctr : │ │
│ │ update_meta.ctrs(w) │ │
│ │ │ │
│ │ // 2-bit 饱和计数器更新 │ │
│ │ wmeta_data(w).ctr = bimWrite(old_ctr, was_taken) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ 2-bit 饱和计数器更新逻辑 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ bimWrite 函数 │ │
│ │ │ │
│ │ Taken │ │
│ │ ┌─────────┐ │ │
│ │ │ │ │ │
│ │ ┌─────────▼─────────┐ │ │
│ │ ┌────┤ 00 (强NT) │◄───┐ │ │
│ │ │ └─────────┬─────────┘ │ │ │
│ │ │ │ Taken │ Not Taken │ │
│ │ │ Not Taken ▼ │ │ │
│ │ │ ┌─────────────────────┐ │ │ │
│ │ │ │ 01 (弱NT) │──┘ │ │
│ │ │ └─────────┬───────────┘ │ │
│ │ │ │ Taken │ │
│ │ │ ▼ │ │
│ │ │ ┌─────────────────────┐ │ │
│ │ │ │ 10 (弱T) │──┐ │ │
│ │ │ └─────────┬───────────┘ │ Not Taken │ │
│ │ │ │ Taken │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌─────────────────────┐ │ │ │
│ │ └───►│ 11 (强T) │◄─┘ │ │
│ │ └─────────┬───────────┘ │ │
│ │ │ │ │
│ │ └─────────┐ │ │
│ │ Taken │ │ │
│ │ (保持饱和) │ │
│ │ │ │
│ │ 代码逻辑: │ │
│ │ if (v==3 && taken) → 3 (饱和) │ │
│ │ if (v==0 && !taken) → 0 (饱和) │ │
│ │ if (taken) → v + 1 │ │
│ │ else → v - 1 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ 执行写入操作 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. 写入 SRAM (直接映射,无需选择 Way): │ │
│ │ │ │
│ │ btb.write( │ │
│ │ addr = doing_reset ? reset_idx : s1_update_idx, │ │
│ │ data = [wbtb_data × bankWidth], │ │
│ │ mask = wbtb_mask │ │
│ │ ) │ │
│ │ │ │
│ │ meta.write( │ │
│ │ addr = doing_reset ? reset_idx : s1_update_idx, │ │
│ │ data = wmeta_data, │ │
│ │ mask = wmeta_mask │ │
│ │ ) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2. 更新 Write Bypass Buffer: │ │
│ │ │ │
│ │ if (wmeta_mask != 0): │ │
│ │ if (wrbypass_hit): │ │
│ │ // 更新已有条目 │ │
│ │ wrbypass(hit_idx) := wmeta_data │ │
│ │ else: │ │
│ │ // 分配新条目 (FIFO) │ │
│ │ wrbypass(enq_idx) := wmeta_data │ │
│ │ wrbypass_idxs(enq_idx) := update_idx │ │
│ │ enq_idx := WrapInc(enq_idx, 2) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

---
μBTB vs BTB 对比

┌─────────────────────────────────────────────────────────────────────────────────┐
│ μBTB vs BTB 对比 │
├────────────────────┬────────────────────────┬───────────────────────────────────┤
│ 特性 │ μBTB │ BTB │
├────────────────────┼────────────────────────┼───────────────────────────────────┤
│ 组织结构 │ 直接映射 (Direct) │ 2-way 组相联 │
│ │ nSets=256 │ nSets=128, nWays=2 │
├────────────────────┼────────────────────────┼───────────────────────────────────┤
│ 响应延迟 │ F1 (最快!) │ F2 │
├────────────────────┼────────────────────────┼───────────────────────────────────┤
│ 目标地址存储 │ 仅 offset (13-bit) │ offset + eBTB (完整地址) │
│ │ 范围: ±4KB │ 支持任意远距离跳转 │
├────────────────────┼────────────────────────┼───────────────────────────────────┤
│ 分支预测 │ 内置 2-bit 饱和计数器 │ 无 (仅提供目标地址) │
│ │ 可直接预测 taken/NT │ 需要配合其他预测器 │
├────────────────────┼────────────────────────┼───────────────────────────────────┤
│ Write Bypass │ 有 (2 entries) │ 无 │
│ │ 解决 RAW 冲突 │ │
├────────────────────┼────────────────────────┼───────────────────────────────────┤
│ 存储开销 │ 较小 │ 较大 (多 Way + eBTB) │
├────────────────────┼────────────────────────┼───────────────────────────────────┤
│ 冲突处理 │ 直接覆盖 (容易冲突) │ 多 Way 减少冲突 │
├────────────────────┼────────────────────────┼───────────────────────────────────┤
│ 用途 │ 快速首次预测 │ 精确预测, 远距离跳转 │
│ │ 第一级预测器 │ 第二级预测器 │
└────────────────────┴────────────────────────┴───────────────────────────────────┘

---
关键设计要点总结

┌─────────────────────────────────────────────────────────────────┐
│ μBTB 设计要点总结 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 极速响应 - F1 输出: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ • μBTB 专门优化为低延迟 │ │
│ │ • 在 F1 阶段就能给出预测,比 BTB 快 1 个周期 │ │
│ │ • 适合作为前端流水线的第一级预测器 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 2. 直接映射 - 无 Way 选择: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ • 省去 Way 选择逻辑,减少延迟 │ │
│ │ • 代价是更高的冲突率 │ │
│ │ • 通过更多 Sets (256 vs 128) 部分缓解 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 3. 内置 2-bit 计数器: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ • 可以直接进行 taken/not-taken 预测 │ │
│ │ • BTB 不包含计数器,需要配合 BIM 等预测器 │ │
│ │ • 简化预测流程,减少组件间依赖 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 4. Write Bypass Buffer: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ • 2 个条目的小型缓冲区 │ │
│ │ • 解决 SRAM 写后读 (RAW) 延迟问题 │ │
│ │ • 保证计数器更新的正确性 │ │
│ │ • FIFO 替换策略 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 5. 无远距离跳转支持: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ • 只有 13-bit offset,范围 ±4KB │ │
│ │ • 没有 eBTB 扩展 │ │
│ │ • 远距离跳转依赖 BTB 处理 │ │
│ │ • 保持 μBTB 结构简单、低延迟 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 6. 流水线集成: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ S0: SRAM 读取 │ │
│ │ S1/F1: Tag 比较 + 预测输出 (μBTB 主输出) │ │
│ │ F2: RegNext 传递 (BTB 主输出) │ │
│ │ F3: RegNext 传递 (最终确认) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

update 的过程

1. 整体时序概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────────────────────────────┐
│ μBTB 更新的完整生命周期 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 时间线: │
│ │
│ ══════════════════════════════════════════════════════════════════════════ │
│ │ 预测阶段 │ 执行阶段 │ 提交阶段 │ 更新阶段 │ │
│ ══════════════════════════════════════════════════════════════════════════ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ F1预测 │ ─────────────────────► │ 分支执行 │───►│ 提交更新 │ │
│ │ 保存meta│ │ 得到结果 │ │ s1_update│ │
│ └─────────┘ └─────────┘ └──────────┘ │
│ │ │ │
│ │ ctr 值随 meta 一路传递 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

2. 预测阶段:保存 Meta 信息

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 预测阶段 (S0 → S1 → F3) │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ S0 周期: 发起 SRAM 读取 │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ PC = 0x1000 │ │
│ │ s0_idx = PC[11:4] = 0x00 (假设) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ meta SRAM │ │ btb SRAM │ │ │
│ │ │ read(0x00) │ │ read(0x00) │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ (下一周期可用) (下一周期可用) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ S1 周期: 读取完成,生成预测,保存 meta │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ SRAM 返回数据: │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ s1_req_rmeta[bankWidth]: │ │ │
│ │ │ slot 0: {tag=0xABC, is_br=1, ctr=10} ← 条件分支,弱跳转 │ │ │
│ │ │ slot 1: {tag=0xDEF, is_br=0, ctr=11} ← JAL │ │ │
│ │ │ slot 2: {tag=0x000, is_br=0, ctr=00} ← 无效 │ │ │
│ │ │ slot 3: {tag=0x123, is_br=1, ctr=01} ← 条件分支,弱不跳转 │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 生成预测 & 保存 meta: │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ s1_meta.ctrs[0] = 10 ← 保存当前 ctr 值! │ │ │
│ │ │ s1_meta.ctrs[1] = 11 │ │ │
│ │ │ s1_meta.ctrs[2] = 00 │ │ │
│ │ │ s1_meta.ctrs[3] = 01 │ │ │
│ │ │ │ │ │
│ │ │ 这些 ctr 值会随预测结果一起传递给后端! │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ F3 周期: meta 输出到流水线 │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ io.f3_meta := RegNext(RegNext(s1_meta.asUInt)) │ │
│ │ │ │
│ │ meta (包含 ctrs) 随预测结果一起发送给后端 │ │
│ │ 后端会在分支执行/提交时把这个 meta 带回来 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

3. 更新请求到达

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 更新请求结构 (s1_update) │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 经过 N 个周期后,分支执行完毕,提交时发送更新请求: │
│ │
│ s1_update.valid = true │
│ s1_update.bits: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 基本信息: │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ pc = 0x1000 (分支指令所在 fetch block) │ │ │
│ │ │ target = 0x1200 (实际跳转目标) │ │ │
│ │ │ cfi_idx.valid = true (有控制流指令) │ │ │
│ │ │ cfi_idx.bits = 0 (在 slot 0) │ │ │
│ │ │ cfi_taken = true (实际跳转了) │ │ │
│ │ │ cfi_is_br = true (是条件分支) │ │ │
│ │ │ cfi_is_jal = false (不是 JAL) │ │ │
│ │ │ is_commit_update= true (提交时更新) │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 分支掩码: │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ br_mask = 0b1001 (slot 0 和 slot 3 是分支指令) │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 预测时保存的 meta (从 F3 带回来的): │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ meta.ctrs[0] = 10 ← 预测时 slot 0 的 ctr 值 │ │ │
│ │ │ meta.ctrs[1] = 11 │ │ │
│ │ │ meta.ctrs[2] = 00 │ │ │
│ │ │ meta.ctrs[3] = 01 │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

4. Write Bypass 检查

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Step 1: Write Bypass 检查 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ s1_update_idx = 0x00 (从 update.pc 提取) │
│ │
│ 检查 bypass buffer: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Write Bypass Buffer 当前状态: │ │
│ │ ┌─────────┬──────────────────────────────────────────────────────┐ │ │
│ │ │ Entry 0 │ idx=0x05, meta=[ctr=11, ctr=00, ctr=10, ctr=01] │ │ │
│ │ ├─────────┼──────────────────────────────────────────────────────┤ │ │
│ │ │ Entry 1 │ idx=0x10, meta=[ctr=00, ctr=11, ctr=01, ctr=10] │ │ │
│ │ └─────────┴──────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 比较: │ │
│ │ wrbypass_hits[0] = (0x05 == 0x00) = false │ │
│ │ wrbypass_hits[1] = (0x10 == 0x00) = false │ │
│ │ │ │
│ │ wrbypass_hit = false ← 没有命中! │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 结论: 使用 s1_update_meta.ctrs (预测时保存的值) │
│ │
│ ═══════════════════════════════════════════════════════════════════════════ │
│ │
│ 另一种情况 - Bypass 命中: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 假设 bypass buffer 状态: │ │
│ │ ┌─────────┬──────────────────────────────────────────────────────┐ │ │
│ │ │ Entry 0 │ idx=0x00, meta=[ctr=11, ...] ← 刚刚更新过! │ │ │
│ │ └─────────┴──────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ wrbypass_hits[0] = (0x00 == 0x00) = true ✓ │ │
│ │ wrbypass_hit = true │ │
│ │ wrbypass_hit_idx = 0 │ │
│ │ │ │
│ │ 使用 wrbypass[0].ctr = 11 (bypass 中的最新值) │ │
│ │ 而不是 s1_update_meta.ctrs = 10 (过时的值) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

5. 计算更新数据

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Step 2: 计算 BTB Data (offset) │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 计算 offset: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 分支指令 PC = pc + (cfi_idx << 1) │ │
│ │ = 0x1000 + (0 << 1) │ │
│ │ = 0x1000 │ │
│ │ │ │
│ │ new_offset = target - 分支指令PC │ │
│ │ = 0x1200 - 0x1000 │ │
│ │ = 0x200 (512) │ │
│ │ │ │
│ │ s1_update_wbtb_data.offset = 0x200 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 计算 wbtb_mask (哪些 slot 需要写入 BTB data): │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 条件: cfi_idx.valid && cfi_taken && is_commit_update │ │
│ │ true && true && true = true │ │
│ │ │ │
│ │ s1_update_wbtb_mask = UIntToOH(cfi_idx) & Fill(bankWidth, true) │ │
│ │ = UIntToOH(0) & 0b1111 │ │
│ │ = 0b0001 & 0b1111 │ │
│ │ = 0b0001 │ │
│ │ ││││ │ │
│ │ │││└─ slot 0: 写入 ✓ (taken 的分支) │ │
│ │ ││└── slot 1: 不写 │ │
│ │ │└─── slot 2: 不写 │ │
│ │ └──── slot 3: 不写 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────┐
│ Step 3: 计算 Meta Data │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 计算 wmeta_mask (哪些 slot 需要写入 meta): │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ s1_update_wmeta_mask = (wbtb_mask | br_mask) & is_commit_update │ │
│ │ = (0b0001 | 0b1001) & 0b1111 │ │
│ │ = 0b1001 & 0b1111 │ │
│ │ = 0b1001 │ │
│ │ ││││ │ │
│ │ │││└─ slot 0: 写入 ✓ (是分支) │ │
│ │ ││└── slot 1: 不写 │ │
│ │ │└─── slot 2: 不写 │ │
│ │ └──── slot 3: 写入 ✓ (是分支) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 对每个 slot 计算 wmeta_data: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Slot 0 的计算 │ │ │
│ │ ├─────────────────────────────────────────────────────────────────┤ │ │
│ │ │ │ │ │
│ │ │ tag = s1_update_idx >> log2(nSets) = 更新地址的高位 │ │ │
│ │ │ is_br = br_mask[0] = 1 (是条件分支) │ │ │
│ │ │ │ │ │
│ │ │ was_taken 判断: │ │ │
│ │ │ cfi_idx.valid = true │ │ │
│ │ │ cfi_idx.bits == 0 = true (就是 slot 0) │ │ │
│ │ │ cfi_is_br && cfi_taken = true && true = true │ │ │
│ │ │ → was_taken = true │ │ │
│ │ │ │ │ │
│ │ │ 获取旧 ctr: │ │ │
│ │ │ wrbypass_hit = false │ │ │
│ │ │ → old_ctr = s1_update_meta.ctrs[0] = 10 (二进制) │ │ │
│ │ │ │ │ │
│ │ │ 更新 ctr (bimWrite): │ │ │
│ │ │ old_ctr = 10 (弱跳转), was_taken = true │ │ │
│ │ │ → new_ctr = 10 + 1 = 11 (强跳转) │ │ │
│ │ │ │ │ │
│ │ │ wmeta_data[0] = {tag=..., is_br=1, ctr=11} │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Slot 3 的计算 │ │ │
│ │ ├─────────────────────────────────────────────────────────────────┤ │ │
│ │ │ │ │ │
│ │ │ tag = 更新地址的高位 │ │ │
│ │ │ is_br = br_mask[3] = 1 (是条件分支) │ │ │
│ │ │ │ │ │
│ │ │ was_taken 判断: │ │ │
│ │ │ cfi_idx.bits == 3 = false (CFI 在 slot 0,不是 slot 3) │ │ │
│ │ │ → was_taken = false │ │ │
│ │ │ │ │ │
│ │ │ 获取旧 ctr: │ │ │
│ │ │ old_ctr = s1_update_meta.ctrs[3] = 01 (弱不跳转) │ │ │
│ │ │ │ │ │
│ │ │ 更新 ctr (bimWrite): │ │ │
│ │ │ old_ctr = 01, was_taken = false │ │ │
│ │ │ → new_ctr = 01 - 1 = 00 (强不跳转) │ │ │
│ │ │ │ │ │
│ │ │ wmeta_data[3] = {tag=..., is_br=1, ctr=00} │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

6. 执行 SRAM 写入

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Step 4: 写入 SRAM │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ BTB Data 写入: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ btb.write( │ │
│ │ addr = 0x00, │ │
│ │ data = [0x200, 0x200, 0x200, 0x200], ← 所有 slot 用同一个 offset │ │
│ │ mask = [1, 0, 0, 0] ← 只写 slot 0 │ │
│ │ ) │ │
│ │ │ │
│ │ SRAM 操作: │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Set 0x00: │ │ │
│ │ │ slot 0: offset = 0x200 ← 写入新值 │ │ │
│ │ │ slot 1: (保持不变) │ │ │
│ │ │ slot 2: (保持不变) │ │ │
│ │ │ slot 3: (保持不变) │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Meta 写入: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ meta.write( │ │
│ │ addr = 0x00, │ │
│ │ data = [wmeta_data[0], wmeta_data[1], wmeta_data[2], wmeta_data[3]],│ │
│ │ mask = [1, 0, 0, 1] ← 写 slot 0 和 slot 3 │ │
│ │ ) │ │
│ │ │ │
│ │ SRAM 操作: │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Set 0x00: │ │ │
│ │ │ slot 0: {tag, is_br=1, ctr=11} ← 写入,ctr: 10→11 │ │ │
│ │ │ slot 1: (保持不变) │ │ │
│ │ │ slot 2: (保持不变) │ │ │
│ │ │ slot 3: {tag, is_br=1, ctr=00} ← 写入,ctr: 01→00 │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

7. 更新 Write Bypass Buffer

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Step 5: 更新 Write Bypass │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 条件: wmeta_mask != 0 (有 meta 被写入) │
│ 0b1001 != 0 → true │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ wrbypass_hit = false,所以分配新条目: │ │
│ │ │ │
│ │ 之前 bypass buffer: │ │
│ │ ┌─────────┬──────────────────────────────────────────────────────┐ │ │
│ │ │ Entry 0 │ idx=0x05, meta=[...] │ │ │
│ │ ├─────────┼──────────────────────────────────────────────────────┤ │ │
│ │ │ Entry 1 │ idx=0x10, meta=[...] │ │ │
│ │ └─────────┴──────────────────────────────────────────────────────┘ │ │
│ │ wrbypass_enq_idx = 0 │ │
│ │ │ │
│ │ 执行写入: │ │
│ │ wrbypass[0] := wmeta_data │ │
│ │ wrbypass_idxs[0] := 0x00 │ │
│ │ wrbypass_enq_idx := WrapInc(0, 2) = 1 │ │
│ │ │ │
│ │ 之后 bypass buffer: │ │
│ │ ┌─────────┬──────────────────────────────────────────────────────┐ │ │
│ │ │ Entry 0 │ idx=0x00, meta=[ctr=11, -, -, ctr=00] ← 新写入! │ │ │
│ │ ├─────────┼──────────────────────────────────────────────────────┤ │ │
│ │ │ Entry 1 │ idx=0x10, meta=[...] (保持) │ │ │
│ │ └─────────┴──────────────────────────────────────────────────────┘ │ │
│ │ wrbypass_enq_idx = 1 ← 下次写 entry 1 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

8. 完整流程总结图

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ μBTB 更新完整流程 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ s1_update 到达 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 1. 提取 update_idx │ │
│ │ s1_update_idx = update.pc 的索引部分 │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 2. 检查 Write Bypass │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ for each bypass entry: │ │ │
│ │ │ hit = (bypass_idx == update_idx) │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────┴──────────────┐ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Bypass Hit │ │ Bypass Miss │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────────────────┐ ┌───────────────────────────┐ │
│ │ old_ctr = bypass[hit].ctr │ │ old_ctr = update_meta.ctr │ │
│ │ (最新值) │ │ (预测时保存的值) │ │
│ └─────────────┬─────────────┘ └─────────────┬─────────────┘ │
│ │ │ │
│ └──────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 3. 计算新值 │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ new_offset = target - (pc + cfi_idx << 1) │ │ │
│ │ │ new_ctr = bimWrite(old_ctr, was_taken) │ │ │
│ │ │ new_tag = update_idx >> log2(nSets) │ │ │
│ │ │ new_is_br = br_mask[slot] │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 4. 计算写入掩码 │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ wbtb_mask = OH(cfi_idx) & cfi_valid & taken & commit │ │ │
│ │ │ wmeta_mask = (wbtb_mask | br_mask) & commit │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 5. 写入 SRAM │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ btb.write(addr=idx, data=offset, mask=wbtb_mask) │ │ │
│ │ │ meta.write(addr=idx, data=wmeta, mask=wmeta_mask) │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 6. 更新 Write Bypass Buffer │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ if (wmeta_mask != 0): │ │ │
│ │ │ if (bypass_hit): │ │ │
│ │ │ bypass[hit_idx] := wmeta_data // 更新已有条目 │ │ │
│ │ │ else: │ │ │
│ │ │ bypass[enq_idx] := wmeta_data // 分配新条目 │ │ │
│ │ │ bypass_idxs[enq_idx] := idx │ │ │
│ │ │ enq_idx := (enq_idx + 1) % 2 // FIFO │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 更新完成 ✓ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

9. Bypass 解决的实际问题示例

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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Bypass 解决 RAW 冲突的实际例子 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 场景: 循环中的分支,连续两次提交更新 │
│ │
│ for (int i = 0; i < 100; i++) { // 分支在 0x1000 │
│ ... │
│ } │
│ │
│ ═══════════════════════════════════════════════════════════════════════════ │
│ │
│ Cycle N: 第一次迭代提交,分支 taken │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ update arrives: idx=0x10, ctr from meta = 10 │ │
│ │ bypass check: miss │ │
│ │ old_ctr = 10 (from meta) │ │
│ │ new_ctr = 10 + 1 = 11 │ │
│ │ SRAM write: ctr = 11 │ │
│ │ bypass write: entry[0] = {idx=0x10, ctr=11} │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Cycle N+1: 第二次迭代提交,分支又 taken │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ update arrives: idx=0x10, ctr from meta = 10 ← 还是旧值! │ │
│ │ (因为 meta 是预测时保存的,那时 SRAM 里是 10) │ │
│ │ │ │
│ │ bypass check: HIT! entry[0].idx == 0x10 │ │
│ │ old_ctr = 11 (from bypass, 最新值!) │ │
│ │ new_ctr = 11 + 1 = ... 饱和到 11 │ │
│ │ SRAM write: ctr = 11 │ │
│ │ bypass update: entry[0] = {idx=0x10, ctr=11} │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 如果没有 bypass: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Cycle N+1: │ │
│ │ old_ctr = 10 (from meta, 过时!) │ │
│ │ new_ctr = 10 + 1 = 11 │ │
│ │ SRAM write: ctr = 11 │ │
│ │ │ │
│ │ 两次 taken,结果都是 ctr=11,丢失了一次更新! │ │
│ │ 正确应该: taken 两次后 ctr 应该保持 11 (饱和) │ │
│ │ 但如果初始是 01,两次 taken 应该是 01→10→11 │ │
│ │ 没有 bypass 会变成 01→10, 01→10 (错误!) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

总结

阶段 操作 数据来源
预测 读 SRAM,保存 ctr 到 meta SRAM
执行 meta 随预测结果传递到后端 流水线寄存器
提交 发送 update,带回 meta 后端
更新 检查 bypass,获取最新 ctr bypass > meta
更新 计算新 ctr,写 SRAM bimWrite
更新 写 bypass buffer wmeta_data

Write Bypass 的本质: 一个 2 条目的小缓存,记录最近写入 SRAM 的数据,用于解决 SRAM 写延迟导致的 RAW 冲突,保证计数器更新的正确性。

mask

这是 Chisel SyncReadMem 的 带掩码写入 (Masked Write) 功能,允许只更新一个 Set 中的部分 slot,而不是全部覆盖。

1
2
3
4
5
6
SyncReadMem.write 的签名

def write(addr: UInt, data: Vec[T], mask: Seq[Bool]): Unit
// ──────────── ───────────── ──────────────
// 写入地址 写入数据 写入掩码
// (选择哪个Set) (每个slot的值) (哪些slot真正写入)

图解 Mask 写入机制

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

┌─────────────────────────────────────────────────────────────────────────────────┐
│ Masked Write 工作原理 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 假设 bankWidth = 4,写入 addr = 0x10 │
│ │
│ 写入参数: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ addr = 0x10 │ │
│ │ data = [0x200, 0x200, 0x200, 0x200] ← 4 个 slot 的数据 │ │
│ │ mask = [true, false, false, true] ← 0b1001 │ │
│ │ ───── ───── ───── ──── │ │
│ │ slot0 slot1 slot2 slot3 │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ SRAM Set 0x10 写入前: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ slot 0: 0x100 (旧值) │ │
│ │ slot 1: 0x050 (旧值) │ │
│ │ slot 2: 0x080 (旧值) │ │
│ │ slot 3: 0x0A0 (旧值) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Mask 作用: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ slot 0: mask[0]=true → 写入 data[0]=0x200 ✓ │ │
│ │ slot 1: mask[1]=false → 保持旧值 0x050 ✗ │ │
│ │ slot 2: mask[2]=false → 保持旧值 0x080 ✗ │ │
│ │ slot 3: mask[3]=true → 写入 data[3]=0x200 ✓ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ SRAM Set 0x10 写入后: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ slot 0: 0x200 ← 新值 │ │
│ │ slot 1: 0x050 ← 保持不变 │ │
│ │ slot 2: 0x080 ← 保持不变 │ │
│ │ slot 3: 0x200 ← 新值 │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

代码中的两种情况

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

┌─────────────────────────────────────────────────────────────────────────────────┐
│ Reset vs Normal Update │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 情况 1: doing_reset = true (复位阶段) │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ addr = reset_idx (0, 1, 2, ... nSets-1 依次清零) │ │
│ │ data = [0, 0, 0, 0] │ │
│ │ mask = ~0 = 0b1111 = [true, true, true, true] │ │
│ │ │ │
│ │ 效果: 整个 Set 的所有 slot 全部清零 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 情况 2: doing_reset = false (正常更新) │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ addr = s1_update_idx │ │
│ │ data = [wbtb_data, wbtb_data, wbtb_data, wbtb_data] │ │
│ │ mask = s1_update_wbtb_mask (例如 0b0001) │ │
│ │ │ │
│ │ 效果: 只更新 mask 为 1 的 slot,其他保持不变 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

为什么需要 Mask?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

┌─────────────────────────────────────────────────────────────────────────────────┐
│ Mask 的必要性 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 一个 Fetch Block 可能包含多个分支,但只有部分需要更新: │
│ │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ slot 0 │ slot 1 │ slot 2 │ slot 3 │ │
│ │ BR (T) │ ADD │ SUB │ BR (NT) │ │
│ │ taken! │ 非分支 │ 非分支 │ 未执行 │ │
│ └─────────┴─────────┴─────────┴─────────┘ │
│ │
│ 这次更新: │
│ - slot 0 的分支 taken 了,需要更新 offset │
│ - slot 1, 2 不是分支,不更新 │
│ - slot 3 的分支还没执行到 (被 slot 0 跳走了),不更新 │
│ │
│ wbtb_mask = 0b0001 → 只写 slot 0 │
│ │
│ 如果没有 mask,强制写入所有 slot: │
│ - slot 1, 2, 3 的有效数据会被错误覆盖! │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

硬件实现

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

┌─────────────────────────────────────────────────────────────────────────────────┐
│ SRAM Mask 硬件实现 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 每个 slot 有独立的写使能信号: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ addr │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 地址译码器 │ │ │
│ │ └──────┬──────┘ │ │
│ │ │ word_select │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ SRAM Array │ │ │
│ │ │ ┌─────────┬─────────┬─────────┬─────────┐ │ │ │
│ │ │ │ slot 0 │ slot 1 │ slot 2 │ slot 3 │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ WE[0] │ WE[1] │ WE[2] │ WE[3] │ ← 独立写使能│ │ │
│ │ │ │ ↑ │ ↑ │ ↑ │ ↑ │ │ │ │
│ │ │ └────┼────┴────┼────┴────┼────┴────┼────┘ │ │ │
│ │ └───────┼─────────┼─────────┼─────────┼──────────────────┘ │ │
│ │ │ │ │ │ │ │
│ │ mask[0] mask[1] mask[2] mask[3] │ │
│ │ (true) (false) (false) (true) │ │
│ │ │ │ │ │ │ │
│ │ WE[0]=1 WE[1]=0 WE[2]=0 WE[3]=1 │ │
│ │ 写入! 不写 不写 写入! │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

总结

概念 说明
mask 布尔向量,指定哪些 slot 真正写入
mask[i]=true slot i 写入 data[i]
mask[i]=false slot i 保持原值不变
Reset 时 mask 全 1,清零整个 Set
Update 时 mask 只在需要更新的 slot 为 1
硬件实现 每个 slot 有独立的写使能 (Write Enable)

这就是 字节/字掩码写入 (Byte/Word Mask Write),是 SRAM 的标准功能,Chisel 的 SyncReadMem 直接支持这个特性。


riscv-boom's ubtb
http://blog.luliang.online/2025/12/02/分支预测器ubtb/
作者
Luyoung
发布于
2025年12月2日
许可协议