1 from abc
import ABCMeta
, abstractmethod
9 from nmigen
import tracer
10 from nmigen
.build
.plat
import Platform
11 from nmigen
.build
.run
import BuildPlan
, BuildProducts
12 from nmigen
.utils
import log2_int
14 from nmigen_soc
import wishbone
15 from nmigen_soc
.memory
import MemoryMap
17 from .. import __version__
21 "Config", "ECP5Config", "Artix7Config",
27 class Config(metaclass
=ABCMeta
):
34 DRAM type (e.g. `"DDR3"`).
38 Number of byte groups of the DRAM interface.
40 Number of ranks. A rank is a set of DRAM chips that are connected to the same CS pin.
42 Frequency of the input clock, which drives the internal PLL.
44 Frequency of the user clock, which is generated by the internal PLL.
46 Input clock domain. Defaults to `"litedram_input"`.
48 User clock domain. Defaults to `"litedram_user"`.
50 User port data width. Defaults to 128.
51 cmd_buffer_depth : int
52 Command buffer depth. Defaults to 16.
54 CSR bus data width. Defaults to 32.
58 __doc__
= _doc_template
.format(
60 LiteDRAM base configuration.
71 input_domain
= "litedram_input",
72 user_domain
= "litedram_user",
73 user_data_width
= 128,
74 cmd_buffer_depth
= 16,
79 elif memtype
in {"DDR", "LPDDR", "DDR2"}:
81 elif memtype
in {"DDR3", "DDR4"}:
84 raise ValueError("Unsupported DRAM type, must be one of \"SDR\", \"DDR\", \"LPDDR\", "
85 "\"DDR2\", \"DDR3\" or \"DDR4\", not {!r}"
88 if not isinstance(module_name
, str):
89 raise ValueError("Module name must be a string, not {!r}"
91 if not isinstance(module_bytes
, int) or module_bytes
<= 0:
92 raise ValueError("Number of byte groups must be a positive integer, not {!r}"
93 .format(module_bytes
))
94 if not isinstance(module_ranks
, int) or module_ranks
<= 0:
95 raise ValueError("Number of ranks must be a positive integer, not {!r}"
96 .format(module_ranks
))
97 if not isinstance(input_clk_freq
, int) or input_clk_freq
<= 0:
98 raise ValueError("Input clock frequency must be a positive integer, not {!r}"
99 .format(input_clk_freq
))
100 if not isinstance(user_clk_freq
, int) or user_clk_freq
<= 0:
101 raise ValueError("User clock frequency must be a positive integer, not {!r}"
102 .format(user_clk_freq
))
103 if not isinstance(input_domain
, str):
104 raise ValueError("Input domain name must be a string, not {!r}"
105 .format(input_domain
))
106 if not isinstance(user_domain
, str):
107 raise ValueError("User domain name must be a string, not {!r}"
108 .format(user_domain
))
109 if user_data_width
not in {8, 16, 32, 64, 128}:
110 raise ValueError("User port data width must be one of 8, 16, 32, 64 or 128, "
112 .format(user_data_width
))
113 if not isinstance(cmd_buffer_depth
, int) or cmd_buffer_depth
<= 0:
114 raise ValueError("Command buffer depth must be a positive integer, not {!r}"
115 .format(cmd_buffer_depth
))
116 if csr_data_width
not in {8, 16, 32, 64}:
117 raise ValueError("CSR data width must be one of 8, 16, 32, or 64, not {!r}"
118 .format(csr_data_width
))
120 self
.memtype
= memtype
122 self
.module_name
= module_name
123 self
.module_bytes
= module_bytes
124 self
.module_ranks
= module_ranks
125 self
.input_clk_freq
= input_clk_freq
126 self
.user_clk_freq
= user_clk_freq
127 self
.input_domain
= input_domain
128 self
.user_domain
= user_domain
129 self
.user_data_width
= user_data_width
130 self
.cmd_buffer_depth
= cmd_buffer_depth
131 self
.csr_data_width
= csr_data_width
136 """LiteDRAM PHY name.
138 raise NotImplementedError
140 def get_module(self
):
141 """Get DRAM module description.
145 An instance of :class:`litedram.modules.SDRAMModule`, describing its geometry and timings.
147 import litedram
.modules
148 module_class
= getattr(litedram
.modules
, self
.module_name
)
149 module
= module_class(
150 clk_freq
= self
.user_clk_freq
,
153 assert module
.memtype
== self
.memtype
157 class ECP5Config(Config
):
158 phy_name
= "ECP5DDRPHY"
160 __doc__
= Config
._doc
_template
.format(
162 LiteDRAM configuration for ECP5 FPGAs.
166 Frequency of the PHY initialization clock, which is generated by the internal PLL.
169 def __init__(self
, *, init_clk_freq
, **kwargs
):
170 super().__init
__(**kwargs
)
172 if not isinstance(init_clk_freq
, int) or init_clk_freq
<= 0:
173 raise ValueError("Init clock frequency must be a positive integer, not {!r}"
174 .format(init_clk_freq
))
175 self
.init_clk_freq
= init_clk_freq
178 class Artix7Config(Config
):
179 phy_name
= "A7DDRPHY"
181 __doc__
= Config
._doc
_template
.format(
183 LiteDRAM configuration for Artix 7 FPGAs.
187 FPGA speed grade (e.g. "-1").
189 Command additional latency.
191 Nominal termination impedance.
193 Write termination impedance.
195 Output driver impedance.
196 iodelay_clk_freq : int
197 IODELAY reference clock frequency.
200 def __init__(self
, *,
208 super().__init
__(**kwargs
)
210 speedgrades
= ("-1", "-2", "-2L", "-2G", "-3")
211 if speedgrade
not in speedgrades
:
212 raise ValueError("Speed grade must be one of \'{}\', not {!r}"
213 .format("\', \'".join(speedgrades
), speedgrade
))
214 if not isinstance(cmd_latency
, int) or cmd_latency
< 0:
215 raise ValueError("Command latency must be a non-negative integer, not {!r}"
216 .format(cmd_latency
))
217 if not isinstance(rtt_nom
, int) or rtt_nom
< 0:
218 raise ValueError("Nominal termination impedance must be a non-negative integer, "
221 if not isinstance(rtt_wr
, int) or rtt_wr
< 0:
222 raise ValueError("Write termination impedance must be a non-negative integer, "
225 if not isinstance(ron
, int) or ron
< 0:
226 raise ValueError("Output driver impedance must be a non-negative integer, "
229 if not isinstance(iodelay_clk_freq
, int) or iodelay_clk_freq
<= 0:
230 raise ValueError("IODELAY clock frequency must be a positive integer, not {!r}"
231 .format(iodelay_clk_freq
))
233 self
.speedgrade
= speedgrade
234 self
.cmd_latency
= cmd_latency
235 self
.rtt_nom
= rtt_nom
238 self
.iodelay_clk_freq
= iodelay_clk_freq
241 class NativePort(Record
):
242 """LiteDRAM native port interface.
244 In the "Attributes" section, port directions are given from the point of view of user logic.
256 Port granularity, i.e. its smallest transferable unit of data. LiteDRAM native ports have a
257 granularity of 8 bits.
258 cmd.valid : Signal(), in
260 cmd.ready : Signal(), out
261 Command ready. Commands are accepted when `cmd.valid` and `cmd.ready` are both asserted.
262 cmd.last : Signal(), in
263 Command last. Indicates the last command of a burst.
264 cmd.we : Signal(), in
265 Command write enable. Indicates that this command is a write.
266 cmd.addr : Signal(addr_width), in
268 w.valid : Signal(), in
270 w.ready : Signal(), out
271 Write ready. Write data is accepted when `w.valid` and `w.ready` are both asserted.
272 w.data : Signal(data_width), in
274 w.we : Signal(data_width // granularity), bitmask, in
275 Write mask. Indicates which bytes in `w.data` are valid.
276 r.valid : Signal(), out
278 r.ready : Signal(), in
279 Read ready. Read data is consumed when `r.valid` and `r.ready` are both asserted.
280 r.data : Signal(data_width), out
283 def __init__(self
, *, addr_width
, data_width
, name
=None, src_loc_at
=0):
284 if not isinstance(addr_width
, int) or addr_width
<= 0:
285 raise ValueError("Address width must be a positive integer, not {!r}"
287 if not isinstance(data_width
, int) or data_width
<= 0 or data_width
& data_width
- 1:
288 raise ValueError("Data width must be a positive power of two integer, not {!r}"
291 self
.addr_width
= addr_width
292 self
.data_width
= data_width
302 ("addr", addr_width
),
307 ("data", data_width
),
308 ("we", data_width
// self
.granularity
),
313 ("data", data_width
),
315 ], name
=name
, src_loc_at
=1 + src_loc_at
)
318 def memory_map(self
):
319 """Map of the native port.
323 An instance of :class:`nmigen_soc.memory.MemoryMap`.
327 Raises an :exn:`AttributeError` if the port does not have a memory map.
329 if self
._map
is None:
330 raise AttributeError("Native port {!r} does not have a memory map"
335 def memory_map(self
, memory_map
):
336 if not isinstance(memory_map
, MemoryMap
):
337 raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
339 if memory_map
.data_width
!= 8:
340 raise ValueError("Memory map has data width {}, which is not the same as native port "
342 .format(memory_map
.data_width
, 8))
343 granularity_bits
= log2_int(self
.data_width
// 8)
344 if memory_map
.addr_width
!= max(1, self
.addr_width
+ granularity_bits
):
345 raise ValueError("Memory map has address width {}, which is not the same as native "
346 "port address width {} ({} address bits + {} granularity bits)"
347 .format(memory_map
.addr_width
, self
.addr_width
+ granularity_bits
,
348 self
.addr_width
, granularity_bits
))
350 self
._map
= memory_map
353 class Core(Elaboratable
):
354 """An nMigen wrapper for a standalone LiteDRAM core.
358 config : :class:`Config`
359 LiteDRAM configuration.
360 pins : :class:`nmigen.lib.io.Pin`
361 Optional. DRAM pins. See :class:`nmigen_boards.resources.DDR3Resource` for layout.
363 Optional. Name of the LiteDRAM core. If ``None`` (default) the name is inferred from the
364 name of the variable this instance is assigned to.
369 Name of the LiteDRAM core.
372 user_port : :class:`NativePort`
373 User port. Provides access to the DRAM storage.
375 def __init__(self
, config
, *, pins
=None, name
=None, src_loc_at
=0):
376 if not isinstance(config
, Config
):
377 raise TypeError("Config must be an instance of litedram.Config, "
382 if name
is not None and not isinstance(name
, str):
383 raise TypeError("Name must be a string, not {!r}".format(name
))
384 self
.name
= name
or tracer
.get_var_name(depth
=2 + src_loc_at
)
386 module
= config
.get_module()
387 size
= config
.module_bytes \
388 * 2**( module
.geom_settings
.bankbits
389 + module
.geom_settings
.rowbits
390 + module
.geom_settings
.colbits
)
394 user_addr_width
= module
.geom_settings
.rowbits \
395 + module
.geom_settings
.colbits \
396 + log2_int(module
.nbanks
) \
397 + max(log2_int(config
.module_ranks
), 1)
399 self
.user_port
= NativePort(
400 addr_width
= user_addr_width
- log2_int(config
.user_data_width
// 8),
401 data_width
= config
.user_data_width
,
403 user_map
= MemoryMap(addr_width
=user_addr_width
, data_width
=8)
404 user_map
.add_resource(object(), name
="user_port_0", size
=size
)
405 self
.user_port
.memory_map
= user_map
407 self
._ctrl
_bus
= None
412 """Control bus interface.
414 *Please note that accesses to the CSRs exposed by this interface are not atomic.*
416 The memory map of this interface is populated by reading the ``{{self.name}}_csr.csv``
417 file from the build products.
421 An instance of :class:`nmigen_soc.wishbone.Interface`.
425 Raises an :exn:`AttributeError` if this getter is called before LiteDRAM is built (i.e.
426 before :meth:`Core.build` is called with `do_build=True`).
428 if self
._ctrl
_bus
is None:
429 raise AttributeError("Control bus memory map has not been populated. "
430 "Core.build(do_build=True) must be called before accessing "
432 return self
._ctrl
_bus
434 def _populate_ctrl_map(self
, build_products
):
435 if not isinstance(build_products
, BuildProducts
):
436 raise TypeError("Build products must be an instance of BuildProducts, not {!r}"
437 .format(build_products
))
439 # LiteDRAM's Wishbone to CSR bridge has a granularity of 8 bits.
440 ctrl_map
= MemoryMap(addr_width
=1, data_width
=8)
442 csr_csv
= build_products
.get(f
"{self.name}_csr.csv", mode
="t")
443 for row
in csv
.reader(csr_csv
.split("\n"), delimiter
=","):
444 if not row
or row
[0][0] == "#": continue
445 res_type
, res_name
, addr
, size
, attrs
= row
446 if res_type
== "csr_register":
447 ctrl_map
.add_resource(
450 addr
= int(addr
, 16),
451 size
= int(size
, 10) * self
.config
.csr_data_width
// ctrl_map
.data_width
,
455 self
._ctrl
_bus
= wishbone
.Interface(
456 addr_width
= ctrl_map
.addr_width
457 - log2_int(self
.config
.csr_data_width
// ctrl_map
.data_width
),
458 data_width
= self
.config
.csr_data_width
,
459 granularity
= ctrl_map
.data_width
,
461 self
._ctrl
_bus
.memory_map
= ctrl_map
463 def build(self
, builder
, platform
, build_dir
, *, do_build
=True, sim
=False, name_force
=False):
464 """Build the LiteDRAM core.
468 builder: :class:`litedram.Builder`
470 platform: :class:`nmigen.build.Platform`
473 Root build directory.
475 Execute the build locally. Defaults to ``True``.
477 Do the build in simulation mode (i.e. by replacing the PHY with a model). Defaults to
480 Ignore builder name conflicts. Defaults to ``False``.
484 An instance of :class:`nmigen.build.run.LocalBuildProducts` if ``do_build`` is ``True``.
485 Otherwise, an instance of :class:``nmigen.build.run.BuildPlan``.
487 if not isinstance(builder
, Builder
):
488 raise TypeError("Builder must be an instance of litedram.Builder, not {!r}"
490 if not isinstance(platform
, Platform
):
491 raise TypeError("Platform must be an instance of nmigen.build.Platform, not {!r}"
494 plan
= builder
.prepare(self
, platform
, sim
=sim
, name_force
=name_force
)
498 products
= plan
.execute_local(f
"{build_dir}/{__name__}")
499 self
._populate
_ctrl
_map
(products
)
501 core_src
= f
"{self.name}/{self.name}.v"
502 platform
.add_file(core_src
, products
.get(core_src
, mode
="t"))
506 def elaborate(self
, platform
):
508 "i_clk" : ClockSignal(self
.config
.input_domain
),
509 "i_rst" : ResetSignal(self
.config
.input_domain
),
510 "o_user_clk" : ClockSignal(self
.config
.user_domain
),
511 "o_user_rst" : ResetSignal(self
.config
.user_domain
),
513 "i_wb_ctrl_adr" : self
.ctrl_bus
.adr
,
514 "i_wb_ctrl_dat_w" : self
.ctrl_bus
.dat_w
,
515 "o_wb_ctrl_dat_r" : self
.ctrl_bus
.dat_r
,
516 "i_wb_ctrl_sel" : self
.ctrl_bus
.sel
,
517 "i_wb_ctrl_cyc" : self
.ctrl_bus
.cyc
,
518 "i_wb_ctrl_stb" : self
.ctrl_bus
.stb
,
519 "o_wb_ctrl_ack" : self
.ctrl_bus
.ack
,
520 "i_wb_ctrl_we" : self
.ctrl_bus
.we
,
522 "i_user_port_0_cmd_valid" : self
.user_port
.cmd
.valid
,
523 "o_user_port_0_cmd_ready" : self
.user_port
.cmd
.ready
,
524 "i_user_port_0_cmd_we" : self
.user_port
.cmd
.we
,
525 "i_user_port_0_cmd_addr" : self
.user_port
.cmd
.addr
,
526 "i_user_port_0_wdata_valid" : self
.user_port
.w
.valid
,
527 "o_user_port_0_wdata_ready" : self
.user_port
.w
.ready
,
528 "i_user_port_0_wdata_we" : self
.user_port
.w
.we
,
529 "i_user_port_0_wdata_data" : self
.user_port
.w
.data
,
530 "o_user_port_0_rdata_valid" : self
.user_port
.r
.valid
,
531 "i_user_port_0_rdata_ready" : self
.user_port
.r
.ready
,
532 "o_user_port_0_rdata_data" : self
.user_port
.r
.data
,
535 if self
._pins
is not None:
537 "o_ddram_a" : self
._pins
.a
,
538 "o_ddram_ba" : self
._pins
.ba
,
539 "o_ddram_ras_n" : self
._pins
.ras
,
540 "o_ddram_cas_n" : self
._pins
.cas
,
541 "o_ddram_we_n" : self
._pins
.we
,
542 "o_ddram_dm" : self
._pins
.dm
,
543 "o_ddram_clk_p" : self
._pins
.clk
.p
,
544 "o_ddram_cke" : self
._pins
.clk_en
,
545 "o_ddram_odt" : self
._pins
.odt
,
548 if hasattr(self
._pins
, "cs"):
550 "o_ddram_cs_n" : self
._pins
.cs
,
553 if hasattr(self
._pins
, "rst"):
555 "o_ddram_reset_n" : self
._pins
.rst
,
558 if isinstance(self
.config
, ECP5Config
):
560 "i_ddram_dq" : self
._pins
.dq
,
561 "i_ddram_dqs_p" : self
._pins
.dqs
.p
,
563 elif isinstance(self
.config
, Artix7Config
):
565 "io_ddram_dq" : self
._pins
.dq
,
566 "io_ddram_dqs_p" : self
._pins
.dqs
.p
,
567 "io_ddram_dqs_n" : self
._pins
.dqs
.n
,
568 "o_ddram_clk_n" : self
._pins
.clk
.n
,
573 return Instance(f
"{self.name}", **core_kwargs
)
578 "build_{{top.name}}.sh": r
"""
583 "{{top.name}}_config.yml": r
"""
586 # General ------------------------------------------------------------------
587 {% if top.config.phy_name == "ECP5DDRPHY" %}
588 "device": "{{platform.device}}-{{platform.speed}}{{platform.package}}",
591 {% if top.config.phy_name == "A7DDRPHY" %}
592 "speedgrade": {{top.config.speedgrade}},
594 "memtype": "{{top.config.memtype}}",
597 # PHY ----------------------------------------------------------------------
598 {% if top.config.phy_name == "A7DDRPHY" %}
599 "cmd_latency": {{top.config.cmd_latency}},
601 "sdram_module": "{{top.config.module_name}}",
602 "sdram_module_nb": {{top.config.module_bytes}},
603 "sdram_rank_nb": {{top.config.module_ranks}},
604 "sdram_phy": "{{top.config.phy_name}}",
606 # Electrical ---------------------------------------------------------------
607 {% if top.config.phy_name == "A7DDRPHY" %}
608 "rtt_nom": "{{top.config.rtt_nom}}ohm",
609 "rtt_wr": "{{top.config.rtt_wr}}ohm",
610 "ron": "{{top.config.ron}}ohm",
613 # Frequency ----------------------------------------------------------------
614 "input_clk_freq": {{top.config.input_clk_freq}},
615 "sys_clk_freq": {{top.config.user_clk_freq}},
616 {% if top.config.phy_name == "ECP5DDRPHY" %}
617 "init_clk_freq": {{top.config.init_clk_freq}},
618 {% elif top.config.phy_name == "A7DDRPHY" %}
619 "iodelay_clk_freq": {{top.config.iodelay_clk_freq}},
622 # Core ---------------------------------------------------------------------
623 "cmd_buffer_depth": {{top.config.cmd_buffer_depth}},
624 "csr_data_width": {{top.config.csr_data_width}},
626 # User Ports ---------------------------------------------------------------
630 "data_width": {{top.config.user_data_width}},
636 command_templates
= [
638 python -m litedram.gen
640 --output-dir {{top.name}}
641 --gateware-dir {{top.name}}
642 --csr-csv {{top.name}}_csr.csv
646 {{top.name}}_config.yml
656 * ``{{top.name}}_csr.csv`` : CSR listing.
657 * ``{{top.name}}/build_{{top.name}}.sh``: LiteDRAM build script.
658 * ``{{top.name}}/{{top.name}}.v`` : LiteDRAM core.
659 * ``{{top.name}}/software/include/generated/csr.h`` : CSR accessors.
660 * ``{{top.name}}/software/include/generated/git.h`` : Git version.
661 * ``{{top.name}}/software/include/generated/mem.h`` : Memory regions.
662 * ``{{top.name}}/software/include/generated/sdram_phy.h`` : SDRAM initialization sequence.
663 * ``{{top.name}}/software/include/generated/soc.h`` : SoC constants.
665 Lattice ECP5 platform:
666 * ``{{top.name}}/{{top.name}}.lpf`` : Constraints file.
667 * ``{{top.name}}/{{top.name}}.ys`` : Yosys script.
669 Xilinx Artix7 platform:
670 * ``{{top.name}}/{{top.name}}.xdc`` : Constraints file
671 * ``{{top.name}}/{{top.name}}.tcl`` : Vivado script.
673 Name conflict avoidance
674 -----------------------
676 Every time :meth:`litedram.Builder.prepare` is called, the name of the :class:`litedram.Core`
677 instance is added to ``namespace`. This allows the detection of name conflicts, which are
678 problematic for the following reasons:
679 * if two build plans are executed locally within the same root directory, the latter could
680 overwrite the products of the former.
681 * the LiteDRAM instance name becomes the name of its top-level Verilog module; importing
682 two modules with the same name will cause a toolchain error.
690 self
.namespace
= set()
692 def prepare(self
, core
, platform
, *, sim
=False, name_force
=False):
693 """Prepare a build plan.
697 core : :class:`litedram.Core`
698 The LiteDRAM instance to be built.
699 platform : :class:`nmigen.build.plat.Platform`
702 Do the build in simulation mode (i.e. by replacing the PHY with a model).
704 Force name. If ``True``, no exception will be raised in case of a name conflict with a
705 previous LiteDRAM instance.
709 A :class:`nmigen.build.run.BuildPlan` for this LiteDRAM instance.
713 Raises a :exn:`ValueError` if ``core.name`` conflicts with a previous build plan and
714 ``name_force`` is ``False``.
716 if not isinstance(core
, Core
):
717 raise TypeError("LiteDRAM core must be an instance of litedram.Core, not {!r}"
719 if not isinstance(platform
, Platform
):
720 raise TypeError("Target platform must be an instance of nmigen.build.plat.Platform, "
724 if core
.name
in self
.namespace
and not name_force
:
726 "LiteDRAM core name '{}' has already been used for a previous build. Building "
727 "this instance may overwrite previous build products. Passing `name_force=True` "
728 "will disable this check".format(core
.name
)
730 self
.namespace
.add(core
.name
)
732 autogenerated
= f
"Automatically generated by LambdaSoC {__version__}. Do not edit."
736 for index
, command_tpl
in enumerate(self
.command_templates
):
737 command
= render(command_tpl
, origin
="<command#{}>".format(index
+ 1))
738 command
= re
.sub(r
"\s+", " ", command
)
739 commands
.append(command
)
740 return "\n".join(commands
)
742 def render(source
, origin
):
744 source
= textwrap
.dedent(source
).strip()
745 compiled
= jinja2
.Template(source
, trim_blocks
=True, lstrip_blocks
=True)
746 except jinja2
.TemplateSyntaxError
as e
:
747 e
.args
= ("{} (at {}:{})".format(e
.message
, origin
, e
.lineno
),)
749 return compiled
.render({
750 "autogenerated": autogenerated
,
751 "emit_commands": emit_commands
,
754 "platform": platform
,
757 plan
= BuildPlan(script
=f
"build_{core.name}")
758 for filename_tpl
, content_tpl
in self
.file_templates
.items():
759 plan
.add_file(render(filename_tpl
, origin
=filename_tpl
),
760 render(content_tpl
, origin
=content_tpl
))