cores: add LiteDRAM core.
[lambdasoc.git] / lambdasoc / cores / litedram.py
1 from abc import ABCMeta, abstractmethod
2 import csv
3 import jinja2
4 import os
5 import re
6 import textwrap
7
8 from nmigen import *
9 from nmigen import tracer
10 from nmigen.build.run import BuildPlan
11 from nmigen.utils import log2_int
12
13 from nmigen_soc import wishbone
14 from nmigen_soc.memory import MemoryMap
15
16 from .. import __version__
17
18
19 __all__ = [
20 "Config", "ECP5Config", "Artix7Config",
21 "NativePort", "Core",
22 "Builder",
23 ]
24
25
26 class Config(metaclass=ABCMeta):
27 _doc_template = """
28 {description}
29
30 Parameters
31 ----------
32 memtype : str
33 DRAM type (e.g. `"DDR3"`).
34 module_name : str
35 DRAM module name.
36 module_bytes : int
37 Number of byte groups of the DRAM interface.
38 module_ranks : int
39 Number of ranks. A rank is a set of DRAM chips that are connected to the same CS pin.
40 input_clk_freq : int
41 Frequency of the input clock, which drives the internal PLL.
42 user_clk_freq : int
43 Frequency of the user clock, which is generated by the internal PLL.
44 input_domain : str
45 Input clock domain. Defaults to `"litedram_input"`.
46 user_domain : str
47 User clock domain. Defaults to `"litedram_user"`.
48 user_data_width : int
49 User port data width. Defaults to 128.
50 cmd_buffer_depth : int
51 Command buffer depth. Defaults to 16.
52 csr_data_width : int
53 CSR bus data width. Defaults to 32.
54 {parameters}
55 """
56
57 __doc__ = _doc_template.format(
58 description="""
59 LiteDRAM base configuration.
60 """.strip(),
61 parameters="",
62 )
63 def __init__(self, *,
64 memtype,
65 module_name,
66 module_bytes,
67 module_ranks,
68 input_clk_freq,
69 user_clk_freq,
70 input_domain = "litedram_input",
71 user_domain = "litedram_user",
72 user_data_width = 128,
73 cmd_buffer_depth = 16,
74 csr_data_width = 32):
75
76 if memtype == "DDR2":
77 rate = "1:2"
78 elif memtype in {"DDR3", "DDR4"}:
79 rate = "1:4"
80 else:
81 raise ValueError("Unsupported DRAM type, must be one of \"DDR2\", \"DDR3\" or "
82 "\"DDR4\", not {!r}"
83 .format(memtype))
84
85 if not isinstance(module_name, str):
86 raise ValueError("Module name must be a string, not {!r}"
87 .format(module_name))
88 if not isinstance(module_bytes, int) or module_bytes <= 0:
89 raise ValueError("Number of byte groups must be a positive integer, not {!r}"
90 .format(module_bytes))
91 if not isinstance(module_ranks, int) or module_ranks <= 0:
92 raise ValueError("Number of ranks must be a positive integer, not {!r}"
93 .format(module_ranks))
94 if not isinstance(input_clk_freq, int) or input_clk_freq <= 0:
95 raise ValueError("Input clock frequency must be a positive integer, not {!r}"
96 .format(input_clk_freq))
97 if not isinstance(user_clk_freq, int) or user_clk_freq <= 0:
98 raise ValueError("User clock frequency must be a positive integer, not {!r}"
99 .format(user_clk_freq))
100 if not isinstance(input_domain, str):
101 raise ValueError("Input domain name must be a string, not {!r}"
102 .format(input_domain))
103 if not isinstance(user_domain, str):
104 raise ValueError("User domain name must be a string, not {!r}"
105 .format(user_domain))
106 if user_data_width not in {8, 16, 32, 64, 128}:
107 raise ValueError("User port data width must be one of 8, 16, 32, 64 or 128, "
108 "not {!r}"
109 .format(user_data_width))
110 if not isinstance(cmd_buffer_depth, int) or cmd_buffer_depth <= 0:
111 raise ValueError("Command buffer depth must be a positive integer, not {!r}"
112 .format(cmd_buffer_depth))
113 if csr_data_width not in {8, 16, 32, 64}:
114 raise ValueError("CSR data width must be one of 8, 16, 32, or 64, not {!r}"
115 .format(csr_data_width))
116
117 self.memtype = memtype
118 self._rate = rate
119 self.module_name = module_name
120 self.module_bytes = module_bytes
121 self.module_ranks = module_ranks
122 self.input_clk_freq = input_clk_freq
123 self.user_clk_freq = user_clk_freq
124 self.input_domain = input_domain
125 self.user_domain = user_domain
126 self.user_data_width = user_data_width
127 self.cmd_buffer_depth = cmd_buffer_depth
128 self.csr_data_width = csr_data_width
129
130 @property
131 @abstractmethod
132 def phy_name(self):
133 """LiteDRAM PHY name.
134 """
135 raise NotImplementedError
136
137 def get_module(self):
138 """Get DRAM module description.
139
140 Return value
141 ------------
142 An instance of :class:`litedram.modules.SDRAMModule`, describing its geometry and timings.
143 """
144 import litedram.modules
145 module_class = getattr(litedram.modules, self.module_name)
146 module = module_class(
147 clk_freq = self.user_clk_freq,
148 rate = self._rate,
149 )
150 assert module.memtype == self.memtype
151 return module
152
153 def request_pins(self, platform, name, number):
154 """Request DRAM pins.
155
156 This helper requests the DRAM pins with `dir="-"` and `xdr=0`, because LiteDRAM already
157 provides its own I/O buffers.
158
159 Arguments
160 ---------
161 platform : :class:`nmigen.build.Platform`
162 Target platform.
163 name : str
164 DRAM resource name.
165 number : int
166 DRAM resource number.
167
168 Return value
169 ------------
170 A :class:`Record` providing raw access to DRAM pins.
171 """
172 res = platform.lookup(name, number)
173 return platform.request(
174 name, number,
175 dir={io.name: "-" for io in res.ios},
176 xdr={io.name: 0 for io in res.ios},
177 )
178
179
180 class ECP5Config(Config):
181 phy_name = "ECP5DDRPHY"
182
183 __doc__ = Config._doc_template.format(
184 description = """
185 LiteDRAM configuration for ECP5 FPGAs.
186 """.strip(),
187 parameters = r"""
188 init_clk_freq : int
189 Frequency of the PHY initialization clock, which is generated by the internal PLL.
190 """.strip(),
191 )
192 def __init__(self, *, init_clk_freq, **kwargs):
193 super().__init__(**kwargs)
194
195 if not isinstance(init_clk_freq, int) or init_clk_freq <= 0:
196 raise ValueError("Init clock frequency must be a positive integer, not {!r}"
197 .format(init_clk_freq))
198 self.init_clk_freq = init_clk_freq
199
200
201 class Artix7Config(Config):
202 phy_name = "A7DDRPHY"
203
204 __doc__ = Config._doc_template.format(
205 description = """
206 LiteDRAM configuration for Artix 7 FPGAs.
207 """.strip(),
208 parameters = r"""
209 speedgrade : str
210 FPGA speed grade (e.g. "-1").
211 cmd_latency : int
212 Command additional latency.
213 rtt_nom : int
214 Nominal termination impedance.
215 rtt_wr : int
216 Write termination impedance.
217 ron : int
218 Output driver impedance.
219 iodelay_clk_freq : int
220 IODELAY reference clock frequency.
221 """.strip(),
222 )
223 def __init__(self, *,
224 speedgrade,
225 cmd_latency,
226 rtt_nom,
227 rtt_wr,
228 ron,
229 iodelay_clk_freq,
230 **kwargs):
231 super().__init__(**kwargs)
232
233 speedgrades = ("-1", "-2", "-2L", "-2G", "-3")
234 if speedgrade not in speedgrades:
235 raise ValueError("Speed grade must be one of \'{}\', not {!r}"
236 .format("\', \'".join(speedgrades), speedgrade))
237 if not isinstance(cmd_latency, int) or cmd_latency < 0:
238 raise ValueError("Command latency must be a non-negative integer, not {!r}"
239 .format(cmd_latency))
240 if not isinstance(rtt_nom, int) or rtt_nom < 0:
241 raise ValueError("Nominal termination impedance must be a non-negative integer, "
242 "not {!r}"
243 .format(rtt_nom))
244 if not isinstance(rtt_wr, int) or rtt_wr < 0:
245 raise ValueError("Write termination impedance must be a non-negative integer, "
246 "not {!r}"
247 .format(rtt_wr))
248 if not isinstance(ron, int) or ron < 0:
249 raise ValueError("Output driver impedance must be a non-negative integer, "
250 "not {!r}"
251 .format(ron))
252 if not isinstance(iodelay_clk_freq, int) or iodelay_clk_freq <= 0:
253 raise ValueError("IODELAY clock frequency must be a positive integer, not {!r}"
254 .format(iodelay_clk_freq))
255
256 self.speedgrade = speedgrade
257 self.cmd_latency = cmd_latency
258 self.rtt_nom = rtt_nom
259 self.rtt_wr = rtt_wr
260 self.ron = ron
261 self.iodelay_clk_freq = iodelay_clk_freq
262
263
264 class NativePort(Record):
265 """LiteDRAM native port interface.
266
267 In the "Attributes" section, port directions are given from the point of view of user logic.
268
269 Parameters
270 ----------
271 addr_width : int
272 Port address width.
273 data_width : int
274 Port data width.
275
276 Attributes
277 ----------
278 granularity : int
279 Port granularity, i.e. its smallest transferable unit of data. LiteDRAM native ports have a
280 granularity of 8 bits.
281 cmd.valid : Signal(), in
282 Command valid.
283 cmd.ready : Signal(), out
284 Command ready. Commands are accepted when `cmd.valid` and `cmd.ready` are both asserted.
285 cmd.last : Signal(), in
286 Command last. Indicates the last command of a burst.
287 cmd.we : Signal(), in
288 Command write enable. Indicates that this command is a write.
289 cmd.addr : Signal(addr_width), in
290 Command address.
291 w.valid : Signal(), in
292 Write valid.
293 w.ready : Signal(), out
294 Write ready. Write data is accepted when `w.valid` and `w.ready` are both asserted.
295 w.data : Signal(data_width), in
296 Write data.
297 w.we : Signal(data_width // granularity), bitmask, in
298 Write mask. Indicates which bytes in `w.data` are valid.
299 r.valid : Signal(), out
300 Read valid.
301 r.ready : Signal(), in
302 Read ready. Read data is consumed when `r.valid` and `r.ready` are both asserted.
303 r.data : Signal(data_width), out
304 Read data.
305 """
306 def __init__(self, *, addr_width, data_width, name=None, src_loc_at=0):
307 if not isinstance(addr_width, int) or addr_width <= 0:
308 raise ValueError("Address width must be a positive integer, not {!r}"
309 .format(addr_width))
310 if not isinstance(data_width, int) or data_width <= 0 or data_width & data_width - 1:
311 raise ValueError("Data width must be a positive power of two integer, not {!r}"
312 .format(data_width))
313
314 self.addr_width = addr_width
315 self.data_width = data_width
316 self.granularity = 8
317 self._map = None
318
319 super().__init__([
320 ("cmd", [
321 ("valid", 1),
322 ("ready", 1),
323 ("last", 1),
324 ("we", 1),
325 ("addr", addr_width),
326 ]),
327 ("w", [
328 ("valid", 1),
329 ("ready", 1),
330 ("data", data_width),
331 ("we", data_width // self.granularity),
332 ]),
333 ("r", [
334 ("valid", 1),
335 ("ready", 1),
336 ("data", data_width),
337 ]),
338 ], name=name, src_loc_at=1 + src_loc_at)
339
340 @property
341 def memory_map(self):
342 """Map of the native port.
343
344 Return value
345 ------------
346 An instance of :class:`nmigen_soc.memory.MemoryMap`.
347
348 Exceptions
349 ----------
350 Raises an :exn:`AttributeError` if the port does not have a memory map.
351 """
352 if self._map is None:
353 raise AttributeError("Native port {!r} does not have a memory map"
354 .format(self))
355 return self._map
356
357 @memory_map.setter
358 def memory_map(self, memory_map):
359 if not isinstance(memory_map, MemoryMap):
360 raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
361 .format(memory_map))
362 if memory_map.data_width != 8:
363 raise ValueError("Memory map has data width {}, which is not the same as native port "
364 "granularity {}"
365 .format(memory_map.data_width, 8))
366 granularity_bits = log2_int(self.data_width // 8)
367 if memory_map.addr_width != max(1, self.addr_width + granularity_bits):
368 raise ValueError("Memory map has address width {}, which is not the same as native "
369 "port address width {} ({} address bits + {} granularity bits)"
370 .format(memory_map.addr_width, self.addr_width + granularity_bits,
371 self.addr_width, granularity_bits))
372 memory_map.freeze()
373 self._map = memory_map
374
375
376 class Core(Elaboratable):
377 _namespace = set()
378
379 @classmethod
380 def clear_namespace(cls):
381 """Clear private namespace.
382
383 Every time an instance of :class:`litedram.Core` is created, its name is stored in a
384 private namespace. This allows us to detect name collisions, which are problematic for at
385 least two reasons:
386 * by default, a sub-directory named after the instance is created at build-time in
387 order to isolate it from other LiteDRAM builds. A collision would overwrite previous
388 build products.
389 * the instance name becomes the name of its top-level Verilog module. Importing two
390 modules with the same name will cause a toolchain error.
391
392 :meth:`litedram.Core.clear_namespace` resets this namespace. It is intended for cases where
393 stateless class instantiations are desirable, such as unit testing.
394 """
395 cls._namespace.clear()
396
397 """An nMigen wrapper for a standalone LiteDRAM core.
398
399 Parameters
400 ----------
401 config : :class:`Config`
402 LiteDRAM configuration.
403 pins : :class:`nmigen.lib.io.Pin`
404 Optional. DRAM pins. See :class:`nmigen_boards.resources.DDR3Resource` for layout.
405 name : str
406 Optional. Name of the LiteDRAM core. If ``None`` (default) the name is inferred from the
407 name of the variable this instance is assigned to.
408 name_force: bool
409 Force name. If ``True``, no exception will be raised in case of a name collision with a
410 previous LiteDRAM instance. Defaults to ``False``.
411
412 Attributes
413 ----------
414 name : str
415 Name of the LiteDRAM core.
416 size : int
417 DRAM size, in bytes.
418 user_port : :class:`NativePort`
419 User port. Provides access to the DRAM storage.
420
421 Exceptions
422 ----------
423 Raises a :exn:`ValueError` if ``name`` collides with the name given to a previous LiteDRAM
424 instance and ``name_force`` is ``False``.
425 """
426 def __init__(self, config, *, pins=None, name=None, name_force=False, src_loc_at=0):
427 if not isinstance(config, Config):
428 raise TypeError("Config must be an instance of litedram.Config, "
429 "not {!r}"
430 .format(config))
431 self.config = config
432
433 if name is not None and not isinstance(name, str):
434 raise TypeError("Name must be a string, not {!r}".format(name))
435 name = name or tracer.get_var_name(depth=2 + src_loc_at)
436
437 if not name_force and name in Core._namespace:
438 raise ValueError(
439 "Name '{}' has already been used for a previous litedram.Core instance. Building "
440 "this instance may overwrite previous build products. Passing `name_force=True` "
441 "will disable this check.".format(name)
442 )
443 Core._namespace.add(name)
444 self.name = name
445
446 module = config.get_module()
447 size = config.module_bytes \
448 * 2**( module.geom_settings.bankbits
449 + module.geom_settings.rowbits
450 + module.geom_settings.colbits)
451
452 self.size = size
453
454 user_addr_width = module.geom_settings.rowbits \
455 + module.geom_settings.colbits \
456 + log2_int(module.nbanks) \
457 + max(log2_int(config.module_ranks), 1)
458
459 self.user_port = NativePort(
460 addr_width = user_addr_width - log2_int(config.user_data_width // 8),
461 data_width = config.user_data_width,
462 )
463 user_map = MemoryMap(addr_width=user_addr_width, data_width=8)
464 user_map.add_resource("user_port_0", size=size)
465 self.user_port.memory_map = user_map
466
467 self._ctrl_bus = None
468 self._pins = pins
469
470 @property
471 def ctrl_bus(self):
472 """Control bus interface.
473
474 *Please note that accesses to the CSRs exposed by this interface are not atomic.*
475
476 The memory map of this interface is populated by reading the ``{{self.name}}_csr.csv``
477 file from the build products.
478
479 Return value
480 ------------
481 An instance of :class:`nmigen_soc.wishbone.Interface`.
482
483 Exceptions
484 ----------
485 Raises an :exn:`AttributeError` if this getter is called before LiteDRAM is built (i.e.
486 before :meth:`Core.build` is called with `do_build=True`).
487 """
488 if self._ctrl_bus is None:
489 raise AttributeError("Core.build(do_build=True) must be called before accessing "
490 "Core.ctrl_bus")
491 return self._ctrl_bus
492
493 def build(self, do_build=True, build_dir="build/litedram", sim=False):
494 """Build the LiteDRAM core.
495
496 Arguments
497 ---------
498 do_build : bool
499 Build the LiteDRAM core. Defaults to `True`.
500 build_dir : str
501 Build directory.
502 sim : bool
503 Do the build in simulation mode (i.e. with a PHY model). Defaults to `False`.
504
505 Return value
506 ------------
507 An instance of :class:`nmigen.build.run.LocalBuildProducts` if ``do_build`` is ``True``.
508 Otherwise, an instance of :class:``nmigen.build.run.BuildPlan``.
509 """
510 plan = Builder().prepare(self, build_dir, sim)
511 if not do_build:
512 return plan
513
514 products = plan.execute_local(build_dir)
515
516 # LiteDRAM's Wishbone to CSR bridge uses an 8-bit granularity.
517 ctrl_map = MemoryMap(addr_width=1, data_width=8)
518
519 with products.extract(f"{self.name}_csr.csv") as csr_csv_filename:
520 with open(csr_csv_filename, "r") as csr_csv:
521 for row in csv.reader(csr_csv, delimiter=","):
522 if row[0][0] == "#": continue
523 res_type, res_name, addr, size, attrs = row
524 if res_type == "csr_register":
525 ctrl_map.add_resource(
526 res_name,
527 addr = int(addr, 16),
528 size = int(size, 10) * self.config.csr_data_width
529 // ctrl_map.data_width,
530 extend = True,
531 )
532
533 self._ctrl_bus = wishbone.Interface(
534 addr_width = ctrl_map.addr_width
535 - log2_int(self.config.csr_data_width // ctrl_map.data_width),
536 data_width = self.config.csr_data_width,
537 granularity = ctrl_map.data_width,
538 )
539 self._ctrl_bus.memory_map = ctrl_map
540
541 return products
542
543 def elaborate(self, platform):
544 core_kwargs = {
545 "i_clk" : ClockSignal(self.config.input_domain),
546 "i_rst" : ResetSignal(self.config.input_domain),
547 "o_user_clk" : ClockSignal(self.config.user_domain),
548 "o_user_rst" : ResetSignal(self.config.user_domain),
549
550 "i_wb_ctrl_adr" : self.ctrl_bus.adr,
551 "i_wb_ctrl_dat_w" : self.ctrl_bus.dat_w,
552 "o_wb_ctrl_dat_r" : self.ctrl_bus.dat_r,
553 "i_wb_ctrl_sel" : self.ctrl_bus.sel,
554 "i_wb_ctrl_cyc" : self.ctrl_bus.cyc,
555 "i_wb_ctrl_stb" : self.ctrl_bus.stb,
556 "o_wb_ctrl_ack" : self.ctrl_bus.ack,
557 "i_wb_ctrl_we" : self.ctrl_bus.we,
558
559 "i_user_port_0_cmd_valid" : self.user_port.cmd.valid,
560 "o_user_port_0_cmd_ready" : self.user_port.cmd.ready,
561 "i_user_port_0_cmd_we" : self.user_port.cmd.we,
562 "i_user_port_0_cmd_addr" : self.user_port.cmd.addr,
563 "i_user_port_0_wdata_valid" : self.user_port.w.valid,
564 "o_user_port_0_wdata_ready" : self.user_port.w.ready,
565 "i_user_port_0_wdata_we" : self.user_port.w.we,
566 "i_user_port_0_wdata_data" : self.user_port.w.data,
567 "o_user_port_0_rdata_valid" : self.user_port.r.valid,
568 "i_user_port_0_rdata_ready" : self.user_port.r.ready,
569 "o_user_port_0_rdata_data" : self.user_port.r.data,
570 }
571
572 if self._pins is not None:
573 core_kwargs.update({
574 "o_ddram_a" : self._pins.a,
575 "o_ddram_ba" : self._pins.ba,
576 "o_ddram_ras_n" : self._pins.ras,
577 "o_ddram_cas_n" : self._pins.cas,
578 "o_ddram_we_n" : self._pins.we,
579 "o_ddram_dm" : self._pins.dm,
580 "o_ddram_clk_p" : self._pins.clk.p,
581 "o_ddram_cke" : self._pins.clk_en,
582 "o_ddram_odt" : self._pins.odt,
583 })
584
585 if hasattr(self._pins, "cs"):
586 core_kwargs.update({
587 "o_ddram_cs_n" : self._pins.cs,
588 })
589
590 if hasattr(self._pins, "rst"):
591 core_kwargs.update({
592 "o_ddram_reset_n" : self._pins.rst,
593 })
594
595 if isinstance(self.config, ECP5Config):
596 core_kwargs.update({
597 "i_ddram_dq" : self._pins.dq,
598 "i_ddram_dqs_p" : self._pins.dqs.p,
599 })
600 elif isinstance(self.config, Artix7Config):
601 core_kwargs.update({
602 "io_ddram_dq" : self._pins.dq,
603 "io_ddram_dqs_p" : self._pins.dqs.p,
604 "io_ddram_dqs_n" : self._pins.dqs.n,
605 "o_ddram_clk_n" : self._pins.clk.n,
606 })
607 else:
608 assert False
609
610 return Instance(f"{self.name}", **core_kwargs)
611
612
613 class Builder:
614 """
615 LiteDRAM builder
616 ----------------
617
618 Build products (any):
619 * ``{{top.name}}_csr.csv`` : CSR listing.
620 * ``{{top.name}}/build_{{top.name}}.sh``: LiteDRAM build script.
621 * ``{{top.name}}/{{top.name}}.v`` : LiteDRAM core.
622 * ``{{top.name}}/software/include/generated/csr.h`` : CSR accessors.
623 * ``{{top.name}}/software/include/generated/git.h`` : Git version.
624 * ``{{top.name}}/software/include/generated/mem.h`` : Memory regions.
625 * ``{{top.name}}/software/include/generated/sdram_phy.h`` : SDRAM initialization sequence.
626 * ``{{top.name}}/software/include/generated/soc.h`` : SoC constants.
627
628 Build products (ECP5):
629 * ``{{top.name}}/{{top.name}}.lpf`` : Constraints file.
630 * ``{{top.name}}/{{top.name}}.ys`` : Yosys script.
631
632 Build products (Artix 7):
633 * ``{{top.name}}/{{top.name}}.xdc`` : Constraints file
634 * ``{{top.name}}/{{top.name}}.tcl`` : Vivado script.
635 """
636
637 file_templates = {
638 "build_{{top.name}}.sh": r"""
639 # {{autogenerated}}
640 set -e
641 {{emit_commands()}}
642 """,
643 "{{top.name}}_config.yml": r"""
644 # {{autogenerated}}
645 {
646 # General ------------------------------------------------------------------
647 "cpu": "None",
648 {% if top.config.phy_name == "A7DDRPHY" %}
649 "speedgrade": {{top.config.speedgrade}},
650 {% endif %}
651 "memtype": "{{top.config.memtype}}",
652
653 # PHY ----------------------------------------------------------------------
654 {% if top.config.phy_name == "A7DDRPHY" %}
655 "cmd_latency": {{top.config.cmd_latency}},
656 {% endif %}
657 "sdram_module": "{{top.config.module_name}}",
658 "sdram_module_nb": {{top.config.module_bytes}},
659 "sdram_rank_nb": {{top.config.module_ranks}},
660 "sdram_phy": "{{top.config.phy_name}}",
661
662 # Electrical ---------------------------------------------------------------
663 {% if top.config.phy_name == "A7DDRPHY" %}
664 "rtt_nom": "{{top.config.rtt_nom}}ohm",
665 "rtt_wr": "{{top.config.rtt_wr}}ohm",
666 "ron": "{{top.config.ron}}ohm",
667 {% endif %}
668
669 # Frequency ----------------------------------------------------------------
670 "input_clk_freq": {{top.config.input_clk_freq}},
671 "sys_clk_freq": {{top.config.user_clk_freq}},
672 {% if top.config.phy_name == "ECP5DDRPHY" %}
673 "init_clk_freq": {{top.config.init_clk_freq}},
674 {% elif top.config.phy_name == "A7DDRPHY" %}
675 "iodelay_clk_freq": {{top.config.iodelay_clk_freq}},
676 {% endif %}
677
678 # Core ---------------------------------------------------------------------
679 "cmd_buffer_depth": {{top.config.cmd_buffer_depth}},
680 "csr_data_width": {{top.config.csr_data_width}},
681
682 # User Ports ---------------------------------------------------------------
683 "user_ports": {
684 "0": {
685 "type": "native",
686 "data_width": {{top.config.user_data_width}},
687 },
688 },
689 }
690 """,
691 }
692 command_templates = [
693 r"""
694 python -m litedram.gen
695 --name {{top.name}}
696 --output-dir {{top.name}}
697 --gateware-dir {{top.name}}
698 --csr-csv {{top.name}}_csr.csv
699 {% if sim %}
700 --sim
701 {% endif %}
702 {{top.name}}_config.yml
703 """,
704 ]
705
706 def prepare(self, top, build_dir, sim):
707 if not isinstance(top, Core):
708 raise TypeError("Top module must be an instance of litedram.Core, not {!r}"
709 .format(top))
710
711 autogenerated = f"Automatically generated by LambdaSoC {__version__}. Do not edit."
712
713 def emit_commands():
714 commands = []
715 for index, command_tpl in enumerate(self.command_templates):
716 command = render(command_tpl, origin="<command#{}>".format(index + 1))
717 command = re.sub(r"\s+", " ", command)
718 commands.append(command)
719 return "\n".join(commands)
720
721 def render(source, origin):
722 try:
723 source = textwrap.dedent(source).strip()
724 compiled = jinja2.Template(source, trim_blocks=True, lstrip_blocks=True)
725 except jinja2.TemplateSyntaxError as e:
726 e.args = ("{} (at {}:{})".format(e.message, origin, e.lineno),)
727 raise
728 return compiled.render({
729 "autogenerated": autogenerated,
730 "build_dir": os.path.abspath(build_dir),
731 "emit_commands": emit_commands,
732 "sim": sim,
733 "top": top,
734 })
735
736 plan = BuildPlan(script=f"build_{top.name}")
737 for filename_tpl, content_tpl in self.file_templates.items():
738 plan.add_file(render(filename_tpl, origin=filename_tpl),
739 render(content_tpl, origin=content_tpl))
740 return plan