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 {"DDR3", "DDR4"}:
81 raise ValueError("Unsupported DRAM type, must be one of \"DDR2\", \"DDR3\" or "
85 if not isinstance(module_name
, str):
86 raise ValueError("Module name must be a string, not {!r}"
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, "
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
))
117 self
.memtype
= memtype
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
133 """LiteDRAM PHY name.
135 raise NotImplementedError
137 def get_module(self
):
138 """Get DRAM module description.
142 An instance of :class:`litedram.modules.SDRAMModule`, describing its geometry and timings.
144 import litedram
.modules
145 module_class
= getattr(litedram
.modules
, self
.module_name
)
146 module
= module_class(
147 clk_freq
= self
.user_clk_freq
,
150 assert module
.memtype
== self
.memtype
153 def request_pins(self
, platform
, name
, number
):
154 """Request DRAM pins.
156 This helper requests the DRAM pins with `dir="-"` and `xdr=0`, because LiteDRAM already
157 provides its own I/O buffers.
161 platform : :class:`nmigen.build.Platform`
166 DRAM resource number.
170 A :class:`Record` providing raw access to DRAM pins.
172 res
= platform
.lookup(name
, number
)
173 return platform
.request(
175 dir={io
.name
: "-" for io
in res
.ios
},
176 xdr
={io
.name
: 0 for io
in res
.ios
},
180 class ECP5Config(Config
):
181 phy_name
= "ECP5DDRPHY"
183 __doc__
= Config
._doc
_template
.format(
185 LiteDRAM configuration for ECP5 FPGAs.
189 Frequency of the PHY initialization clock, which is generated by the internal PLL.
192 def __init__(self
, *, init_clk_freq
, **kwargs
):
193 super().__init
__(**kwargs
)
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
201 class Artix7Config(Config
):
202 phy_name
= "A7DDRPHY"
204 __doc__
= Config
._doc
_template
.format(
206 LiteDRAM configuration for Artix 7 FPGAs.
210 FPGA speed grade (e.g. "-1").
212 Command additional latency.
214 Nominal termination impedance.
216 Write termination impedance.
218 Output driver impedance.
219 iodelay_clk_freq : int
220 IODELAY reference clock frequency.
223 def __init__(self
, *,
231 super().__init
__(**kwargs
)
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, "
244 if not isinstance(rtt_wr
, int) or rtt_wr
< 0:
245 raise ValueError("Write termination impedance must be a non-negative integer, "
248 if not isinstance(ron
, int) or ron
< 0:
249 raise ValueError("Output driver impedance must be a non-negative integer, "
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
))
256 self
.speedgrade
= speedgrade
257 self
.cmd_latency
= cmd_latency
258 self
.rtt_nom
= rtt_nom
261 self
.iodelay_clk_freq
= iodelay_clk_freq
264 class NativePort(Record
):
265 """LiteDRAM native port interface.
267 In the "Attributes" section, port directions are given from the point of view of user logic.
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
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
291 w.valid : Signal(), in
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
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
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
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}"
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}"
314 self
.addr_width
= addr_width
315 self
.data_width
= data_width
325 ("addr", addr_width
),
330 ("data", data_width
),
331 ("we", data_width
// self
.granularity
),
336 ("data", data_width
),
338 ], name
=name
, src_loc_at
=1 + src_loc_at
)
341 def memory_map(self
):
342 """Map of the native port.
346 An instance of :class:`nmigen_soc.memory.MemoryMap`.
350 Raises an :exn:`AttributeError` if the port does not have a memory map.
352 if self
._map
is None:
353 raise AttributeError("Native port {!r} does not have a memory map"
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}"
362 if memory_map
.data_width
!= 8:
363 raise ValueError("Memory map has data width {}, which is not the same as native port "
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
))
373 self
._map
= memory_map
376 class Core(Elaboratable
):
377 """An nMigen wrapper for a standalone LiteDRAM core.
381 config : :class:`Config`
382 LiteDRAM configuration.
383 pins : :class:`nmigen.lib.io.Pin`
384 Optional. DRAM pins. See :class:`nmigen_boards.resources.DDR3Resource` for layout.
386 Optional. Name of the LiteDRAM core. If ``None`` (default) the name is inferred from the
387 name of the variable this instance is assigned to.
389 Force name. If ``True``, no exception will be raised in case of a name collision with a
390 previous LiteDRAM instance. Defaults to ``False``.
395 Name of the LiteDRAM core.
398 user_port : :class:`NativePort`
399 User port. Provides access to the DRAM storage.
403 Raises a :exn:`ValueError` if ``name`` collides with the name given to a previous LiteDRAM
404 instance and ``name_force`` is ``False``.
406 def __init__(self
, config
, *, pins
=None, name
=None, name_force
=False, src_loc_at
=0):
407 if not isinstance(config
, Config
):
408 raise TypeError("Config must be an instance of litedram.Config, "
413 if name
is not None and not isinstance(name
, str):
414 raise TypeError("Name must be a string, not {!r}".format(name
))
415 self
.name
= name
or tracer
.get_var_name(depth
=2 + src_loc_at
)
417 module
= config
.get_module()
418 size
= config
.module_bytes \
419 * 2**( module
.geom_settings
.bankbits
420 + module
.geom_settings
.rowbits
421 + module
.geom_settings
.colbits
)
425 user_addr_width
= module
.geom_settings
.rowbits \
426 + module
.geom_settings
.colbits \
427 + log2_int(module
.nbanks
) \
428 + max(log2_int(config
.module_ranks
), 1)
430 self
.user_port
= NativePort(
431 addr_width
= user_addr_width
- log2_int(config
.user_data_width
// 8),
432 data_width
= config
.user_data_width
,
434 user_map
= MemoryMap(addr_width
=user_addr_width
, data_width
=8)
435 user_map
.add_resource("user_port_0", size
=size
)
436 self
.user_port
.memory_map
= user_map
438 self
._ctrl
_bus
= None
443 """Control bus interface.
445 *Please note that accesses to the CSRs exposed by this interface are not atomic.*
447 The memory map of this interface is populated by reading the ``{{self.name}}_csr.csv``
448 file from the build products.
452 An instance of :class:`nmigen_soc.wishbone.Interface`.
456 Raises an :exn:`AttributeError` if this getter is called before LiteDRAM is built (i.e.
457 before :meth:`Core.build` is called with `do_build=True`).
459 if self
._ctrl
_bus
is None:
460 raise AttributeError("Control bus memory map has not been populated. "
461 "Core.build(do_build=True) must be called before accessing "
463 return self
._ctrl
_bus
465 def _populate_ctrl_map(self
, build_products
):
466 if not isinstance(build_products
, BuildProducts
):
467 raise TypeError("Build products must be an instance of BuildProducts, not {!r}"
468 .format(build_products
))
470 # LiteDRAM's Wishbone to CSR bridge has a granularity of 8 bits.
471 ctrl_map
= MemoryMap(addr_width
=1, data_width
=8)
473 csr_csv
= build_products
.get(f
"{self.name}_csr.csv", mode
="t")
474 for row
in csv
.reader(csr_csv
.split("\n"), delimiter
=","):
475 if not row
or row
[0][0] == "#": continue
476 res_type
, res_name
, addr
, size
, attrs
= row
477 if res_type
== "csr_register":
478 ctrl_map
.add_resource(
480 addr
= int(addr
, 16),
481 size
= int(size
, 10) * self
.config
.csr_data_width
// ctrl_map
.data_width
,
485 self
._ctrl
_bus
= wishbone
.Interface(
486 addr_width
= ctrl_map
.addr_width
487 - log2_int(self
.config
.csr_data_width
// ctrl_map
.data_width
),
488 data_width
= self
.config
.csr_data_width
,
489 granularity
= ctrl_map
.data_width
,
491 self
._ctrl
_bus
.memory_map
= ctrl_map
493 def build(self
, builder
, *, do_build
=True, build_dir
="build/litedram", sim
=False,
495 """Build the LiteDRAM core.
499 builder: :class:`litedram.Builder`
502 Execute the build locally. Defaults to ``True``.
504 Root build directory. Defaults to ``"build/litedram"``.
506 Do the build in simulation mode (i.e. by replacing the PHY with a model). Defaults to
509 Ignore builder name conflicts. Defaults to ``False``.
513 An instance of :class:`nmigen.build.run.LocalBuildProducts` if ``do_build`` is ``True``.
514 Otherwise, an instance of :class:``nmigen.build.run.BuildPlan``.
516 if not isinstance(builder
, Builder
):
517 raise TypeError("Builder must be an instance of litedram.Builder, not {!r}"
520 plan
= builder
.prepare(self
, sim
=sim
, name_force
=name_force
)
524 products
= plan
.execute_local(build_dir
)
525 self
._populate
_ctrl
_map
(products
)
528 def elaborate(self
, platform
):
530 "i_clk" : ClockSignal(self
.config
.input_domain
),
531 "i_rst" : ResetSignal(self
.config
.input_domain
),
532 "o_user_clk" : ClockSignal(self
.config
.user_domain
),
533 "o_user_rst" : ResetSignal(self
.config
.user_domain
),
535 "i_wb_ctrl_adr" : self
.ctrl_bus
.adr
,
536 "i_wb_ctrl_dat_w" : self
.ctrl_bus
.dat_w
,
537 "o_wb_ctrl_dat_r" : self
.ctrl_bus
.dat_r
,
538 "i_wb_ctrl_sel" : self
.ctrl_bus
.sel
,
539 "i_wb_ctrl_cyc" : self
.ctrl_bus
.cyc
,
540 "i_wb_ctrl_stb" : self
.ctrl_bus
.stb
,
541 "o_wb_ctrl_ack" : self
.ctrl_bus
.ack
,
542 "i_wb_ctrl_we" : self
.ctrl_bus
.we
,
544 "i_user_port_0_cmd_valid" : self
.user_port
.cmd
.valid
,
545 "o_user_port_0_cmd_ready" : self
.user_port
.cmd
.ready
,
546 "i_user_port_0_cmd_we" : self
.user_port
.cmd
.we
,
547 "i_user_port_0_cmd_addr" : self
.user_port
.cmd
.addr
,
548 "i_user_port_0_wdata_valid" : self
.user_port
.w
.valid
,
549 "o_user_port_0_wdata_ready" : self
.user_port
.w
.ready
,
550 "i_user_port_0_wdata_we" : self
.user_port
.w
.we
,
551 "i_user_port_0_wdata_data" : self
.user_port
.w
.data
,
552 "o_user_port_0_rdata_valid" : self
.user_port
.r
.valid
,
553 "i_user_port_0_rdata_ready" : self
.user_port
.r
.ready
,
554 "o_user_port_0_rdata_data" : self
.user_port
.r
.data
,
557 if self
._pins
is not None:
559 "o_ddram_a" : self
._pins
.a
,
560 "o_ddram_ba" : self
._pins
.ba
,
561 "o_ddram_ras_n" : self
._pins
.ras
,
562 "o_ddram_cas_n" : self
._pins
.cas
,
563 "o_ddram_we_n" : self
._pins
.we
,
564 "o_ddram_dm" : self
._pins
.dm
,
565 "o_ddram_clk_p" : self
._pins
.clk
.p
,
566 "o_ddram_cke" : self
._pins
.clk_en
,
567 "o_ddram_odt" : self
._pins
.odt
,
570 if hasattr(self
._pins
, "cs"):
572 "o_ddram_cs_n" : self
._pins
.cs
,
575 if hasattr(self
._pins
, "rst"):
577 "o_ddram_reset_n" : self
._pins
.rst
,
580 if isinstance(self
.config
, ECP5Config
):
582 "i_ddram_dq" : self
._pins
.dq
,
583 "i_ddram_dqs_p" : self
._pins
.dqs
.p
,
585 elif isinstance(self
.config
, Artix7Config
):
587 "io_ddram_dq" : self
._pins
.dq
,
588 "io_ddram_dqs_p" : self
._pins
.dqs
.p
,
589 "io_ddram_dqs_n" : self
._pins
.dqs
.n
,
590 "o_ddram_clk_n" : self
._pins
.clk
.n
,
595 return Instance(f
"{self.name}", **core_kwargs
)
600 "build_{{top.name}}.sh": r
"""
605 "{{top.name}}_config.yml": r
"""
608 # General ------------------------------------------------------------------
610 {% if top.config.phy_name == "A7DDRPHY" %}
611 "speedgrade": {{top.config.speedgrade}},
613 "memtype": "{{top.config.memtype}}",
615 # PHY ----------------------------------------------------------------------
616 {% if top.config.phy_name == "A7DDRPHY" %}
617 "cmd_latency": {{top.config.cmd_latency}},
619 "sdram_module": "{{top.config.module_name}}",
620 "sdram_module_nb": {{top.config.module_bytes}},
621 "sdram_rank_nb": {{top.config.module_ranks}},
622 "sdram_phy": "{{top.config.phy_name}}",
624 # Electrical ---------------------------------------------------------------
625 {% if top.config.phy_name == "A7DDRPHY" %}
626 "rtt_nom": "{{top.config.rtt_nom}}ohm",
627 "rtt_wr": "{{top.config.rtt_wr}}ohm",
628 "ron": "{{top.config.ron}}ohm",
631 # Frequency ----------------------------------------------------------------
632 "input_clk_freq": {{top.config.input_clk_freq}},
633 "sys_clk_freq": {{top.config.user_clk_freq}},
634 {% if top.config.phy_name == "ECP5DDRPHY" %}
635 "init_clk_freq": {{top.config.init_clk_freq}},
636 {% elif top.config.phy_name == "A7DDRPHY" %}
637 "iodelay_clk_freq": {{top.config.iodelay_clk_freq}},
640 # Core ---------------------------------------------------------------------
641 "cmd_buffer_depth": {{top.config.cmd_buffer_depth}},
642 "csr_data_width": {{top.config.csr_data_width}},
644 # User Ports ---------------------------------------------------------------
648 "data_width": {{top.config.user_data_width}},
654 command_templates
= [
656 python -m litedram.gen
658 --output-dir {{top.name}}
659 --gateware-dir {{top.name}}
660 --csr-csv {{top.name}}_csr.csv
664 {{top.name}}_config.yml
674 * ``{{top.name}}_csr.csv`` : CSR listing.
675 * ``{{top.name}}/build_{{top.name}}.sh``: LiteDRAM build script.
676 * ``{{top.name}}/{{top.name}}.v`` : LiteDRAM core.
677 * ``{{top.name}}/software/include/generated/csr.h`` : CSR accessors.
678 * ``{{top.name}}/software/include/generated/git.h`` : Git version.
679 * ``{{top.name}}/software/include/generated/mem.h`` : Memory regions.
680 * ``{{top.name}}/software/include/generated/sdram_phy.h`` : SDRAM initialization sequence.
681 * ``{{top.name}}/software/include/generated/soc.h`` : SoC constants.
683 Lattice ECP5 platform:
684 * ``{{top.name}}/{{top.name}}.lpf`` : Constraints file.
685 * ``{{top.name}}/{{top.name}}.ys`` : Yosys script.
687 Xilinx Artix7 platform:
688 * ``{{top.name}}/{{top.name}}.xdc`` : Constraints file
689 * ``{{top.name}}/{{top.name}}.tcl`` : Vivado script.
691 Name conflict avoidance
692 -----------------------
694 Every time :meth:`litedram.Builder.prepare` is called, the name of the :class:`litedram.Core`
695 instance is added to ``namespace`. This allows the detection of name conflicts, which are
696 problematic for the following reasons:
697 * if two build plans are executed locally within the same root directory, the latter could
698 overwrite the products of the former.
699 * the LiteDRAM instance name becomes the name of its top-level Verilog module; importing
700 two modules with the same name will cause a toolchain error.
708 self
.namespace
= set()
710 def prepare(self
, core
, *, sim
=False, name_force
=False):
711 """Prepare a build plan.
715 core : :class:`litedram.Core`
716 The LiteDRAM instance to be built.
718 Do the build in simulation mode (i.e. by replacing the PHY with a model).
720 Force name. If ``True``, no exception will be raised in case of a name conflict with a
721 previous LiteDRAM instance.
725 A :class:`nmigen.build.run.BuildPlan` for this LiteDRAM instance.
729 Raises a :exn:`ValueError` if ``core.name`` conflicts with a previous build plan and
730 ``name_force`` is ``False``.
732 if not isinstance(core
, Core
):
733 raise TypeError("LiteDRAM core must be an instance of litedram.Core, not {!r}"
736 if core
.name
in self
.namespace
and not name_force
:
738 "LiteDRAM core name '{}' has already been used for a previous build. Building "
739 "this instance may overwrite previous build products. Passing `name_force=True` "
740 "will disable this check".format(core
.name
)
742 self
.namespace
.add(core
.name
)
744 autogenerated
= f
"Automatically generated by LambdaSoC {__version__}. Do not edit."
748 for index
, command_tpl
in enumerate(self
.command_templates
):
749 command
= render(command_tpl
, origin
="<command#{}>".format(index
+ 1))
750 command
= re
.sub(r
"\s+", " ", command
)
751 commands
.append(command
)
752 return "\n".join(commands
)
754 def render(source
, origin
):
756 source
= textwrap
.dedent(source
).strip()
757 compiled
= jinja2
.Template(source
, trim_blocks
=True, lstrip_blocks
=True)
758 except jinja2
.TemplateSyntaxError
as e
:
759 e
.args
= ("{} (at {}:{})".format(e
.message
, origin
, e
.lineno
),)
761 return compiled
.render({
762 "autogenerated": autogenerated
,
763 "emit_commands": emit_commands
,
768 plan
= BuildPlan(script
=f
"build_{core.name}")
769 for filename_tpl
, content_tpl
in self
.file_templates
.items():
770 plan
.add_file(render(filename_tpl
, origin
=filename_tpl
),
771 render(content_tpl
, origin
=content_tpl
))