-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtlb_l2.sv
315 lines (282 loc) · 13.5 KB
/
tlb_l2.sv
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
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 06/23/2021 02:44:00 PM
// Design Name:
// Module Name: tlb_l2
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tlb_l2 import ariane_pkg::*; #(
parameter int unsigned TLB_SETS = 16,
parameter int unsigned TLB_WAYS = 8,
parameter int unsigned ASID_WIDTH = 1,
parameter int unsigned PAGE_LEVELS = 3
)(
input logic clk_i, // Clock
input logic rst_ni, // Asynchronous reset active low
input logic flush_i, // Flush signal
// Update TLB
input tlb_update_t update_i,
input logic ptw_active_i,
// Lookup signals
input logic lu_access_i,
input logic [ASID_WIDTH-1:0] lu_asid_i,
input logic [riscv::VLEN-1:0] lu_vaddr_i,
output riscv::pte_t lu_content_o,
input logic [ASID_WIDTH-1:0] asid_to_be_flushed_i,
input logic [riscv::VLEN-1:0] vaddr_to_be_flushed_i,
output logic lu_is_2M_o,
output logic lu_is_1G_o,
output logic lu_hit_o,
output logic all_hashes_checked_o
);
`define K $clog2(TLB_SETS)
`define ALL_ENTRIES (TLB_SETS * TLB_WAYS)
// SV39 defines three levels of page tables
struct packed {
logic [ASID_WIDTH-1:0] asid;
logic [8:0] vpn2;
logic [8:0] vpn1;
logic [8:0] vpn0;
logic is_2M;
logic is_1G;
logic valid;
} [TLB_SETS-1:0][TLB_WAYS-1:0] tags_q, tags_n;
riscv::pte_t [TLB_SETS-1:0][TLB_WAYS-1:0] content_q, content_n;
logic [2:0][8:0] vpn; // assumption of SV39 mode and VLEN = 64 as a default
logic [TLB_SETS-1:0][TLB_WAYS-1:0] lu_hit;
logic [TLB_SETS-1:0][TLB_WAYS-1:0] replace_en;
//-------------
// Translation
//-------------
assign vpn[0] = lu_vaddr_i[20:12];
assign vpn[1] = lu_vaddr_i[29:21];
assign vpn[2] = lu_vaddr_i[38:30];
logic [`K-1:0] ind; // set index
logic [1:0] hash_ord_q, hash_ord_n; // hash-rehash order
logic hit_flag; // notifies order counter about hit
assign ind = vpn[hash_ord_q][`K-1:0];
always_comb begin : translation
// default assignment
lu_hit = '{default: 0};
lu_hit_o = 1'b0;
lu_content_o = '{default: 0};
lu_is_1G_o = 1'b0;
lu_is_2M_o = 1'b0;
all_hashes_checked_o = 1'b0;
hash_ord_n = hash_ord_q;
hit_flag = 1'b0;
// if ptw is in progress do not update counters
// these contraints are necessary to keep valid counter values
if (lu_access_i && !ptw_active_i) begin
for (int unsigned i = 0; i < TLB_WAYS; i++) begin
if (tags_q[ind][i].valid && ((lu_asid_i == tags_q[ind][i].asid) || content_q[ind][i].g) && vpn[2][8:`K] == tags_q[ind][i].vpn2[8:`K]) begin
if (tags_q[ind][i].is_1G) begin
lu_is_1G_o = 1'b1;
lu_content_o = content_q[ind][i];
lu_hit_o = 1'b1;
lu_hit[ind][i] = 1'b1;
hit_flag = 1'b1;
// not a giga page hit so check further
end else if (vpn[1][8:`K] == tags_q[ind][i].vpn1[8:`K]) begin
// this could be a 2 mega page hit or a 4 kB hit
// output accordingly
if (tags_q[ind][i].is_2M || vpn[0][8:`K] == tags_q[ind][i].vpn0[8:`K]) begin
lu_is_2M_o = tags_q[ind][i].is_2M;
lu_content_o = content_q[ind][i];
lu_hit_o = 1'b1;
lu_hit[ind][i] = 1'b1;
hit_flag = 1'b1;
end
end
end
end
// avoid overflow for the last miss
if (hit_flag) begin
hash_ord_n = 1'b0;
all_hashes_checked_o = 1'b1; // validate hash check ahead of time if tlb hits
end else begin
if (hash_ord_q < 'd2) begin
hash_ord_n += 1;
end else begin
hash_ord_n = 1'b0;
all_hashes_checked_o = 1'b1; // set once all hashes are checked
end
end
end
end
logic asid_to_be_flushed_is0; // indicates that the ASID provided by SFENCE.VMA (rs2)is 0, active high
logic vaddr_to_be_flushed_is0; // indicates that the VADDR provided by SFENCE.VMA (rs1)is 0, active high
assign asid_to_be_flushed_is0 = ~(|asid_to_be_flushed_i);
assign vaddr_to_be_flushed_is0 = ~(|vaddr_to_be_flushed_i);
// ------------------
// Update and Flush
// ------------------
always_comb begin : update_flush
tags_n = tags_q;
content_n = content_q;
// traversing all entries is inevitable, because of 4 possible flushes
for (int unsigned i = 0; i < TLB_SETS; i++) begin
for (int unsigned j = 0; j < TLB_WAYS; j++) begin
if (flush_i) begin
// invalidate logic
if (lu_asid_i == 1'b0) // flush everything if ASID is 0
tags_n[i][j].valid = 1'b0;
else if (lu_asid_i == tags_q[i][j].asid) // just flush entries from this ASID
tags_n[i][j].valid = 1'b0;
// normal replacement
// compare vpns for all possible set indices with respect to page size
end else if (update_i.valid && ((update_i.is_1G && (i == update_i.vpn[17+`K:18])) ||
(update_i.is_2M && (i == update_i.vpn[8+`K:9])) ||
(!update_i.is_1G && !update_i.is_2M && (i == update_i.vpn[`K-1:0])))
&& replace_en[i][j]) begin
// update tag array
tags_n[i][j] = '{
asid: update_i.asid,
vpn2: update_i.vpn [26:18],
vpn1: update_i.vpn [17:9],
vpn0: update_i.vpn [8:0],
is_1G: update_i.is_1G,
is_2M: update_i.is_2M,
valid: 1'b1
};
// and content as well
content_n[i][j] = update_i.content;
end
end
end
end
// -----------------------------------------------
// PLRU - Pseudo Least Recently Used Replacement
// -----------------------------------------------
logic [TLB_SETS-1:0][TLB_WAYS-2:0] plru_tree_q, plru_tree_n;
always_comb begin : plru_replacement
plru_tree_n = plru_tree_q;
// The PLRU-tree indexing:
// lvl0 0
// / \
// / \
// lvl1 1 2
// / \ / \
// lvl2 3 4 5 6
// / \ /\/\ /\
// ... ... ... ...
// Just predefine which nodes will be set/cleared
// E.g. for a TLB with 8 entries, the for-loop is semantically
// equivalent to the following pseudo-code:
// unique case (1'b1)
// lu_hit[7]: plru_tree_n[0, 2, 6] = {1, 1, 1};
// lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0};
// lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1};
// lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0};
// lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1};
// lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0};
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
// default: begin /* No hit */ end
// endcase
for (int unsigned i = 0; i < TLB_WAYS; i++) begin
automatic int unsigned idx_base, shift, new_index;
// we got a hit so update the pointer as it was least recently used
if (lu_hit[ind][i] & all_hashes_checked_o) begin
// Set the nodes to the values we would expect
for (int unsigned lvl = 0; lvl < $clog2(TLB_WAYS); lvl++) begin
idx_base = $unsigned((2**lvl)-1);
// lvl0 <=> MSB, lvl1 <=> MSB-1, ...
shift = $clog2(TLB_WAYS) - lvl;
// to circumvent the 32 bit integer arithmetic assignment
new_index = ~((i >> (shift-1)) & 32'b1);
plru_tree_n[ind][idx_base + (i >> shift)] = new_index[0];
end
end
end
// Decode tree to write enable signals
// Next for-loop basically creates the following logic for e.g. an 8 entry
// TLB (note: pseudo-code obviously):
// replace_en[7] = &plru_tree_q[ 6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,1}
// replace_en[6] = &plru_tree_q[~6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,0}
// replace_en[5] = &plru_tree_q[ 5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,1}
// replace_en[4] = &plru_tree_q[~5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,0}
// replace_en[3] = &plru_tree_q[ 4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,1}
// replace_en[2] = &plru_tree_q[~4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,0}
// replace_en[1] = &plru_tree_q[ 3,~1,~0]; //plru_tree_q[0,1,3]=={0,0,1}
// replace_en[0] = &plru_tree_q[~3,~1,~0]; //plru_tree_q[0,1,3]=={0,0,0}
// For each entry traverse the tree. If every tree-node matches,
// the corresponding bit of the entry's index, this is
// the next entry to replace.
// This is a bit awful, but helps to keep replace enable result in the same cycle with plru tree update
for (int unsigned i = 0; i < TLB_SETS; i++) begin
for (int unsigned j = 0; j < TLB_WAYS; j ++) begin
automatic logic en;
automatic int unsigned idx_base, shift, new_index;
en = 1'b1;
for (int unsigned lvl = 0; lvl < $clog2(TLB_WAYS); lvl++) begin
idx_base = $unsigned((2**lvl)-1);
// lvl0 <=> MSB, lvl1 <=> MSB-1, ...
shift = $clog2(TLB_WAYS) - lvl;
// en &= plru_tree_q[idx_base + (i>>shift)] == ((i >> (shift-1)) & 1'b1);
new_index = (j >> (shift-1)) & 32'b1;
if (new_index[0]) begin
en &= plru_tree_q[i][idx_base + (j >> shift)];
end else begin
en &= ~plru_tree_q[i][idx_base + (j >> shift)];
end
end
replace_en[i][j] = en;
end
end
end
// sequential process
always_ff @(posedge clk_i or negedge rst_ni) begin
if(~rst_ni) begin
tags_q <= '{default: 0};
content_q <= '{default: 0};
plru_tree_q <= '{default: 0};
hash_ord_q <= 1'b0;
end else begin
tags_q <= tags_n;
content_q <= content_n;
plru_tree_q <= plru_tree_n;
hash_ord_q <= hash_ord_n;
end
end
// //--------------
// // Sanity checks
// //--------------
// //pragma translate_off
// `ifndef VERILATOR
// initial begin : p_assertions
// assert ((TLB_SETS % 2 == 0) && (TLB_SETS > 1))
// else begin $error("TLB SETS must be a multiple of 2 and greater than 1"); $stop(); end
// assert ((TLB_WAYS % 2 == 0) && (TLB_WAYS > 1))
// else begin $error("TLB WAYS must be a multiple of 2 and greater than 1"); $stop(); end
// assert (ASID_WIDTH >= 1)
// else begin $error("ASID width must be at least 1"); $stop(); end
// end
// // Just for checking
// function int countSetBits(logic[TLB_SETS-1:0][TLB_WAYS-1:0] vector);
// automatic int count = 0;
// foreach (vector[i, j]) begin
// count += vector[i][j];
// end
// return count;
// endfunction
// assert property (@(posedge clk_i)(countSetBits(lu_hit) <= 1))
// else begin $error("More then one hit in TLB!"); $stop(); end
// assert property (@(posedge clk_i)(countSetBits(replace_en) <= 1))
// else begin $error("More then one TLB entry selected for next replace!"); $stop(); end
// `endif
// //pragma translate_on
endmodule