pinout module#

AMD Versal LPDDR4/4X pin rules on one DDRMC triplet, as data.

Positions are triplet-relative: Pos(bank, nibble, pin), where bank is 0, 1 or 2 within the triplet (the first, second and third bank of AMD’s figures), nibble is N0 to N8 and pin is P0 to P5. Pos.index is the MxP number in the pin file’s pin names. On the VP1002 the only triplet is M0, banks 700, 701 and 702.

Sources#

  • PG313: AMD, Versal Adaptive SoC Programmable Network on Chip and Integrated Memory Controller 1.1 LogiCORE IP Product Guide, PG313 v1.1, 2026-06-23, https://docs.amd.com/r/en-US/pg313-network-on-chip.

  • AM013: AMD, Versal Adaptive SoC Packaging and Pinouts Architecture Manual, AM013 v1.10, “About ASCII Package Files”.

  • Tutorial: AMD, Vivado-Design-Tutorials, tag 2025.2, Versal/PCB_Design/Memory_Pinouts/README.md, Appendix B, “LPDDR4 and LPDDR4X Pinouts” (Apache-2.0).

  • Boards: AMD board files, XilinxBoardStore tag 2025.2, boards/Xilinx/vmk180/production/3.3, .../vpk120/production/1.2 and .../vek280/production/1.2 (part0_pins.xml and board.xml), and the VCK190 constraints lpddr4_mc3_vck190.xdc in Vivado-Design-Tutorials tag 2025.2, Versal/Memory_and_NoC/NoC_DDRMC/Performance_Tuning (Apache-2.0). Each pin there is placed in its triplet with the device’s own AMD package file. tests/test_js4_pinout.py holds the result.

Rule table#

#

Rule

Source

1

Every command, address, control, reset and clock pin is fixed: CA_A, CA_B, CKE_A, CKE_B, CS_A, CS_B, CK_T, CK_C, RESET_N.

PG313, “LPDDR4/4X Pin Rules”

2

DQ, DM and DQS pins are fixed apart from the two swaps in rules 3 and 4.

PG313, “LPDDR4/4X Pin Rules”

3

DQ bits within a byte can be swapped.

PG313, “LPDDR4/4X Pin Rules”, Table 1

4

Bytes can swap between the x16 halves of one channel (DQ[7:0] with DQ[15:8]); the DQS and DM go with their byte. A DQS pair swaps only with a DQS pair and a DM only with a DM. Channel A (DQ[15:0]) must not swap with channel B (DQ[31:16]).

PG313, “LPDDR4/4X Pin Rules”, Table 2

5

Memory controller channel 0 must not swap with channel 1.

PG313, “LPDDR4/4X Pin Rules”

6

The DQ mapping to the memory is one to one by name: LPDDR4_DQ_A[0] to DQ0 of channel A, through LPDDR4_DQ_B[15] to DQ15 of channel B.

PG313, “LPDDR4/4X Pin Rules”

7

A byte occupies one nibble pair (N0/N1, N2/N3, N4/N5 or N6/N7) of one bank: DQS_T and DQS_C on P0 and P1 of one nibble, DMI on P0 of the other, the eight DQ on P2 to P5 of both. P0 and P1 of either nibble cannot swap with a DQ. In Tables 1 and 2, P1 of the DMI nibble carries CS_B or is NC.

PG313, Tables 1 and 2

8

IO_VR_700 connects to a 240 ohm resistor pulled up to the bank’s VCCO.

PG313, “LPDDR4/4X Pin Rules”

9

Each DDRMC needs one differential system clock (sys_clk).

PG313, “LPDDR4/4X Pin Rules”

10

The sys_clk nibble holds the system clock pair, RESET_n and ALERT_n.

PG313, “Pin Efficient 1x32 Component Interface (Non-Flipped)”

11

A single channel interface always uses the Optimum pinout, which supports single or dual rank, and cannot select the pin efficient option.

PG313, “Single Channel LPDDR4/4X Interfaces”

12

Single channel 32-bit, non-flipped nibble use: first bank N0, N1 DQ B; N2, N3 DQ A; N8 sys_clk; N4, N5 AC B; N6, N7 AC A. Second bank N0, N1 DQ B; N4, N5 DQ A. The rest is free.

PG313, “Single Channel LPDDR4/4X Interfaces”, Figure 2

13

Standard dual channel 2x32, non-flipped: channel 0 as rule 12; second bank N2, N3 DQ CH1 B and N6, N7 DQ CH1 A; third bank N0, N1 AC CH1 A, N2, N3 DQ CH1 A, N4, N5 AC CH1 B, N6, N7 DQ CH1 B. A single channel pinout is channel 0 of this one.

PG313, “Dual Channel LPDDR4/4X Interfaces”, Figure 2

14

Pin efficient 2x32, non-flipped, single rank only: first bank N0 to N3 DQ CH0 B, N8 AC CH0, N4 to N7 DQ CH0 A; second bank N0, N1 DQ CH1 A, N2, N3 DQ CH1 B, N8 AC CH1, N4, N5 DQ CH1 A, N6, N7 DQ CH1 B; third bank N8 sys_clk.

PG313, “Dual Channel LPDDR4/4X Interfaces”, Figure 3

15

The pins of an interface can be reversed in the triplet of banks (the flipped pinout).

Tutorial, Appendix B

16

LPDDR4 and LPDDR4X pinouts are identical; the interface is x32 only; two ranks are supported for dual-die devices.

Tutorial, Appendix B

17

The pin file’s “DDRMC Only” column: YES means DDR memory only, YES_PLUS_GC adds clock management access, NO means full I/O logic access. NO does not exclude DDR use.

AM013, “About ASCII Package Files”

18

“All pins swaps must be captured in the design’s XDC and validated before generating hardware.” This kit exports no XDC and does not check Vivado legality; that validation is the designer’s step.

PG313, “LPDDR4/4X Pin Rules”

Inferred from the boards#

The documents place most command, address, clock, reset and sys_clk pins only to the nibble. The positions below are where the boards that use a layout agree, and the kit encodes them on that basis. VEK280 uses the pin efficient layout on all three of its triplets, M0 and M1 non-flipped and M2 flipped; every other board interface uses the standard layout, flipped.

The kit offers the flipped layout, OPTIMUM_X32_FLIPPED, so each of its fixed positions, and every DQS_T, DQS_C and DMI position, is where channel 0 of VMK180, VCK190 and VPK120 puts it, pin for pin (test_standard_channel0_matches_the_kit_layout_pin_for_pin). The kit encodes the layout as the mirror of the non-flipped OPTIMUM_X32 (see Pos.mirrored()); that test is what ties it to the boards. The non-flipped positions are the other way round: 23 of OPTIMUM_X32’s 25 fixed positions (all but CS0_B and CS1_B, which PG313 Tables 1 and 2 print) and the DQS_T, DQS_C and DMI positions of byte slot 2 are mirrors of flipped board layouts, which no board carries and no AMD document prints. Either way, the chosen assignment is validated in Vivado (the Tutorial’s “Obtaining Versal Memory Pinouts in Vivado” and “Modifying XDC Pinouts and DRC Checking”; rule 18).

  • Flip as a mirror: bank b to 2 - b, nibble n to 7 - n (N8 stays), pin kept. The nibble mirror is what PG313’s figures “Nibble Utilization for 1x32 Component Interface (Non-Flipped)” and “(Flipped)” show. That the pin index is kept is from the boards: VEK280’s flipped M2 interfaces, mirrored, put every non-DQ pin at the pin index of its non-flipped M0 counterpart (lpddr4_5 against lpddr4_1, lpddr4_4 against lpddr4_0; test_vek280_flipped_triplet_mirrors_an_unflipped_one). Every board interface, mirrored, matches rules 7 and 12 to 14 and the pin positions PG313 Tables 1 and 2 print. VMK180, VCK190, VPK120, VEK280.

  • Pin positions of CA, CK and CKE of both channels, CS0_A, RESET_N, and the sys_clk pair (fixed): channel 0 of VMK180, VCK190 and VPK120 all agree. VEK280 uses the pin efficient pinout (rule 14), a different documented configuration, and has no say.

  • Byte slot 2, second bank: rule 12 puts channel B’s byte on N0 and N1 (N7 and N6 flipped); which nibble carries DQS and which DMI is from channel 0 of VMK180, VCK190 and VPK120.

  • A command or address pin may sit on P1 of a DMI nibble: VEK280’s pin efficient interfaces put CA bits of channel A there (rule 14 gives each controller channel one AC nibble, N8). nibble_violations() accepts it; the kit’s layouts use it only for CS0_B and CS1_B, as Tables 1 and 2 print.

  • CS1_A position: VMK180 and VPK120 agree; VCK190 is single rank and has no CS1. CS1_B is PG313 Table 2’s MxP19 in the non-flipped layout; flipped, it is MxP133, where VMK180 and VPK120 put it.

  • One CKE per channel on the controller side, a judgement the person confirms. What each source says:

    • PG313 “LPDDR4/4X Pin Rules” names the controller pins CKE_A and CKE_B, with no rank index.

    • UG863 v1.12 “Timing Constraint Rules for LPDDR4/4x Signals”, Table 1, lists CKE0_A and CKE1_A (and the _B pair) under the pin pair set “Adaptive SoC to LPDDR4/4x Device”, CKE1 marked dual rank only. That reads as a second controller CKE per channel for dual rank.

    • UG863 “Signals and Connections for LPDDR4/4x Interfaces” lists CKE0 and CKE1 (dual rank only) as memory signals, each terminated 160 ohm.

    • Every board file maps one controller pin to CKE_A and one to CKE_B, including the dual-rank VMK180 and VPK120, which map two CS pins per channel. The VCK190 constraints name cke_a[0], indexed.

    The kit follows the boards: both memory CKE balls of a channel join the controller’s one CKE. If the controller drives a CKE1, this is wrong and the offer needs a second CKE pin per channel, whose position no source in hand gives.

What the kit offers#

OPTIMUM_X32_FLIPPED is rule 12 taken to pin level and flipped (rule 15): a single channel x32 on the third and second banks of the triplet (VP1002 banks 702 and 701), the layout VMK180, VCK190 and VPK120 use for channel 0. Its free choices are rule 4 (which of its channel’s two byte slots each byte takes) and rule 3 (any order of a byte’s eight DQ within its slot): 2 * 2 * factorial(8) ** 4 assignments, assignment_count().

Not offered, though documented: the non-flipped pinout (OPTIMUM_X32, banks 700 and 701), dual channel, and pin efficient (rule 14, single rank only; the kit’s memory is dual rank).

class Pos(bank, nibble, pin)[source]#

Bases: object

A triplet-relative pin position.

Parameters:
bank: int#

0, 1 or 2 within the triplet

nibble: int#

0 to 8

pin: int#

0 to 5

property index: int#

The MxP number of this position in the pin file.

mirrored()[source]#

This position in the flipped pinout (rule 15, mirror inferred).

Return type:

Pos

static parse(text)[source]#

Parse three digits: bank, nibble, pin ("130" is bank 1 N3 P0).

Return type:

Pos

Parameters:

text (str)

class Channel(*values)[source]#

Bases: Enum

An LPDDR4 x16 channel of the memory.

A = 'A'#
B = 'B'#
byte_channel(byte)[source]#

The channel of byte 0 to 3 (rule 4: bytes 0 and 1 are channel A).

Return type:

Channel

Parameters:

byte (int)

class ByteSlot(channel, bank, dqs_nibble)[source]#

Bases: object

The nibble pair one byte occupies (rule 7).

Parameters:
channel: Channel#
bank: int#
dqs_nibble: int#

Nibble with DQS_T on P0 and DQS_C on P1

property dmi_nibble: int#

The partner nibble, with DMI on P0.

property dqs_t: Pos#
property dqs_c: Pos#
property dmi: Pos#
property dq: tuple[Pos, ...]#

P2 to P5 of the DQS nibble, then of the DMI nibble. Any order of a byte’s DQ over them is allowed (rule 3).

Type:

The eight DQ positions

mirrored()[source]#
Return type:

ByteSlot

class NibbleLayout(name, pairs, ac, sys_clk_bank)[source]#

Bases: object

A documented base layout at nibble level, for one x32 interface.

Parameters:
name: str#
pairs: Mapping[Channel, tuple[tuple[int, int], ...]]#

Per channel, the (bank, even nibble) of each DQ nibble pair

ac: Mapping[Channel, frozenset[tuple[int, int]]]#

Per channel, the (bank, nibble) of each AC nibble

sys_clk_bank: int#
mirrored()[source]#
Return type:

NibbleLayout

OPTIMUM_CH0 = NibbleLayout(name='Optimum x32, channel 0 (PG313 single channel Figure 2)', pairs={<Channel.A: 'A'>: ((0, 2), (1, 4)), <Channel.B: 'B'>: ((0, 0), (1, 0))}, ac={<Channel.A: 'A'>: frozenset({(0, 7), (0, 6)}), <Channel.B: 'B'>: frozenset({(0, 4), (0, 5)})}, sys_clk_bank=0)#

Rules 12 and 13, channel 0.

OPTIMUM_CH1 = NibbleLayout(name='Optimum 2x32, channel 1 (PG313 dual channel Figure 2)', pairs={<Channel.A: 'A'>: ((1, 6), (2, 2)), <Channel.B: 'B'>: ((1, 2), (2, 6))}, ac={<Channel.A: 'A'>: frozenset({(2, 0), (2, 1)}), <Channel.B: 'B'>: frozenset({(2, 4), (2, 5)})}, sys_clk_bank=0)#

Rule 13, channel 1.

PIN_EFFICIENT_CH0 = NibbleLayout(name='Pin efficient 2x32, channel 0 (PG313 dual channel Figure 3)', pairs={<Channel.A: 'A'>: ((0, 4), (0, 6)), <Channel.B: 'B'>: ((0, 0), (0, 2))}, ac={<Channel.A: 'A'>: frozenset({(0, 8)}), <Channel.B: 'B'>: frozenset({(0, 8)})}, sys_clk_bank=2)#

Rule 14, channel 0. Its AC nibble serves both memory channels.

PIN_EFFICIENT_CH1 = NibbleLayout(name='Pin efficient 2x32, channel 1 (PG313 dual channel Figure 3)', pairs={<Channel.A: 'A'>: ((1, 0), (1, 4)), <Channel.B: 'B'>: ((1, 2), (1, 6))}, ac={<Channel.A: 'A'>: frozenset({(1, 8)}), <Channel.B: 'B'>: frozenset({(1, 8)})}, sys_clk_bank=2)#

Rule 14, channel 1.

nibble_violations(pinout, layout)[source]#

Check a pinout against rules 1 to 7, 10 and one nibble layout.

pinout maps canonical names to positions: DQ0 to DQ31, DQS0_T/DQS0_C to DQS3_*, DMI0 to DMI3, CA0_A to CA5_B, CK_T_A, CK_C_A, CKE_A, CS0_A, CS1_A (and _B), RESET_N, and optionally SYS_CLK_P/SYS_CLK_N. Returns one line per broken rule; an empty list is a member.

Return type:

list[str]

Parameters:
class PinLayout(nibbles, slots, fixed=dict())[source]#

Bases: object

A base layout at pin level: the fixed pins and the byte slots.

Parameters:
nibbles: NibbleLayout#
slots: tuple[ByteSlot, ...]#

Byte slots; slot k holds byte k before any byte swap

fixed: Mapping[str, Pos]#
mirrored()[source]#
Return type:

PinLayout

byte_orders()[source]#

Every allowed byte-to-slot assignment (rule 4): entry k is the slot index byte k takes.

Return type:

list[tuple[int, int, int, int]]

violations(pinout)[source]#

Check a pinout against this layout at pin level, plus every rule nibble_violations() checks. Fixed names absent from pinout (CS1 on a single-rank board) are skipped.

Return type:

list[str]

Parameters:

pinout (Mapping[str, Pos])

OPTIMUM_X32 = PinLayout(nibbles=NibbleLayout(name='Optimum x32, channel 0 (PG313 single channel Figure 2)', pairs={<Channel.A: 'A'>: ((0, 2), (1, 4)), <Channel.B: 'B'>: ((0, 0), (1, 0))}, ac={<Channel.A: 'A'>: frozenset({(0, 7), (0, 6)}), <Channel.B: 'B'>: frozenset({(0, 4), (0, 5)})}, sys_clk_bank=0), slots=(ByteSlot(channel=<Channel.A: 'A'>, bank=1, dqs_nibble=4), ByteSlot(channel=<Channel.A: 'A'>, bank=0, dqs_nibble=2), ByteSlot(channel=<Channel.B: 'B'>, bank=1, dqs_nibble=0), ByteSlot(channel=<Channel.B: 'B'>, bank=0, dqs_nibble=1)), fixed={'CA2_A': Pos(bank=0, nibble=7, pin=0), 'CS1_A': Pos(bank=0, nibble=7, pin=1), 'CA1_A': Pos(bank=0, nibble=7, pin=2), 'CA0_A': Pos(bank=0, nibble=7, pin=3), 'CS0_A': Pos(bank=0, nibble=7, pin=4), 'CA3_A': Pos(bank=0, nibble=7, pin=5), 'CK_T_A': Pos(bank=0, nibble=6, pin=0), 'CK_C_A': Pos(bank=0, nibble=6, pin=1), 'CA4_A': Pos(bank=0, nibble=6, pin=2), 'CA5_A': Pos(bank=0, nibble=6, pin=3), 'CKE_A': Pos(bank=0, nibble=6, pin=4), 'CA0_B': Pos(bank=0, nibble=5, pin=2), 'CA3_B': Pos(bank=0, nibble=5, pin=4), 'CA2_B': Pos(bank=0, nibble=5, pin=5), 'CK_T_B': Pos(bank=0, nibble=4, pin=0), 'CK_C_B': Pos(bank=0, nibble=4, pin=1), 'CA5_B': Pos(bank=0, nibble=4, pin=2), 'CA4_B': Pos(bank=0, nibble=4, pin=3), 'CKE_B': Pos(bank=0, nibble=4, pin=4), 'CA1_B': Pos(bank=0, nibble=4, pin=5), 'CS0_B': Pos(bank=0, nibble=0, pin=1), 'CS1_B': Pos(bank=0, nibble=3, pin=1), 'SYS_CLK_P': Pos(bank=0, nibble=8, pin=0), 'SYS_CLK_N': Pos(bank=0, nibble=8, pin=1), 'RESET_N': Pos(bank=0, nibble=8, pin=2)})#

single channel x32, non-flipped, on the first and second banks. Not the kit’s layout. 23 of the 25 fixed positions, and byte slot 2’s DQS_T, DQS_C and DMI positions, are inferred by mirroring the boards.

Type:

Rule 12 taken to pin level

OPTIMUM_X32_FLIPPED = PinLayout(nibbles=NibbleLayout(name='Optimum x32, channel 0 (PG313 single channel Figure 2), flipped', pairs={<Channel.A: 'A'>: ((2, 4), (1, 2)), <Channel.B: 'B'>: ((2, 6), (1, 6))}, ac={<Channel.A: 'A'>: frozenset({(2, 0), (2, 1)}), <Channel.B: 'B'>: frozenset({(2, 3), (2, 2)})}, sys_clk_bank=2), slots=(ByteSlot(channel=<Channel.A: 'A'>, bank=1, dqs_nibble=3), ByteSlot(channel=<Channel.A: 'A'>, bank=2, dqs_nibble=5), ByteSlot(channel=<Channel.B: 'B'>, bank=1, dqs_nibble=7), ByteSlot(channel=<Channel.B: 'B'>, bank=2, dqs_nibble=6)), fixed={'CA2_A': Pos(bank=2, nibble=0, pin=0), 'CS1_A': Pos(bank=2, nibble=0, pin=1), 'CA1_A': Pos(bank=2, nibble=0, pin=2), 'CA0_A': Pos(bank=2, nibble=0, pin=3), 'CS0_A': Pos(bank=2, nibble=0, pin=4), 'CA3_A': Pos(bank=2, nibble=0, pin=5), 'CK_T_A': Pos(bank=2, nibble=1, pin=0), 'CK_C_A': Pos(bank=2, nibble=1, pin=1), 'CA4_A': Pos(bank=2, nibble=1, pin=2), 'CA5_A': Pos(bank=2, nibble=1, pin=3), 'CKE_A': Pos(bank=2, nibble=1, pin=4), 'CA0_B': Pos(bank=2, nibble=2, pin=2), 'CA3_B': Pos(bank=2, nibble=2, pin=4), 'CA2_B': Pos(bank=2, nibble=2, pin=5), 'CK_T_B': Pos(bank=2, nibble=3, pin=0), 'CK_C_B': Pos(bank=2, nibble=3, pin=1), 'CA5_B': Pos(bank=2, nibble=3, pin=2), 'CA4_B': Pos(bank=2, nibble=3, pin=3), 'CKE_B': Pos(bank=2, nibble=3, pin=4), 'CA1_B': Pos(bank=2, nibble=3, pin=5), 'CS0_B': Pos(bank=2, nibble=7, pin=1), 'CS1_B': Pos(bank=2, nibble=4, pin=1), 'SYS_CLK_P': Pos(bank=2, nibble=8, pin=0), 'SYS_CLK_N': Pos(bank=2, nibble=8, pin=1), 'RESET_N': Pos(bank=2, nibble=8, pin=2)})#

OPTIMUM_X32 flipped (rule 15), on the third and second banks. Every position is where channel 0 of VMK180, VCK190 and VPK120 puts it (VCK190, single rank, has no CS1). Slot k still holds byte k before any byte swap.

Type:

The kit’s layout

assignment_count(layout=PinLayout(nibbles=NibbleLayout(name='Optimum x32, channel 0 (PG313 single channel Figure 2), flipped', pairs={<Channel.A: 'A'>: ((2, 4), (1, 2)), <Channel.B: 'B'>: ((2, 6), (1, 6))}, ac={<Channel.A: 'A'>: frozenset({(2, 0), (2, 1)}), <Channel.B: 'B'>: frozenset({(2, 3), (2, 2)})}, sys_clk_bank=2), slots=(ByteSlot(channel=<Channel.A: 'A'>, bank=1, dqs_nibble=3), ByteSlot(channel=<Channel.A: 'A'>, bank=2, dqs_nibble=5), ByteSlot(channel=<Channel.B: 'B'>, bank=1, dqs_nibble=7), ByteSlot(channel=<Channel.B: 'B'>, bank=2, dqs_nibble=6)), fixed={'CA2_A': Pos(bank=2, nibble=0, pin=0), 'CS1_A': Pos(bank = 2, nibble=0, pin=1), 'CA1_A': Pos(bank = 2, nibble=0, pin=2), 'CA0_A': Pos(bank = 2, nibble=0, pin=3), 'CS0_A': Pos(bank = 2, nibble=0, pin=4), 'CA3_A': Pos(bank = 2, nibble=0, pin=5), 'CK_T_A': Pos(bank = 2, nibble=1, pin=0), 'CK_C_A': Pos(bank = 2, nibble=1, pin=1), 'CA4_A': Pos(bank = 2, nibble=1, pin=2), 'CA5_A': Pos(bank = 2, nibble=1, pin=3), 'CKE_A': Pos(bank = 2, nibble=1, pin=4), 'CA0_B': Pos(bank = 2, nibble=2, pin=2), 'CA3_B': Pos(bank = 2, nibble=2, pin=4), 'CA2_B': Pos(bank = 2, nibble=2, pin=5), 'CK_T_B': Pos(bank = 2, nibble=3, pin=0), 'CK_C_B': Pos(bank = 2, nibble=3, pin=1), 'CA5_B': Pos(bank = 2, nibble=3, pin=2), 'CA4_B': Pos(bank = 2, nibble=3, pin=3), 'CKE_B': Pos(bank = 2, nibble=3, pin=4), 'CA1_B': Pos(bank = 2, nibble=3, pin=5), 'CS0_B': Pos(bank = 2, nibble=7, pin=1), 'CS1_B': Pos(bank = 2, nibble=4, pin=1), 'SYS_CLK_P': Pos(bank = 2, nibble=8, pin=0), 'SYS_CLK_N': Pos(bank = 2, nibble=8, pin=1), 'RESET_N': Pos(bank = 2, nibble=8, pin=2)}))[source]#

Number of distinct pin assignments the layout allows (rules 3 and 4).

Return type:

int

Parameters:

layout (PinLayout)