User Tools

Site Tools


Platforms and I/O

When designing a project in nMigen, at some point you may need to make it run on an FPGA, for a specific board.

You can find the platforms for boards officially supported by nMigen in the nmigen-boards repository.

Adding a new board platform

Let's write a platform file for the LX16DDR board.

 LX16DDR board

nMigen provides generic platforms for various FPGA families. These platforms provide the glue between your design and the vendor toolchain used to configure the target FPGA.

You can find nMigen's currently supported FPGA families in the nmigen.vendor module.

You can create a platform for your board by deriving an FPGA platform.
For example, the LX16DDR uses a Spartan 6 FPGA, so it has to derive from the XilinxSpartan6Platform.

Here is the LX16DDR platform file:
import os
import subprocess
from import *
from nmigen.vendor.xilinx_spartan_3_6 import *
from import *
__all__ = ["LX16DDRPlatform"]
class LX16DDRPlatform(XilinxSpartan6Platform):
    device      = "xc6slx16"
    package     = "ftg256"
    speed       = "2"
    default_clk = "clk50"
    resources   = [
        Resource("clk50", 0, Pins("A10", dir="i"), Clock(50e6), Attrs(IOSTANDARD="LVCMOS33")),
        Resource("user_led", 0, Pins("T9", dir="o"), Attrs(IOSTANDARD="LVCMOS33", DRIVE="24", SLEW="QUIETIO")),
        Resource("user_led", 1, Pins("R9", dir="o"), Attrs(IOSTANDARD="LVCMOS33", DRIVE="24", SLEW="QUIETIO")),
            rx="A14", tx="C13",
            cs="T3", clk="R11", mosi="T10", miso="P10",
            attrs=Attrs(IOSTANDARD="LVCMOS33", SLEW="FAST")
        Resource("ddr3", 0,
            Subsignal("rst", PinsN("E4", dir="o")),
            Subsignal("clk", DiffPairs("E2", "E1", dir="o"), Attrs(IOSTANDARD="DIFF_SSTL15")),
            Subsignal("clk_en", Pins("F4", dir="o")),
            Subsignal("cs", PinsN("L2", dir="o")),
            Subsignal("we", PinsN("C1", dir="o")),
            Subsignal("ras", PinsN("J6", dir="o")),
            Subsignal("cas", PinsN("H5", dir="o")),
            Subsignal("a", Pins("K5 K6 D1 L4 G5 H4 H3 D3 B2 A2 G6 E3 F3 F6 F5", dir="o")),
            Subsignal("ba", Pins("C3 C2 B1", dir="o")),
            Subsignal("dqs", DiffPairs("H2 N3", "H1 N1", dir="io"), Attrs(IOSTANDARD="DIFF_SSTL15")),
            Subsignal("dq", Pins("K2 K1 J3 J1 F2 F1 G3 G1 L3 L1 M2 M1 P2 P1 R2 R1", dir="io")),
            Subsignal("dm", Pins("J4 K3", dir="o")),
            Subsignal("odt", Pins("L5", dir="o")),
            Attrs(IOSTANDARD="SSTL15", SLEW="FAST")
    connectors  = [
        Connector("gpio", 0, "E12 B15 C15 D14 E15 F15 G11 F14 G16 H15 G12 H13 J14 J11 K14 K15 L16 K11 M15 N14 M13 L12 P15 R15 R14 T13 T12"), # U7 1
        Connector("gpio", 1, "E13 B16 C16 D16 E16 F16 F12 F13 G14 H16 H11 H14 J16 J12 J13 K16 L14 K12 M16 N16 M14 L13 P16 R16 T15 T14 R12"), # U7 2
        Connector("gpio", 2, "A14 C13 B12 C11 B10 C9 B8 C7 B6 B5 E10 E11 F9 C8 E7 F7 D6 M7 N8 P9 T5 T6 N9 L8 L10 P12 R9"), # U8 1
        Connector("gpio", 3, "B14 A13 A12 A11 A9 A8 A7 A6 A5 A4 C10 F10 D9 D8 E6 C6 N6 P6 L7 T4 R5 T7 M9 M10 P11 M11 T9")  # U8 2
    def toolchain_program(self, products, name):
        xc3sprog = os.environ.get("XC3SPROG", "xc3sprog")
        with products.extract("{}.bit".format(name)) as bitstream_filename:
  [xc3sprog, "-c", "ft4232h", bitstream_filename], check=True)

Board platforms consist mostly of a list of I/O resources wired to the FPGA, such as clocks, LEDs, UARTs, etc.

The pinout has been obtained by reading the board schematics: lx16ddr_red_board.pdf

Resource("clk50", 0, Pins("A10", dir="i"), Clock(50e6), Attrs(IOSTANDARD="LVCMOS33"))

Pin descriptions have a direction (input, output or inout) which determines the way they can be used in your design. For example, your design cannot drive a pin marked as input (dir=“i”), as doing so could damage your board.

Active-low signals and differential pairs can be declared with PinsN and DiffPairs. Doing so lets nMigen automatically instantiate the right I/O buffer between the pin and our design, so we don't have to do it ourselves.

If the resource describes a clock signal, it must have a Clock() constraint, with its period in nanoseconds.

Other constraints such as I/O standards, slew rate, drive strength, etc can be added to your ressource by using Attrs(NAME=“VALUE”).

Build a LED blinker
from nmigen import *
from lx16ddr import LX16DDRPlatform
class Blinky(Elaboratable):
    def __init__(self, platform):
        m = Module()
        counter = Signal(25)
        m.d.sync += counter.eq(counter + 1)
        led = platform.request("user_led", 0)
        m.d.comb += led.o.eq(counter[24])
        return m
if __name__ == "__main__":
    LX16DDRPlatform().build(Blinky(), do_program=True)

Let's look closer at how we used our led:

led = platform.request("user_led", 0)
m.d.comb += led.o.eq(counter[24])

As we saw before, “user_led”:0 is tied to an output pin. led is therefore a Record with an “o” field.

If we requested a input pin, say “clk50”, we would have gotten a Record with an “i” field.
If we requested an inout pin, we would have gotten a Record with three fields: “i”, “o” and “oe”.

Building the design is only a matter of calling .build():

if __name__ == "__main__":
    LX16DDRPlatform().build(Blinky(), do_program=True)

This will automatically compile your design to RTLIL or Verilog, generate the platform constraints, and call your vendor toolchain to build the bitstream.

do_program=True will call the .toolchain_program() method we defined earlier to automatically program your FPGA afterwards.


nmigen/nmigen_board.txt · Last modified: 2019/09/04 15:17 by jfng