periph: conform with nmigen-soc breaking changes.
[lambdasoc.git] / lambdasoc / periph / sdram.py
1 from nmigen import *
2 from nmigen.asserts import *
3 from nmigen.utils import log2_int
4
5 from nmigen_soc import wishbone
6 from nmigen_soc.memory import MemoryMap
7 from nmigen_soc.periph import ConstantMap
8
9 from . import Peripheral
10
11 from ..cores import litedram
12
13
14 __all__ = ["WritebackCache", "SDRAMPeripheral"]
15
16
17 class WritebackCache(Elaboratable):
18 """Write-back cache.
19
20 A write-back cache designed to bridge the SoC interconnect to LiteDRAM.
21
22 Parameters
23 ----------
24 dram_port : :class:`litedram.NativePort`
25 LiteDRAM user port.
26 size : int
27 Cache size.
28 data_width : int
29 Initiator bus data width.
30 granularity : int
31 Initiator bus granularity.
32 dirty_init : bool
33 Dirty initialization. Defaults to ``False``. May be useful for simulation.
34
35 Attributes
36 ----------
37 intr_bus : :class:`nmigen_soc.wishbone.Interface`
38 Initiator bus, with support for incremental bursts.
39 """
40 def __init__(self, dram_port, *, size, data_width, granularity=None, dirty_init=False):
41 if not isinstance(dram_port, litedram.NativePort):
42 raise TypeError("DRAM port must be an instance of lambdasoc.cores.litedram.NativePort, "
43 "not {!r}"
44 .format(dram_port))
45 if not isinstance(size, int) or size <= 0 or size & size - 1:
46 raise ValueError("Cache size must be a positive power of two integer, not {!r}"
47 .format(size))
48 if not isinstance(data_width, int) or data_width <= 0 or data_width & data_width - 1:
49 raise ValueError("Data width must be a positive power of two integer, not {!r}"
50 .format(data_width))
51 if dram_port.data_width % data_width != 0:
52 raise ValueError("DRAM port data width must be a multiple of data width, but {} is "
53 "not a multiple of {}"
54 .format(dram_port.data_width, data_width))
55
56 self.intr_bus = wishbone.Interface(
57 addr_width = dram_port.addr_width + log2_int(dram_port.data_width // data_width),
58 data_width = data_width,
59 granularity = granularity,
60 features = {"cti", "bte"},
61 )
62 intr_map = MemoryMap(
63 addr_width = self.intr_bus.addr_width + log2_int(data_width // granularity),
64 data_width = granularity,
65 )
66 try:
67 intr_map.add_window(dram_port.memory_map)
68 except AttributeError:
69 pass
70 self.intr_bus.memory_map = intr_map
71
72 self.dram_port = dram_port
73 self.size = size
74
75 self.dirty_init = bool(dirty_init)
76
77 def elaborate(self, platform):
78 m = Module()
79
80 ratio = self.dram_port.data_width // self.intr_bus.data_width
81 nb_lines = (self.size * self.intr_bus.granularity) // self.dram_port.data_width
82
83 intr_adr = Record([
84 ("offset", log2_int(ratio)),
85 ("line", log2_int(nb_lines)),
86 ("tag", len(self.intr_bus.adr) - log2_int(nb_lines) - log2_int(ratio)),
87 ])
88 m.d.comb += intr_adr.eq(self.intr_bus.adr),
89
90 intr_adr_next = Record.like(intr_adr)
91
92 with m.Switch(self.intr_bus.bte):
93 with m.Case(wishbone.BurstTypeExt.LINEAR):
94 m.d.comb += intr_adr_next.eq(intr_adr + 1)
95 with m.Case(wishbone.BurstTypeExt.WRAP_4):
96 m.d.comb += intr_adr_next[:2].eq(intr_adr[:2] + 1)
97 m.d.comb += intr_adr_next[2:].eq(intr_adr[2:])
98 with m.Case(wishbone.BurstTypeExt.WRAP_8):
99 m.d.comb += intr_adr_next[:3].eq(intr_adr[:3] + 1)
100 m.d.comb += intr_adr_next[3:].eq(intr_adr[3:])
101 with m.Case(wishbone.BurstTypeExt.WRAP_16):
102 m.d.comb += intr_adr_next[:4].eq(intr_adr[:4] + 1)
103 m.d.comb += intr_adr_next[4:].eq(intr_adr[4:])
104
105 tag_rp_data = Record([
106 ("tag", intr_adr.tag.shape()),
107 ("dirty", 1),
108 ])
109 tag_wp_data = Record.like(tag_rp_data)
110
111 tag_mem = Memory(width=len(tag_rp_data), depth=nb_lines)
112 if self.dirty_init:
113 tag_mem.init = [-1 for _ in range(nb_lines)]
114
115 m.submodules.tag_rp = tag_rp = tag_mem.read_port(transparent=False)
116 m.submodules.tag_wp = tag_wp = tag_mem.write_port()
117 tag_rp.en.reset = 0
118
119 m.d.comb += [
120 tag_rp_data.eq(tag_rp.data),
121 tag_wp.data.eq(tag_wp_data),
122 ]
123
124 dat_mem = Memory(width=self.dram_port.data_width, depth=nb_lines)
125 m.submodules.dat_rp = dat_rp = dat_mem.read_port(transparent=False)
126 m.submodules.dat_wp = dat_wp = dat_mem.write_port(granularity=self.intr_bus.granularity)
127 dat_rp.en.reset = 0
128
129 intr_bus_r = Record.like(self.intr_bus)
130 intr_adr_r = Record.like(intr_adr)
131 m.d.comb += intr_adr_r.eq(intr_bus_r.adr)
132
133 with m.FSM() as fsm:
134 with m.State("CHECK"):
135 m.d.sync += [
136 intr_bus_r.cyc.eq(self.intr_bus.cyc),
137 intr_bus_r.stb.eq(self.intr_bus.stb),
138 intr_bus_r.adr.eq(self.intr_bus.adr),
139 ]
140 # Tag/Data memory read
141 with m.If(self.intr_bus.cyc & self.intr_bus.stb):
142 with m.If(self.intr_bus.ack & (self.intr_bus.cti == wishbone.CycleType.INCR_BURST)):
143 m.d.comb += [
144 tag_rp.addr.eq(intr_adr_next.line),
145 dat_rp.addr.eq(intr_adr_next.line),
146 ]
147 with m.Else():
148 m.d.comb += [
149 tag_rp.addr.eq(intr_adr.line),
150 dat_rp.addr.eq(intr_adr.line),
151 ]
152 with m.If(~intr_bus_r.cyc | ~intr_bus_r.stb | self.intr_bus.ack):
153 m.d.comb += [
154 tag_rp.en.eq(1),
155 dat_rp.en.eq(1),
156 ]
157 m.d.comb += [
158 self.intr_bus.dat_r.eq(
159 dat_rp.data.word_select(intr_adr.offset, len(self.intr_bus.dat_r))
160 ),
161 ]
162 # Tag/Data memory write
163 m.d.comb += [
164 tag_wp.addr .eq(intr_adr.line),
165 tag_wp_data.tag .eq(intr_adr.tag),
166 tag_wp_data.dirty.eq(1),
167 dat_wp.addr .eq(intr_adr.line),
168 dat_wp.data .eq(Repl(self.intr_bus.dat_w, ratio)),
169 ]
170 with m.If(self.intr_bus.cyc & self.intr_bus.stb):
171 with m.If(intr_adr.tag == tag_rp_data.tag):
172 m.d.comb += self.intr_bus.ack.eq(intr_bus_r.cyc & intr_bus_r.stb)
173 with m.If(self.intr_bus.we & self.intr_bus.ack):
174 m.d.comb += [
175 tag_wp.en.eq(1),
176 dat_wp.en.word_select(intr_adr.offset, len(self.intr_bus.sel)).eq(self.intr_bus.sel),
177 ]
178 with m.Elif(intr_bus_r.cyc & intr_bus_r.stb):
179 m.d.sync += [
180 intr_bus_r.cyc.eq(0),
181 intr_bus_r.stb.eq(0),
182 ]
183 with m.If(tag_rp_data.dirty):
184 m.next = "EVICT"
185 with m.Else():
186 m.next = "REFILL"
187
188 with m.State("EVICT"):
189 evict_done = Record([("cmd", 1), ("w", 1)])
190 with m.If(evict_done.all()):
191 m.d.sync += evict_done.eq(0)
192 m.next = "REFILL"
193 # Command
194 m.d.comb += [
195 self.dram_port.cmd.valid.eq(~evict_done.cmd),
196 self.dram_port.cmd.last .eq(0),
197 self.dram_port.cmd.addr .eq(Cat(intr_adr_r.line, tag_rp_data.tag)),
198 self.dram_port.cmd.we .eq(1),
199 ]
200 with m.If(self.dram_port.cmd.valid & self.dram_port.cmd.ready):
201 m.d.sync += evict_done.cmd.eq(1)
202 # Write
203 m.d.comb += [
204 self.dram_port.w.valid.eq(~evict_done.w),
205 self.dram_port.w.we .eq(Repl(Const(1), self.dram_port.data_width // 8)),
206 self.dram_port.w.data .eq(dat_rp.data),
207 ]
208 with m.If(self.dram_port.w.valid & self.dram_port.w.ready):
209 m.d.sync += evict_done.w.eq(1)
210
211 with m.State("REFILL"):
212 refill_done = Record([("cmd", 1), ("r", 1)])
213 with m.If(refill_done.all()):
214 m.d.sync += refill_done.eq(0)
215 m.next = "CHECK"
216 # Command
217 m.d.comb += [
218 self.dram_port.cmd.valid.eq(~refill_done.cmd),
219 self.dram_port.cmd.last .eq(1),
220 self.dram_port.cmd.addr .eq(Cat(intr_adr_r.line, intr_adr_r.tag)),
221 self.dram_port.cmd.we .eq(0),
222 ]
223 with m.If(self.dram_port.cmd.valid & self.dram_port.cmd.ready):
224 m.d.sync += refill_done.cmd.eq(1)
225 # Read
226 m.d.comb += [
227 self.dram_port.r.ready.eq(~refill_done.r),
228 tag_wp.addr .eq(intr_adr_r.line),
229 tag_wp.en .eq((self.dram_port.r.valid & self.dram_port.r.ready)),
230 tag_wp_data.tag .eq(intr_adr_r.tag),
231 tag_wp_data.dirty.eq(0),
232 dat_wp.addr .eq(intr_adr_r.line),
233 dat_wp.en .eq(Repl((self.dram_port.r.valid & self.dram_port.r.ready), len(dat_wp.en))),
234 dat_wp.data .eq(self.dram_port.r.data),
235 ]
236 with m.If(self.dram_port.r.valid & self.dram_port.r.ready):
237 m.d.sync += refill_done.r.eq(1)
238
239 if platform == "formal":
240 with m.If(Initial()):
241 m.d.comb += [
242 Assume(fsm.ongoing("CHECK")),
243 Assume(~intr_bus_r.cyc),
244 Assume(~evict_done.any()),
245 Assume(~refill_done.any()),
246 ]
247
248 return m
249
250
251 class SDRAMPeripheral(Peripheral, Elaboratable):
252 """SDRAM controller peripheral.
253
254 Parameters
255 ----------
256 core : :class:`litedram.Core`
257 LiteDRAM core.
258 cache_size : int
259 Cache size, in bytes.
260 cache_dirty_init : boot
261 Initialize cache as dirty. Defaults to `False`.
262 """
263 def __init__(self, *, core, cache_size, cache_dirty_init=False):
264 super().__init__()
265
266 if not isinstance(core, litedram.Core):
267 raise TypeError("LiteDRAM core must be an instance of lambdasoc.cores.litedram.Core, "
268 "not {!r}"
269 .format(core))
270 self.core = core
271
272 data_width = core.ctrl_bus.data_width
273 granularity = core.ctrl_bus.granularity
274 granularity_bits = log2_int(data_width // granularity)
275
276 # Data path : bridge -> cache -> LiteDRAM user port
277
278 self._data_bus = self.window(
279 addr_width = core.user_port.addr_width
280 + log2_int(core.user_port.data_width // 8)
281 - granularity_bits,
282 data_width = data_width,
283 granularity = granularity,
284 features = {"cti", "bte"},
285 )
286 data_map = MemoryMap(
287 addr_width = self._data_bus.addr_width + granularity_bits,
288 data_width = granularity,
289 alignment = 0,
290 )
291
292 self._cache = WritebackCache(
293 core.user_port,
294 size = cache_size,
295 data_width = data_width,
296 granularity = granularity,
297 dirty_init = cache_dirty_init,
298 )
299 data_map.add_window(self._cache.intr_bus.memory_map)
300
301 self._data_bus.memory_map = data_map
302
303 # Control path : bridge -> LiteDRAM control port
304
305 self._ctrl_bus = self.window(
306 addr_width = core._ctrl_bus.addr_width,
307 data_width = data_width,
308 granularity = granularity,
309 addr = core.size,
310 )
311 ctrl_map = MemoryMap(
312 addr_width = self._ctrl_bus.addr_width + granularity_bits,
313 data_width = granularity,
314 alignment = 0,
315 )
316
317 ctrl_map.add_window(core.ctrl_bus.memory_map)
318
319 self._ctrl_bus.memory_map = ctrl_map
320
321 self._bridge = self.bridge(data_width=data_width, granularity=granularity)
322 self.bus = self._bridge.bus
323
324 @property
325 def constant_map(self):
326 return ConstantMap(
327 SIZE = self.core.size,
328 CACHE_SIZE = self._cache.size,
329 )
330
331 def elaborate(self, platform):
332 m = Module()
333
334 m.submodules.bridge = self._bridge
335 m.submodules.cache = self._cache
336 m.submodules.core = self.core
337
338 m.d.comb += [
339 self._cache.intr_bus.adr .eq(self._data_bus.adr),
340 self._cache.intr_bus.cyc .eq(self._data_bus.cyc),
341 self._cache.intr_bus.stb .eq(self._data_bus.stb),
342 self._cache.intr_bus.sel .eq(self._data_bus.sel),
343 self._cache.intr_bus.we .eq(self._data_bus.we),
344 self._cache.intr_bus.dat_w.eq(self._data_bus.dat_w),
345 self._cache.intr_bus.cti .eq(self._data_bus.cti),
346 self._cache.intr_bus.bte .eq(self._data_bus.bte),
347 self._data_bus.ack .eq(self._cache.intr_bus.ack),
348 self._data_bus.dat_r.eq(self._cache.intr_bus.dat_r),
349
350 self.core.ctrl_bus.adr .eq(self._ctrl_bus.adr),
351 self.core.ctrl_bus.cyc .eq(self._ctrl_bus.cyc),
352 self.core.ctrl_bus.stb .eq(self._ctrl_bus.stb),
353 self.core.ctrl_bus.sel .eq(self._ctrl_bus.sel),
354 self.core.ctrl_bus.we .eq(self._ctrl_bus.we),
355 self.core.ctrl_bus.dat_w.eq(self._ctrl_bus.dat_w),
356 self._ctrl_bus.ack .eq(self.core.ctrl_bus.ack),
357 self._ctrl_bus.dat_r.eq(self.core.ctrl_bus.dat_r),
358 ]
359
360 return m