add TLB elaboratable
[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, SV48
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, Elaboratable
29 from nmigen.cli import verilog, rtlil
30 from nmigen.lib.coding import Encoder
31
32 from TLB.ariane.ptw import TLBUpdate, PTE, ASID_WIDTH
33 from TLB.ariane.plru import PLRU
34 from TLB.ariane.tlb_content import TLBContent
35
36 TLB_ENTRIES = 8
37
38 class TLB(Elaboratable):
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 vpn3 = Signal(9) #FIXME unused signal
60 vpn2 = Signal(9)
61 vpn1 = Signal(9)
62 vpn0 = Signal(9)
63
64 #-------------
65 # Translation
66 #-------------
67
68 # SV48 defines four levels of page tables
69 m.d.comb += [ vpn0.eq(self.lu_vaddr_i[12:21]),
70 vpn1.eq(self.lu_vaddr_i[21:30]),
71 vpn2.eq(self.lu_vaddr_i[30:39]), ### PLATEN ### OK
72 vpn3.eq(self.lu_vaddr_i[39:48]), ### PLATEN ### now using SV48
73 ]
74
75 tc = []
76 for i in range(self.tlb_entries):
77 tlc = TLBContent(self.pte_width, self.asid_width)
78 setattr(m.submodules, "tc%d" % i, tlc)
79 tc.append(tlc)
80 # connect inputs
81 tlc.update_i = self.update_i # saves a lot of graphviz links
82 m.d.comb += [tlc.vpn0.eq(vpn0),
83 tlc.vpn1.eq(vpn1),
84 tlc.vpn2.eq(vpn2),
85 # TODO 4th
86 tlc.flush_i.eq(self.flush_i),
87 #tlc.update_i.eq(self.update_i),
88 tlc.lu_asid_i.eq(self.lu_asid_i)]
89 tc = Array(tc)
90
91 #--------------
92 # Select hit
93 #--------------
94
95 # use Encoder to select hit index
96 # XXX TODO: assert that there's only one valid entry (one lu_hit)
97 hitsel = Encoder(self.tlb_entries)
98 m.submodules.hitsel = hitsel
99
100 hits = []
101 for i in range(self.tlb_entries):
102 hits.append(tc[i].lu_hit_o)
103 m.d.comb += hitsel.i.eq(Cat(*hits)) # (goes into plru as well)
104 idx = hitsel.o
105
106 active = Signal(reset_less=True)
107 m.d.comb += active.eq(~hitsel.n)
108 with m.If(active):
109 # active hit, send selected as output
110 m.d.comb += [ self.lu_is_1G_o.eq(tc[idx].lu_is_1G_o),
111 self.lu_is_2M_o.eq(tc[idx].lu_is_2M_o),
112 self.lu_hit_o.eq(1),
113 self.lu_content_o.flatten().eq(tc[idx].lu_content_o),
114 ]
115
116 #--------------
117 # PLRU.
118 #--------------
119
120 p = PLRU(self.tlb_entries)
121 plru_tree = Signal(p.TLBSZ)
122 m.submodules.plru = p
123
124 # connect PLRU inputs/outputs
125 # XXX TODO: assert that there's only one valid entry (one replace_en)
126 en = []
127 for i in range(self.tlb_entries):
128 en.append(tc[i].replace_en_i)
129 m.d.comb += [Cat(*en).eq(p.replace_en_o), # output from PLRU into tags
130 p.lu_hit.eq(hitsel.i),
131 p.lu_access_i.eq(self.lu_access_i),
132 p.plru_tree.eq(plru_tree)]
133 m.d.sync += plru_tree.eq(p.plru_tree_o)
134
135 #--------------
136 # Sanity checks
137 #--------------
138
139 assert (self.tlb_entries % 2 == 0) and (self.tlb_entries > 1), \
140 "TLB size must be a multiple of 2 and greater than 1"
141 assert (self.asid_width >= 1), \
142 "ASID width must be at least 1"
143
144 return m
145
146 """
147 # Just for checking
148 function int countSetBits(logic[self.tlb_entries-1:0] vector);
149 automatic int count = 0;
150 foreach (vector[idx]) begin
151 count += vector[idx];
152 end
153 return count;
154 endfunction
155
156 assert property (@(posedge clk_i)(countSetBits(lu_hit) <= 1))
157 else $error("More then one hit in TLB!"); $stop(); end
158 assert property (@(posedge clk_i)(countSetBits(replace_en) <= 1))
159 else $error("More then one TLB entry selected for next replace!");
160 """
161
162 def ports(self):
163 return [self.flush_i, self.lu_access_i,
164 self.lu_asid_i, self.lu_vaddr_i,
165 self.lu_is_2M_o, self.lu_is_1G_o, self.lu_hit_o,
166 ] + self.lu_content_o.ports() + self.update_i.ports()
167
168 if __name__ == '__main__':
169 tlb = TLB()
170 vl = rtlil.convert(tlb, ports=tlb.ports())
171 with open("test_tlb.il", "w") as f:
172 f.write(vl)
173