1 from abc
import ABCMeta
, abstractmethod
9 from nmigen
import tracer
10 from nmigen
.build
.run
import BuildPlan
, BuildProducts
11 from nmigen
.utils
import log2_int
13 from nmigen_soc
import wishbone
14 from nmigen_soc
.memory
import MemoryMap
16 from .. import __version__
20 "Config", "ECP5Config", "Artix7Config",
26 class Config(metaclass
=ABCMeta
):
33 DRAM type (e.g. `"DDR3"`).
37 Number of byte groups of the DRAM interface.
39 Number of ranks. A rank is a set of DRAM chips that are connected to the same CS pin.
41 Frequency of the input clock, which drives the internal PLL.
43 Frequency of the user clock, which is generated by the internal PLL.
45 Input clock domain. Defaults to `"litedram_input"`.
47 User clock domain. Defaults to `"litedram_user"`.
49 User port data width. Defaults to 128.
50 cmd_buffer_depth : int
51 Command buffer depth. Defaults to 16.
53 CSR bus data width. Defaults to 32.
57 __doc__
= _doc_template
.format(
59 LiteDRAM base configuration.
70 input_domain
= "litedram_input",
71 user_domain
= "litedram_user",
72 user_data_width
= 128,
73 cmd_buffer_depth
= 16,
78 elif memtype
in {"DDR", "LPDDR", "DDR2"}:
80 elif memtype
in {"DDR3", "DDR4"}:
83 raise ValueError("Unsupported DRAM type, must be one of \"SDR\", \"DDR\", \"LPDDR\", "
84 "\"DDR2\", \"DDR3\" or \"DDR4\", not {!r}"
87 if not isinstance(module_name
, str):
88 raise ValueError("Module name must be a string, not {!r}"
90 if not isinstance(module_bytes
, int) or module_bytes
<= 0:
91 raise ValueError("Number of byte groups must be a positive integer, not {!r}"
92 .format(module_bytes
))
93 if not isinstance(module_ranks
, int) or module_ranks
<= 0:
94 raise ValueError("Number of ranks must be a positive integer, not {!r}"
95 .format(module_ranks
))
96 if not isinstance(input_clk_freq
, int) or input_clk_freq
<= 0:
97 raise ValueError("Input clock frequency must be a positive integer, not {!r}"
98 .format(input_clk_freq
))
99 if not isinstance(user_clk_freq
, int) or user_clk_freq
<= 0:
100 raise ValueError("User clock frequency must be a positive integer, not {!r}"
101 .format(user_clk_freq
))
102 if not isinstance(input_domain
, str):
103 raise ValueError("Input domain name must be a string, not {!r}"
104 .format(input_domain
))
105 if not isinstance(user_domain
, str):
106 raise ValueError("User domain name must be a string, not {!r}"
107 .format(user_domain
))
108 if user_data_width
not in {8, 16, 32, 64, 128}:
109 raise ValueError("User port data width must be one of 8, 16, 32, 64 or 128, "
111 .format(user_data_width
))
112 if not isinstance(cmd_buffer_depth
, int) or cmd_buffer_depth
<= 0:
113 raise ValueError("Command buffer depth must be a positive integer, not {!r}"
114 .format(cmd_buffer_depth
))
115 if csr_data_width
not in {8, 16, 32, 64}:
116 raise ValueError("CSR data width must be one of 8, 16, 32, or 64, not {!r}"
117 .format(csr_data_width
))
119 self
.memtype
= memtype
121 self
.module_name
= module_name
122 self
.module_bytes
= module_bytes
123 self
.module_ranks
= module_ranks
124 self
.input_clk_freq
= input_clk_freq
125 self
.user_clk_freq
= user_clk_freq
126 self
.input_domain
= input_domain
127 self
.user_domain
= user_domain
128 self
.user_data_width
= user_data_width
129 self
.cmd_buffer_depth
= cmd_buffer_depth
130 self
.csr_data_width
= csr_data_width
135 """LiteDRAM PHY name.
137 raise NotImplementedError
139 def get_module(self
):
140 """Get DRAM module description.
144 An instance of :class:`litedram.modules.SDRAMModule`, describing its geometry and timings.
146 import litedram
.modules
147 module_class
= getattr(litedram
.modules
, self
.module_name
)
148 module
= module_class(
149 clk_freq
= self
.user_clk_freq
,
152 assert module
.memtype
== self
.memtype
155 def request_pins(self
, platform
, name
, number
):
156 """Request DRAM pins.
158 This helper requests the DRAM pins with `dir="-"` and `xdr=0`, because LiteDRAM already
159 provides its own I/O buffers.
163 platform : :class:`nmigen.build.Platform`
168 DRAM resource number.
172 A :class:`Record` providing raw access to DRAM pins.
174 res
= platform
.lookup(name
, number
)
175 return platform
.request(
177 dir={io
.name
: "-" for io
in res
.ios
},
178 xdr
={io
.name
: 0 for io
in res
.ios
},
182 class ECP5Config(Config
):
183 phy_name
= "ECP5DDRPHY"
185 __doc__
= Config
._doc
_template
.format(
187 LiteDRAM configuration for ECP5 FPGAs.
191 Frequency of the PHY initialization clock, which is generated by the internal PLL.
194 def __init__(self
, *, init_clk_freq
, **kwargs
):
195 super().__init
__(**kwargs
)
197 if not isinstance(init_clk_freq
, int) or init_clk_freq
<= 0:
198 raise ValueError("Init clock frequency must be a positive integer, not {!r}"
199 .format(init_clk_freq
))
200 self
.init_clk_freq
= init_clk_freq
203 class Artix7Config(Config
):
204 phy_name
= "A7DDRPHY"
206 __doc__
= Config
._doc
_template
.format(
208 LiteDRAM configuration for Artix 7 FPGAs.
212 FPGA speed grade (e.g. "-1").
214 Command additional latency.
216 Nominal termination impedance.
218 Write termination impedance.
220 Output driver impedance.
221 iodelay_clk_freq : int
222 IODELAY reference clock frequency.
225 def __init__(self
, *,
233 super().__init
__(**kwargs
)
235 speedgrades
= ("-1", "-2", "-2L", "-2G", "-3")
236 if speedgrade
not in speedgrades
:
237 raise ValueError("Speed grade must be one of \'{}\', not {!r}"
238 .format("\', \'".join(speedgrades
), speedgrade
))
239 if not isinstance(cmd_latency
, int) or cmd_latency
< 0:
240 raise ValueError("Command latency must be a non-negative integer, not {!r}"
241 .format(cmd_latency
))
242 if not isinstance(rtt_nom
, int) or rtt_nom
< 0:
243 raise ValueError("Nominal termination impedance must be a non-negative integer, "
246 if not isinstance(rtt_wr
, int) or rtt_wr
< 0:
247 raise ValueError("Write termination impedance must be a non-negative integer, "
250 if not isinstance(ron
, int) or ron
< 0:
251 raise ValueError("Output driver impedance must be a non-negative integer, "
254 if not isinstance(iodelay_clk_freq
, int) or iodelay_clk_freq
<= 0:
255 raise ValueError("IODELAY clock frequency must be a positive integer, not {!r}"
256 .format(iodelay_clk_freq
))
258 self
.speedgrade
= speedgrade
259 self
.cmd_latency
= cmd_latency
260 self
.rtt_nom
= rtt_nom
263 self
.iodelay_clk_freq
= iodelay_clk_freq
266 class NativePort(Record
):
267 """LiteDRAM native port interface.
269 In the "Attributes" section, port directions are given from the point of view of user logic.
281 Port granularity, i.e. its smallest transferable unit of data. LiteDRAM native ports have a
282 granularity of 8 bits.
283 cmd.valid : Signal(), in
285 cmd.ready : Signal(), out
286 Command ready. Commands are accepted when `cmd.valid` and `cmd.ready` are both asserted.
287 cmd.last : Signal(), in
288 Command last. Indicates the last command of a burst.
289 cmd.we : Signal(), in
290 Command write enable. Indicates that this command is a write.
291 cmd.addr : Signal(addr_width), in
293 w.valid : Signal(), in
295 w.ready : Signal(), out
296 Write ready. Write data is accepted when `w.valid` and `w.ready` are both asserted.
297 w.data : Signal(data_width), in
299 w.we : Signal(data_width // granularity), bitmask, in
300 Write mask. Indicates which bytes in `w.data` are valid.
301 r.valid : Signal(), out
303 r.ready : Signal(), in
304 Read ready. Read data is consumed when `r.valid` and `r.ready` are both asserted.
305 r.data : Signal(data_width), out
308 def __init__(self
, *, addr_width
, data_width
, name
=None, src_loc_at
=0):
309 if not isinstance(addr_width
, int) or addr_width
<= 0:
310 raise ValueError("Address width must be a positive integer, not {!r}"
312 if not isinstance(data_width
, int) or data_width
<= 0 or data_width
& data_width
- 1:
313 raise ValueError("Data width must be a positive power of two integer, not {!r}"
316 self
.addr_width
= addr_width
317 self
.data_width
= data_width
327 ("addr", addr_width
),
332 ("data", data_width
),
333 ("we", data_width
// self
.granularity
),
338 ("data", data_width
),
340 ], name
=name
, src_loc_at
=1 + src_loc_at
)
343 def memory_map(self
):
344 """Map of the native port.
348 An instance of :class:`nmigen_soc.memory.MemoryMap`.
352 Raises an :exn:`AttributeError` if the port does not have a memory map.
354 if self
._map
is None:
355 raise AttributeError("Native port {!r} does not have a memory map"
360 def memory_map(self
, memory_map
):
361 if not isinstance(memory_map
, MemoryMap
):
362 raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
364 if memory_map
.data_width
!= 8:
365 raise ValueError("Memory map has data width {}, which is not the same as native port "
367 .format(memory_map
.data_width
, 8))
368 granularity_bits
= log2_int(self
.data_width
// 8)
369 if memory_map
.addr_width
!= max(1, self
.addr_width
+ granularity_bits
):
370 raise ValueError("Memory map has address width {}, which is not the same as native "
371 "port address width {} ({} address bits + {} granularity bits)"
372 .format(memory_map
.addr_width
, self
.addr_width
+ granularity_bits
,
373 self
.addr_width
, granularity_bits
))
375 self
._map
= memory_map
378 class Core(Elaboratable
):
379 """An nMigen wrapper for a standalone LiteDRAM core.
383 config : :class:`Config`
384 LiteDRAM configuration.
385 pins : :class:`nmigen.lib.io.Pin`
386 Optional. DRAM pins. See :class:`nmigen_boards.resources.DDR3Resource` for layout.
388 Optional. Name of the LiteDRAM core. If ``None`` (default) the name is inferred from the
389 name of the variable this instance is assigned to.
394 Name of the LiteDRAM core.
397 user_port : :class:`NativePort`
398 User port. Provides access to the DRAM storage.
400 def __init__(self
, config
, *, pins
=None, name
=None, src_loc_at
=0):
401 if not isinstance(config
, Config
):
402 raise TypeError("Config must be an instance of litedram.Config, "
407 if name
is not None and not isinstance(name
, str):
408 raise TypeError("Name must be a string, not {!r}".format(name
))
409 self
.name
= name
or tracer
.get_var_name(depth
=2 + src_loc_at
)
411 module
= config
.get_module()
412 size
= config
.module_bytes \
413 * 2**( module
.geom_settings
.bankbits
414 + module
.geom_settings
.rowbits
415 + module
.geom_settings
.colbits
)
419 user_addr_width
= module
.geom_settings
.rowbits \
420 + module
.geom_settings
.colbits \
421 + log2_int(module
.nbanks
) \
422 + max(log2_int(config
.module_ranks
), 1)
424 self
.user_port
= NativePort(
425 addr_width
= user_addr_width
- log2_int(config
.user_data_width
// 8),
426 data_width
= config
.user_data_width
,
428 user_map
= MemoryMap(addr_width
=user_addr_width
, data_width
=8)
429 user_map
.add_resource(object(), name
="user_port_0", size
=size
)
430 self
.user_port
.memory_map
= user_map
432 self
._ctrl
_bus
= None
437 """Control bus interface.
439 *Please note that accesses to the CSRs exposed by this interface are not atomic.*
441 The memory map of this interface is populated by reading the ``{{self.name}}_csr.csv``
442 file from the build products.
446 An instance of :class:`nmigen_soc.wishbone.Interface`.
450 Raises an :exn:`AttributeError` if this getter is called before LiteDRAM is built (i.e.
451 before :meth:`Core.build` is called with `do_build=True`).
453 if self
._ctrl
_bus
is None:
454 raise AttributeError("Control bus memory map has not been populated. "
455 "Core.build(do_build=True) must be called before accessing "
457 return self
._ctrl
_bus
459 def _populate_ctrl_map(self
, build_products
):
460 if not isinstance(build_products
, BuildProducts
):
461 raise TypeError("Build products must be an instance of BuildProducts, not {!r}"
462 .format(build_products
))
464 # LiteDRAM's Wishbone to CSR bridge has a granularity of 8 bits.
465 ctrl_map
= MemoryMap(addr_width
=1, data_width
=8)
467 csr_csv
= build_products
.get(f
"{self.name}_csr.csv", mode
="t")
468 for row
in csv
.reader(csr_csv
.split("\n"), delimiter
=","):
469 if not row
or row
[0][0] == "#": continue
470 res_type
, res_name
, addr
, size
, attrs
= row
471 if res_type
== "csr_register":
472 ctrl_map
.add_resource(
475 addr
= int(addr
, 16),
476 size
= int(size
, 10) * self
.config
.csr_data_width
// ctrl_map
.data_width
,
480 self
._ctrl
_bus
= wishbone
.Interface(
481 addr_width
= ctrl_map
.addr_width
482 - log2_int(self
.config
.csr_data_width
// ctrl_map
.data_width
),
483 data_width
= self
.config
.csr_data_width
,
484 granularity
= ctrl_map
.data_width
,
486 self
._ctrl
_bus
.memory_map
= ctrl_map
488 def build(self
, builder
, *, do_build
=True, build_dir
="build/litedram", sim
=False,
490 """Build the LiteDRAM core.
494 builder: :class:`litedram.Builder`
497 Execute the build locally. Defaults to ``True``.
499 Root build directory. Defaults to ``"build/litedram"``.
501 Do the build in simulation mode (i.e. by replacing the PHY with a model). Defaults to
504 Ignore builder name conflicts. Defaults to ``False``.
508 An instance of :class:`nmigen.build.run.LocalBuildProducts` if ``do_build`` is ``True``.
509 Otherwise, an instance of :class:``nmigen.build.run.BuildPlan``.
511 if not isinstance(builder
, Builder
):
512 raise TypeError("Builder must be an instance of litedram.Builder, not {!r}"
515 plan
= builder
.prepare(self
, sim
=sim
, name_force
=name_force
)
519 products
= plan
.execute_local(build_dir
)
520 self
._populate
_ctrl
_map
(products
)
523 def elaborate(self
, platform
):
525 "i_clk" : ClockSignal(self
.config
.input_domain
),
526 "i_rst" : ResetSignal(self
.config
.input_domain
),
527 "o_user_clk" : ClockSignal(self
.config
.user_domain
),
528 "o_user_rst" : ResetSignal(self
.config
.user_domain
),
530 "i_wb_ctrl_adr" : self
.ctrl_bus
.adr
,
531 "i_wb_ctrl_dat_w" : self
.ctrl_bus
.dat_w
,
532 "o_wb_ctrl_dat_r" : self
.ctrl_bus
.dat_r
,
533 "i_wb_ctrl_sel" : self
.ctrl_bus
.sel
,
534 "i_wb_ctrl_cyc" : self
.ctrl_bus
.cyc
,
535 "i_wb_ctrl_stb" : self
.ctrl_bus
.stb
,
536 "o_wb_ctrl_ack" : self
.ctrl_bus
.ack
,
537 "i_wb_ctrl_we" : self
.ctrl_bus
.we
,
539 "i_user_port_0_cmd_valid" : self
.user_port
.cmd
.valid
,
540 "o_user_port_0_cmd_ready" : self
.user_port
.cmd
.ready
,
541 "i_user_port_0_cmd_we" : self
.user_port
.cmd
.we
,
542 "i_user_port_0_cmd_addr" : self
.user_port
.cmd
.addr
,
543 "i_user_port_0_wdata_valid" : self
.user_port
.w
.valid
,
544 "o_user_port_0_wdata_ready" : self
.user_port
.w
.ready
,
545 "i_user_port_0_wdata_we" : self
.user_port
.w
.we
,
546 "i_user_port_0_wdata_data" : self
.user_port
.w
.data
,
547 "o_user_port_0_rdata_valid" : self
.user_port
.r
.valid
,
548 "i_user_port_0_rdata_ready" : self
.user_port
.r
.ready
,
549 "o_user_port_0_rdata_data" : self
.user_port
.r
.data
,
552 if self
._pins
is not None:
554 "o_ddram_a" : self
._pins
.a
,
555 "o_ddram_ba" : self
._pins
.ba
,
556 "o_ddram_ras_n" : self
._pins
.ras
,
557 "o_ddram_cas_n" : self
._pins
.cas
,
558 "o_ddram_we_n" : self
._pins
.we
,
559 "o_ddram_dm" : self
._pins
.dm
,
560 "o_ddram_clk_p" : self
._pins
.clk
.p
,
561 "o_ddram_cke" : self
._pins
.clk_en
,
562 "o_ddram_odt" : self
._pins
.odt
,
565 if hasattr(self
._pins
, "cs"):
567 "o_ddram_cs_n" : self
._pins
.cs
,
570 if hasattr(self
._pins
, "rst"):
572 "o_ddram_reset_n" : self
._pins
.rst
,
575 if isinstance(self
.config
, ECP5Config
):
577 "i_ddram_dq" : self
._pins
.dq
,
578 "i_ddram_dqs_p" : self
._pins
.dqs
.p
,
580 elif isinstance(self
.config
, Artix7Config
):
582 "io_ddram_dq" : self
._pins
.dq
,
583 "io_ddram_dqs_p" : self
._pins
.dqs
.p
,
584 "io_ddram_dqs_n" : self
._pins
.dqs
.n
,
585 "o_ddram_clk_n" : self
._pins
.clk
.n
,
590 return Instance(f
"{self.name}", **core_kwargs
)
595 "build_{{top.name}}.sh": r
"""
600 "{{top.name}}_config.yml": r
"""
603 # General ------------------------------------------------------------------
605 {% if top.config.phy_name == "A7DDRPHY" %}
606 "speedgrade": {{top.config.speedgrade}},
608 "memtype": "{{top.config.memtype}}",
610 # PHY ----------------------------------------------------------------------
611 {% if top.config.phy_name == "A7DDRPHY" %}
612 "cmd_latency": {{top.config.cmd_latency}},
614 "sdram_module": "{{top.config.module_name}}",
615 "sdram_module_nb": {{top.config.module_bytes}},
616 "sdram_rank_nb": {{top.config.module_ranks}},
617 "sdram_phy": "{{top.config.phy_name}}",
619 # Electrical ---------------------------------------------------------------
620 {% if top.config.phy_name == "A7DDRPHY" %}
621 "rtt_nom": "{{top.config.rtt_nom}}ohm",
622 "rtt_wr": "{{top.config.rtt_wr}}ohm",
623 "ron": "{{top.config.ron}}ohm",
626 # Frequency ----------------------------------------------------------------
627 "input_clk_freq": {{top.config.input_clk_freq}},
628 "sys_clk_freq": {{top.config.user_clk_freq}},
629 {% if top.config.phy_name == "ECP5DDRPHY" %}
630 "init_clk_freq": {{top.config.init_clk_freq}},
631 {% elif top.config.phy_name == "A7DDRPHY" %}
632 "iodelay_clk_freq": {{top.config.iodelay_clk_freq}},
635 # Core ---------------------------------------------------------------------
636 "cmd_buffer_depth": {{top.config.cmd_buffer_depth}},
637 "csr_data_width": {{top.config.csr_data_width}},
639 # User Ports ---------------------------------------------------------------
643 "data_width": {{top.config.user_data_width}},
649 command_templates
= [
651 python -m litedram.gen
653 --output-dir {{top.name}}
654 --gateware-dir {{top.name}}
655 --csr-csv {{top.name}}_csr.csv
659 {{top.name}}_config.yml
669 * ``{{top.name}}_csr.csv`` : CSR listing.
670 * ``{{top.name}}/build_{{top.name}}.sh``: LiteDRAM build script.
671 * ``{{top.name}}/{{top.name}}.v`` : LiteDRAM core.
672 * ``{{top.name}}/software/include/generated/csr.h`` : CSR accessors.
673 * ``{{top.name}}/software/include/generated/git.h`` : Git version.
674 * ``{{top.name}}/software/include/generated/mem.h`` : Memory regions.
675 * ``{{top.name}}/software/include/generated/sdram_phy.h`` : SDRAM initialization sequence.
676 * ``{{top.name}}/software/include/generated/soc.h`` : SoC constants.
678 Lattice ECP5 platform:
679 * ``{{top.name}}/{{top.name}}.lpf`` : Constraints file.
680 * ``{{top.name}}/{{top.name}}.ys`` : Yosys script.
682 Xilinx Artix7 platform:
683 * ``{{top.name}}/{{top.name}}.xdc`` : Constraints file
684 * ``{{top.name}}/{{top.name}}.tcl`` : Vivado script.
686 Name conflict avoidance
687 -----------------------
689 Every time :meth:`litedram.Builder.prepare` is called, the name of the :class:`litedram.Core`
690 instance is added to ``namespace`. This allows the detection of name conflicts, which are
691 problematic for the following reasons:
692 * if two build plans are executed locally within the same root directory, the latter could
693 overwrite the products of the former.
694 * the LiteDRAM instance name becomes the name of its top-level Verilog module; importing
695 two modules with the same name will cause a toolchain error.
703 self
.namespace
= set()
705 def prepare(self
, core
, *, sim
=False, name_force
=False):
706 """Prepare a build plan.
710 core : :class:`litedram.Core`
711 The LiteDRAM instance to be built.
713 Do the build in simulation mode (i.e. by replacing the PHY with a model).
715 Force name. If ``True``, no exception will be raised in case of a name conflict with a
716 previous LiteDRAM instance.
720 A :class:`nmigen.build.run.BuildPlan` for this LiteDRAM instance.
724 Raises a :exn:`ValueError` if ``core.name`` conflicts with a previous build plan and
725 ``name_force`` is ``False``.
727 if not isinstance(core
, Core
):
728 raise TypeError("LiteDRAM core must be an instance of litedram.Core, not {!r}"
731 if core
.name
in self
.namespace
and not name_force
:
733 "LiteDRAM core name '{}' has already been used for a previous build. Building "
734 "this instance may overwrite previous build products. Passing `name_force=True` "
735 "will disable this check".format(core
.name
)
737 self
.namespace
.add(core
.name
)
739 autogenerated
= f
"Automatically generated by LambdaSoC {__version__}. Do not edit."
743 for index
, command_tpl
in enumerate(self
.command_templates
):
744 command
= render(command_tpl
, origin
="<command#{}>".format(index
+ 1))
745 command
= re
.sub(r
"\s+", " ", command
)
746 commands
.append(command
)
747 return "\n".join(commands
)
749 def render(source
, origin
):
751 source
= textwrap
.dedent(source
).strip()
752 compiled
= jinja2
.Template(source
, trim_blocks
=True, lstrip_blocks
=True)
753 except jinja2
.TemplateSyntaxError
as e
:
754 e
.args
= ("{} (at {}:{})".format(e
.message
, origin
, e
.lineno
),)
756 return compiled
.render({
757 "autogenerated": autogenerated
,
758 "emit_commands": emit_commands
,
763 plan
= BuildPlan(script
=f
"build_{core.name}")
764 for filename_tpl
, content_tpl
in self
.file_templates
.items():
765 plan
.add_file(render(filename_tpl
, origin
=filename_tpl
),
766 render(content_tpl
, origin
=content_tpl
))