c5a7c601b2290c6806c1e9860aebf84678b3b3e4
1 from abc
import ABCMeta
, abstractmethod
9 from nmigen
import tracer
10 from nmigen
.build
.run
import BuildPlan
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("Core.build(do_build=True) must be called before accessing "
462 return self
._ctrl
_bus
464 def build(self
, builder
, *, do_build
=True, build_dir
="build/litedram", sim
=False,
466 """Build the LiteDRAM core.
470 builder: :class:`litedram.Builder`
473 Execute the build locally. Defaults to ``True``.
475 Root build directory. Defaults to ``"build/litedram"``.
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}"
491 plan
= builder
.prepare(self
, sim
=sim
, name_force
=name_force
)
495 products
= plan
.execute_local(build_dir
)
497 # LiteDRAM's Wishbone to CSR bridge uses an 8-bit granularity.
498 ctrl_map
= MemoryMap(addr_width
=1, data_width
=8)
500 with products
.extract(f
"{self.name}_csr.csv") as csr_csv_filename
:
501 with
open(csr_csv_filename
, "r") as csr_csv
:
502 for row
in csv
.reader(csr_csv
, delimiter
=","):
503 if row
[0][0] == "#": continue
504 res_type
, res_name
, addr
, size
, attrs
= row
505 if res_type
== "csr_register":
506 ctrl_map
.add_resource(
508 addr
= int(addr
, 16),
509 size
= int(size
, 10) * self
.config
.csr_data_width
510 // ctrl_map
.data_width
,
514 self
._ctrl
_bus
= wishbone
.Interface(
515 addr_width
= ctrl_map
.addr_width
516 - log2_int(self
.config
.csr_data_width
// ctrl_map
.data_width
),
517 data_width
= self
.config
.csr_data_width
,
518 granularity
= ctrl_map
.data_width
,
520 self
._ctrl
_bus
.memory_map
= ctrl_map
524 def elaborate(self
, platform
):
526 "i_clk" : ClockSignal(self
.config
.input_domain
),
527 "i_rst" : ResetSignal(self
.config
.input_domain
),
528 "o_user_clk" : ClockSignal(self
.config
.user_domain
),
529 "o_user_rst" : ResetSignal(self
.config
.user_domain
),
531 "i_wb_ctrl_adr" : self
.ctrl_bus
.adr
,
532 "i_wb_ctrl_dat_w" : self
.ctrl_bus
.dat_w
,
533 "o_wb_ctrl_dat_r" : self
.ctrl_bus
.dat_r
,
534 "i_wb_ctrl_sel" : self
.ctrl_bus
.sel
,
535 "i_wb_ctrl_cyc" : self
.ctrl_bus
.cyc
,
536 "i_wb_ctrl_stb" : self
.ctrl_bus
.stb
,
537 "o_wb_ctrl_ack" : self
.ctrl_bus
.ack
,
538 "i_wb_ctrl_we" : self
.ctrl_bus
.we
,
540 "i_user_port_0_cmd_valid" : self
.user_port
.cmd
.valid
,
541 "o_user_port_0_cmd_ready" : self
.user_port
.cmd
.ready
,
542 "i_user_port_0_cmd_we" : self
.user_port
.cmd
.we
,
543 "i_user_port_0_cmd_addr" : self
.user_port
.cmd
.addr
,
544 "i_user_port_0_wdata_valid" : self
.user_port
.w
.valid
,
545 "o_user_port_0_wdata_ready" : self
.user_port
.w
.ready
,
546 "i_user_port_0_wdata_we" : self
.user_port
.w
.we
,
547 "i_user_port_0_wdata_data" : self
.user_port
.w
.data
,
548 "o_user_port_0_rdata_valid" : self
.user_port
.r
.valid
,
549 "i_user_port_0_rdata_ready" : self
.user_port
.r
.ready
,
550 "o_user_port_0_rdata_data" : self
.user_port
.r
.data
,
553 if self
._pins
is not None:
555 "o_ddram_a" : self
._pins
.a
,
556 "o_ddram_ba" : self
._pins
.ba
,
557 "o_ddram_ras_n" : self
._pins
.ras
,
558 "o_ddram_cas_n" : self
._pins
.cas
,
559 "o_ddram_we_n" : self
._pins
.we
,
560 "o_ddram_dm" : self
._pins
.dm
,
561 "o_ddram_clk_p" : self
._pins
.clk
.p
,
562 "o_ddram_cke" : self
._pins
.clk_en
,
563 "o_ddram_odt" : self
._pins
.odt
,
566 if hasattr(self
._pins
, "cs"):
568 "o_ddram_cs_n" : self
._pins
.cs
,
571 if hasattr(self
._pins
, "rst"):
573 "o_ddram_reset_n" : self
._pins
.rst
,
576 if isinstance(self
.config
, ECP5Config
):
578 "i_ddram_dq" : self
._pins
.dq
,
579 "i_ddram_dqs_p" : self
._pins
.dqs
.p
,
581 elif isinstance(self
.config
, Artix7Config
):
583 "io_ddram_dq" : self
._pins
.dq
,
584 "io_ddram_dqs_p" : self
._pins
.dqs
.p
,
585 "io_ddram_dqs_n" : self
._pins
.dqs
.n
,
586 "o_ddram_clk_n" : self
._pins
.clk
.n
,
591 return Instance(f
"{self.name}", **core_kwargs
)
596 "build_{{top.name}}.sh": r
"""
601 "{{top.name}}_config.yml": r
"""
604 # General ------------------------------------------------------------------
606 {% if top.config.phy_name == "A7DDRPHY" %}
607 "speedgrade": {{top.config.speedgrade}},
609 "memtype": "{{top.config.memtype}}",
611 # PHY ----------------------------------------------------------------------
612 {% if top.config.phy_name == "A7DDRPHY" %}
613 "cmd_latency": {{top.config.cmd_latency}},
615 "sdram_module": "{{top.config.module_name}}",
616 "sdram_module_nb": {{top.config.module_bytes}},
617 "sdram_rank_nb": {{top.config.module_ranks}},
618 "sdram_phy": "{{top.config.phy_name}}",
620 # Electrical ---------------------------------------------------------------
621 {% if top.config.phy_name == "A7DDRPHY" %}
622 "rtt_nom": "{{top.config.rtt_nom}}ohm",
623 "rtt_wr": "{{top.config.rtt_wr}}ohm",
624 "ron": "{{top.config.ron}}ohm",
627 # Frequency ----------------------------------------------------------------
628 "input_clk_freq": {{top.config.input_clk_freq}},
629 "sys_clk_freq": {{top.config.user_clk_freq}},
630 {% if top.config.phy_name == "ECP5DDRPHY" %}
631 "init_clk_freq": {{top.config.init_clk_freq}},
632 {% elif top.config.phy_name == "A7DDRPHY" %}
633 "iodelay_clk_freq": {{top.config.iodelay_clk_freq}},
636 # Core ---------------------------------------------------------------------
637 "cmd_buffer_depth": {{top.config.cmd_buffer_depth}},
638 "csr_data_width": {{top.config.csr_data_width}},
640 # User Ports ---------------------------------------------------------------
644 "data_width": {{top.config.user_data_width}},
650 command_templates
= [
652 python -m litedram.gen
654 --output-dir {{top.name}}
655 --gateware-dir {{top.name}}
656 --csr-csv {{top.name}}_csr.csv
660 {{top.name}}_config.yml
670 * ``{{top.name}}_csr.csv`` : CSR listing.
671 * ``{{top.name}}/build_{{top.name}}.sh``: LiteDRAM build script.
672 * ``{{top.name}}/{{top.name}}.v`` : LiteDRAM core.
673 * ``{{top.name}}/software/include/generated/csr.h`` : CSR accessors.
674 * ``{{top.name}}/software/include/generated/git.h`` : Git version.
675 * ``{{top.name}}/software/include/generated/mem.h`` : Memory regions.
676 * ``{{top.name}}/software/include/generated/sdram_phy.h`` : SDRAM initialization sequence.
677 * ``{{top.name}}/software/include/generated/soc.h`` : SoC constants.
679 Lattice ECP5 platform:
680 * ``{{top.name}}/{{top.name}}.lpf`` : Constraints file.
681 * ``{{top.name}}/{{top.name}}.ys`` : Yosys script.
683 Xilinx Artix7 platform:
684 * ``{{top.name}}/{{top.name}}.xdc`` : Constraints file
685 * ``{{top.name}}/{{top.name}}.tcl`` : Vivado script.
687 Name conflict avoidance
688 -----------------------
690 Every time :meth:`litedram.Builder.prepare` is called, the name of the :class:`litedram.Core`
691 instance is added to ``namespace`. This allows the detection of name conflicts, which are
692 problematic for the following reasons:
693 * if two build plans are executed locally within the same root directory, the latter could
694 overwrite the products of the former.
695 * the LiteDRAM instance name becomes the name of its top-level Verilog module; importing
696 two modules with the same name will cause a toolchain error.
704 self
.namespace
= set()
706 def prepare(self
, core
, *, sim
=False, name_force
=False):
707 """Prepare a build plan.
711 core : :class:`litedram.Core`
712 The LiteDRAM instance to be built.
714 Do the build in simulation mode (i.e. by replacing the PHY with a model).
716 Force name. If ``True``, no exception will be raised in case of a name conflict with a
717 previous LiteDRAM instance.
721 A :class:`nmigen.build.run.BuildPlan` for this LiteDRAM instance.
725 Raises a :exn:`ValueError` if ``core.name`` conflicts with a previous build plan and
726 ``name_force`` is ``False``.
728 if not isinstance(core
, Core
):
729 raise TypeError("LiteDRAM core must be an instance of litedram.Core, not {!r}"
732 if core
.name
in self
.namespace
and not name_force
:
734 "LiteDRAM core name '{}' has already been used for a previous build. Building "
735 "this instance may overwrite previous build products. Passing `name_force=True` "
736 "will disable this check".format(core
.name
)
738 self
.namespace
.add(core
.name
)
740 autogenerated
= f
"Automatically generated by LambdaSoC {__version__}. Do not edit."
744 for index
, command_tpl
in enumerate(self
.command_templates
):
745 command
= render(command_tpl
, origin
="<command#{}>".format(index
+ 1))
746 command
= re
.sub(r
"\s+", " ", command
)
747 commands
.append(command
)
748 return "\n".join(commands
)
750 def render(source
, origin
):
752 source
= textwrap
.dedent(source
).strip()
753 compiled
= jinja2
.Template(source
, trim_blocks
=True, lstrip_blocks
=True)
754 except jinja2
.TemplateSyntaxError
as e
:
755 e
.args
= ("{} (at {}:{})".format(e
.message
, origin
, e
.lineno
),)
757 return compiled
.render({
758 "autogenerated": autogenerated
,
759 "emit_commands": emit_commands
,
764 plan
= BuildPlan(script
=f
"build_{core.name}")
765 for filename_tpl
, content_tpl
in self
.file_templates
.items():
766 plan
.add_file(render(filename_tpl
, origin
=filename_tpl
),
767 render(content_tpl
, origin
=content_tpl
))