move code around to get set associative cache working
[soc.git] / src / TLB / ariane / tlb.py
1 """
2 # Copyright 2018 ETH Zurich and University of Bologna.
3 # Copyright and related rights are licensed under the Solderpad Hardware
4 # License, Version 0.51 (the "License"); you may not use this file except in
5 # compliance with the License. You may obtain a copy of the License at
6 # http:#solderpad.org/licenses/SHL-0.51. Unless required by applicable law
7 # or agreed to in writing, software, hardware and materials distributed under
8 # this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
9 # CONDITIONS OF ANY KIND, either express or implied. See the License for the
10 # specific language governing permissions and limitations under the License.
11 #
12 # Author: David Schaffenrath, TU Graz
13 # Author: Florian Zaruba, ETH Zurich
14 # Date: 21.4.2017
15 # Description: Translation Lookaside Buffer, SV39
16 # fully set-associative
17
18 Implementation in c++:
19 https://raw.githubusercontent.com/Tony-Hu/TreePLRU/master/TreePLRU.cpp
20
21 Text description:
22 https://people.cs.clemson.edu/~mark/464/p_lru.txt
23
24 Online simulator:
25 http://www.ntu.edu.sg/home/smitha/ParaCache/Paracache/vm.html
26 """
27 from math import log2
28 from nmigen import Signal, Module, Cat, Const, Array
29 from nmigen.cli import verilog, rtlil
30 from nmigen.lib.coding import Encoder
31
32 from ptw import TLBUpdate, PTE, ASID_WIDTH
33 from plru import PLRU
34 from tlb_content import TLBContent
35
36 TLB_ENTRIES = 8
37
38 class TLB:
39 def __init__(self, tlb_entries=8, asid_width=8):
40 self.tlb_entries = tlb_entries
41 self.asid_width = asid_width
42
43 self.flush_i = Signal() # Flush signal
44 # Lookup signals
45 self.lu_access_i = Signal()
46 self.lu_asid_i = Signal(self.asid_width)
47 self.lu_vaddr_i = Signal(64)
48 self.lu_content_o = PTE()
49 self.lu_is_2M_o = Signal()
50 self.lu_is_1G_o = Signal()
51 self.lu_hit_o = Signal()
52 # Update TLB
53 self.pte_width = len(self.lu_content_o.flatten())
54 self.update_i = TLBUpdate(asid_width)
55
56 def elaborate(self, platform):
57 m = Module()
58
59 vpn2 = Signal(9)
60 vpn1 = Signal(9)
61 vpn0 = Signal(9)
62
63 #-------------
64 # Translation
65 #-------------
66
67 # SV39 defines three levels of page tables
68 m.d.comb += [ vpn0.eq(self.lu_vaddr_i[12:21]),
69 vpn1.eq(self.lu_vaddr_i[21:30]),
70 vpn2.eq(self.lu_vaddr_i[30:39]),
71 ]
72
73 tc = []
74 for i in range(self.tlb_entries):
75 tlc = TLBContent(self.pte_width, self.asid_width)
76 setattr(m.submodules, "tc%d" % i, tlc)
77 tc.append(tlc)
78 # connect inputs
79 tlc.update_i = self.update_i # saves a lot of graphviz links
80 m.d.comb += [tlc.vpn0.eq(vpn0),
81 tlc.vpn1.eq(vpn1),
82 tlc.vpn2.eq(vpn2),
83 tlc.flush_i.eq(self.flush_i),
84 #tlc.update_i.eq(self.update_i),
85 tlc.lu_asid_i.eq(self.lu_asid_i)]
86 tc = Array(tc)
87
88 #--------------
89 # Select hit
90 #--------------
91
92 # use Encoder to select hit index
93 # XXX TODO: assert that there's only one valid entry (one lu_hit)
94 hitsel = Encoder(self.tlb_entries)
95 m.submodules.hitsel = hitsel
96
97 hits = []
98 for i in range(self.tlb_entries):
99 hits.append(tc[i].lu_hit_o)
100 m.d.comb += hitsel.i.eq(Cat(*hits)) # (goes into plru as well)
101 idx = hitsel.o
102
103 active = Signal(reset_less=True)
104 m.d.comb += active.eq(~hitsel.n)
105 with m.If(active):
106 # active hit, send selected as output
107 m.d.comb += [ self.lu_is_1G_o.eq(tc[idx].lu_is_1G_o),
108 self.lu_is_2M_o.eq(tc[idx].lu_is_2M_o),
109 self.lu_hit_o.eq(1),
110 self.lu_content_o.flatten().eq(tc[idx].lu_content_o),
111 ]
112
113 #--------------
114 # PLRU.
115 #--------------
116
117 p = PLRU(self.tlb_entries)
118 plru_tree = Signal(p.TLBSZ)
119 m.submodules.plru = p
120
121 # connect PLRU inputs/outputs
122 # XXX TODO: assert that there's only one valid entry (one replace_en)
123 en = []
124 for i in range(self.tlb_entries):
125 en.append(tc[i].replace_en_i)
126 m.d.comb += [Cat(*en).eq(p.replace_en_o), # output from PLRU into tags
127 p.lu_hit.eq(hitsel.i),
128 p.lu_access_i.eq(self.lu_access_i),
129 p.plru_tree.eq(plru_tree)]
130 m.d.sync += plru_tree.eq(p.plru_tree_o)
131
132 #--------------
133 # Sanity checks
134 #--------------
135
136 assert (self.tlb_entries % 2 == 0) and (self.tlb_entries > 1), \
137 "TLB size must be a multiple of 2 and greater than 1"
138 assert (self.asid_width >= 1), \
139 "ASID width must be at least 1"
140
141 return m
142
143 """
144 # Just for checking
145 function int countSetBits(logic[self.tlb_entries-1:0] vector);
146 automatic int count = 0;
147 foreach (vector[idx]) begin
148 count += vector[idx];
149 end
150 return count;
151 endfunction
152
153 assert property (@(posedge clk_i)(countSetBits(lu_hit) <= 1))
154 else $error("More then one hit in TLB!"); $stop(); end
155 assert property (@(posedge clk_i)(countSetBits(replace_en) <= 1))
156 else $error("More then one TLB entry selected for next replace!");
157 """
158
159 def ports(self):
160 return [self.flush_i, self.lu_access_i,
161 self.lu_asid_i, self.lu_vaddr_i,
162 self.lu_is_2M_o, self.lu_is_1G_o, self.lu_hit_o,
163 ] + self.lu_content_o.ports() + self.update_i.ports()
164
165 if __name__ == '__main__':
166 tlb = TLB()
167 vl = rtlil.convert(tlb, ports=tlb.ports())
168 with open("test_tlb.il", "w") as f:
169 f.write(vl)
170