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