The TLB caches VPN-to-PPN mappings plus the permission bits. A hit returns the PPN in about a cycle; a miss starts a page-table walk.
Entries are tagged by VPN and address-space tag (ASID). The tag lets several processes share the TLB without flushing on every switch.
Structure | Typical shape | Role |
|---|---|---|
Split L1 TLBs | Small, fully associative, one for instructions and one for data | Single-cycle lookup beside the pipeline. |
Unified L2 TLB | Larger and set associative, around hundreds to thousands of entries | Backs both L1 TLBs on a miss. |
Page-walk caches | Caches of upper-level table entries | Shorten walks by skipping cached levels. |
Reach is entries times page size. It bounds the working set that runs without page walks.
reach = entries x page size
L1 example: 64 x 4 KiB = 64 x 4096 B = 262144 B = 256 KiB
L2 example: 1024 x 4 KiB = 4194304 B = 4 MiB
a 2 MiB working set overflows the 256 KiB reach above and leans on the L2 TLBAverage cost follows the cache equation: hit cost plus miss rate times walk cost. Small miss rates still hurt when walks reach DRAM.
assume TLB hit 1 cycle, cached walk 60 cycles, miss rate 1%
average = 1 + 0.01 x 60 = 1 + 0.6 = 1.6 cycles per translation
DRAM-walk case: miss rate 0.1%, walk 960 cycles
average = 1 + 0.001 x 960 = 1 + 0.96 = 1.96 cycles per translationMappings change, so entries must be invalidated: one page, a range, or the whole TLB. A context switch without tagged entries flushes everything.
Multiprocessors broadcast shootdowns to other cores. Stale entries after remapping cause silent corruption, so the invalidation must complete before reuse.
The model below is a plain-SystemVerilog model of the mechanism. An 8-entry fully associative TLB with a round-robin victim pointer and a walk counter runs 28 lookups.
module tb_tlb;
localparam int N = 8;
localparam int W = 8;
logic valid_q [N];
logic [W-1:0] tag_q [N];
logic [W-1:0] dat_q [N];
int unsigned victim_q;
int unsigned walk_q;
int unsigned hit_q;
int unsigned bad;
logic was_hit;
logic [W-1:0] got_ppn;
function automatic logic [W-1:0] pt_walk(input logic [W-1:0] vpn);
pt_walk = vpn + 8'h20;
endfunction
task automatic lookup(input logic [W-1:0] vpn, output logic hit_flag, output logic [W-1:0] ppn);
hit_flag = 1'b0;
ppn = '0;
for (int i = 0; i < N; i++) begin
if (valid_q[i] && (tag_q[i] == vpn)) begin
hit_flag = 1'b1;
ppn = dat_q[i];
end
end
if (hit_flag) begin
hit_q++;
end else begin
walk_q++;
ppn = pt_walk(vpn);
valid_q[victim_q] = 1'b1;
tag_q[victim_q] = vpn;
dat_q[victim_q] = ppn;
victim_q = victim_q + 1;
if (victim_q == N) victim_q = 0;
end
endtask
initial begin
for (int i = 0; i < N; i++) valid_q[i] = 1'b0;
victim_q = 0;
walk_q = 0;
hit_q = 0;
bad = 0;
for (int k = 0; k < 8; k++) begin
lookup(k[7:0], was_hit, got_ppn);
if (was_hit) bad++;
if (got_ppn !== pt_walk(k[7:0])) bad++;
end
for (int k = 0; k < 8; k++) begin
lookup(k[7:0], was_hit, got_ppn);
if (!was_hit) bad++;
if (got_ppn !== pt_walk(k[7:0])) bad++;
end
for (int k = 8; k < 12; k++) begin
lookup(k[7:0], was_hit, got_ppn);
if (was_hit) bad++;
if (got_ppn !== pt_walk(k[7:0])) bad++;
end
for (int k = 4; k < 8; k++) begin
lookup(k[7:0], was_hit, got_ppn);
if (!was_hit) bad++;
if (got_ppn !== pt_walk(k[7:0])) bad++;
end
for (int k = 0; k < 4; k++) begin
lookup(k[7:0], was_hit, got_ppn);
if (was_hit) bad++;
if (got_ppn !== pt_walk(k[7:0])) bad++;
end
if (hit_q != 12) bad++;
if (walk_q != 16) bad++;
if (bad == 0) $display("PASS: 28 lookups give 12 hits and 16 walks");
else $display("FAIL: %0d bad checks", bad);
$finish;
end
endmodule256 KiB. The loop overflows the reach, so most pages walk at least once.
VPNs 8 to 11 evicted entries 0 to 3 under round-robin replacement. Entries 4 to 7 still hold their mappings when re-touched first.