top of page
Search

UberDDR3 + OpenXC7: Open-Source DDR3 Controller Meets Open-Source FPGA Toolchain - Post #14

  • Writer: Angelo Jacobo
    Angelo Jacobo
  • Mar 21
  • 8 min read

Open-source DDR3 controller meets open-source FPGA toolchain!


UberDDR3, the open-source DDR3 controller, now works with OpenXC7, the open-source FPGA toolchain — bringing fully open-source synthesis, place & route, and bitstream generation into the mix. No more Vivado — just run make and let the open-source magic happen!


In this blog post, I dive into what UberDDR3 and OpenXC7 bring to the table, how to set up OpenXC7 and configure the Makefile, and how to run the UberDDR3 example demo.


And yes, we put it to the test! The demo runs on real hardware across multiple AMD 7-series FPGAs—from Spartan-7 to Kintex-7—with DDR3 widths ranging from x16 to x64, spanning boards from Digilent, Enclustra, Alinx, and QMTech!

Table of Contents:


I. What is UberDDR3 and OpenXC7?

UberDDR3 is an open-source DDR3 memory controller designed to be a lightweight, high-performance alternative to vendor-provided controllers—like AMD Vivado’s Memory Interface Generator (MIG). Originally, UberDDR3 was built for the open-source 10Gb Ethernet Switch project. Later, it received funding from NLNet, where it officially got the name UberDDR3. That project started back in March 2024.


Oh hey, it's March 2025 — UberDDR3 just turned one year old! 🎉


I.I A Year of Growth: UberDDR3's Features

Over the past year, UberDDR3 has grown significantly. If you’ve been following my blog posts—thank you! If not, hit that subscribe via email button at the end of this post or follow me on LinkedIn to stay updated. Here’s where UberDDR3 stands today:

WishBone and AXI4-compliant user interfaces

Supports all DDR3 speed grades: DDR3-666, DDR3-800, DDR3-1333, DDR3-1600

Integrates into Vivado Block Diagram for easy drag-and-drop use

Optional Error Correction Coding (SECDED) even for non-ECC DDR3

Optional Self-refresh mode for lower power consumption during idle periods

Optional simple thermal management via external XADC

Optional Built-in Self Test (BIST) for debugging

Supports dual-rank DIMMs


The Open IP Hub Blog Posts section of the UberDDR3 repo elaborates on the blog post links for each feature updates.


In my previous blog post UberDDR3 + MicroBlaze (Part 2), I showed how UberDDR3 outperforms MIG with slightly higher DMIPS/MHz and achieves 32% lower hardware utilization! 🚀


The next new update for UberDDR3 is that ... it now works with the opensource FPGA toolchain, OpenXC7!


I.II The Latest Upgrade: UberDDR3 Now Works with OpenXC7!

OpenXC7 is an open-source FPGA toolchain for AMD/Xilinx Series 7 devices—including Artix-7, Kintex-7, Spartan-7, and Zynq-7000. It builds on existing open-source FPGA tools like Yosys (for synthesis) and nextpnr (for place-and-route), allowing you to synthesize, PnR, and generate bitstreams without needing Vivado.


Fun fact: OpenXC7 is also an NLNet-funded project! Big shoutout to the NLNet team for supporting open-source FPGA development! 🙌


With UberDDR3 + OpenXc7, you can now build a fully open-source DDR3-enabled FPGA design without touching Vivado!


II. Steps To Run OpenXC7

Now, let’s get straight to running OpenXC7. We’ll go from installing the toolchain to preparing the Makefiles and finally running it.


II.I Installing OpenXC7

Based on the OpenXC7 repository, it has two ways to install it:


On my end, I found that the Nix package manager method worked best. I ran into some conflicts when using the Snap-based installation. That said, try both methods and see which one works for you.


I personally prefer the Nix-based method because it creates an isolated development environment specifically for OpenXC7 tools and dependencies. This means I didn’t have to worry about conflicts with my already installed Yosys and nextpnr tools.


II.II Configuring the Makefile

Running OpenXc7 is as simple as executing make. To set up the Makefile, you can use the templates available in the example_demo directory of UberDDR3 repository.


At the top of these Makefiles, you'll find key variables that need modification for your specific FPGA setup:

  • PROJECT → The top-level file name of your project (without the .v extension).

  • FAMILY → The AMD 7-series FPGA family you’re using: spartan7, artix7, or kintex7.

  • PART → The specific part number of your FPGA. If unsure, check Vivado’s project creation window.

  • CHIPDB → The chip database required by nextpnr. Options: SPARTAN7_CHIPDB, ARTIX7_CHIPDB, or KINTEX7_CHIPDB.

  • ADDITIONAL_SOURCES → A list of other Verilog source files required for your project.


To give you an idea of how to configure the Makefile, here are examples from UberDDR3 demo projects for different 7-series FPGA families:

II.III Running OpenXC7

Once your Makefile is set up, running OpenXC7 is as simple as executing make. This will handle everything — from synthesis to place-and-route to bitstream generation.


Since I installed OpenXC7 using Nix, I first need to enter the development environment by running:

  • nix develop github:openxc7/toolchain-nix

Then, I can simply run:

  • make

Here’s the final output from running make for the ArtyS7 demo project:

As shown above, the generated bitstream file is named arty_ddr3.bit.


Now, to program your FPGA with this bitfile, simply run:

  • make program


If everything goes well, you should see a DONE message, indicating successful programming:


III. Example Demo Design Explanation

In this section, I will elaborate on the example demo design for UberDDR3. First, clone the UberDDR3 repository:

The example demo for various FPGA boards are in the example_demo/ directory.


Each example follows a simple structure. Let's break it down using the Arty S7 demo project.


As shown above, the top-level port connections include the 100MHz clock (i_clk), active-high reset (i_rst), DDR3 I/O interface, UART line (rx and tx), and LEDs.


Here's a breakdown of each component's role within the design:

  • LEDs

    The four LEDs turn on once UberDDR3 completes calibration and the Built-in Self-Test (BIST) runs successfully. The BIST involves writing and reading across the entire memory address space in multiple ways, including burst write/read, random write/read, and alternating write/read.

    The code snippet below shows the logic for the LEDs, where state number 23 represents DONE_CALIBRATE:

  • UART

    The uart_rx and uart_tx modules handle UART communication by receiving and transmitting data. The rd_data signal from uart_rx represents the serial data retrieved from the RX line, while o_wb_data from UberDDR3 is transmitted serially via the TX line through the uart_tx module.

    As shown in the code snippet below, the UART operates at a baud rate of 9600.


  • Main Logic

    The core logic is straightforward:

    • If the UART receives lowercase letters (ASCII decimal values 97 to 122, corresponding to 'a' to 'z'), these characters are written to DDR3 memory.

    • If it receives uppercase letters (ASCII decimal values 65 to 90, corresponding to 'A' to 'Z'), it reads back the previously stored lowercase letters from the corresponding DDR3 addresses.


For example, sending "abcdefg" via the UART terminal stores these letters in DDR3. If you then send "ABCDEFG", it retrieves and returns the stored lowercase equivalent: "abcdefg". Simple and effective!


The remaining components are the PLL and the DDR3 top instantiation:

  • PLL

    Since we're using an open-source FPGA toolchain, there's no need for the Clock Wizard IP—ditch it! Instead, you can use the clk_wiz.v template for your projects, which is built around the PLLE2_ADV primitive. Configuring its parameters may seem tricky at first, but this quick guide should help:

    • CLKFBOUT_MULT = This is the multiplication factor applied before any division. In this example, the source clock is 100 MHz, so we multiply it by 10 to get 1000 MHz.

    • CLKOUT0_DIVIDE = This output is for the controller clock, which we want at 83.333 MHz (the lowest valid controller clock). Setting this divide factor to 12 results in 1000 MHz / 12 = 83.333 MHz.

    • CLKOUT1_DIVIDE = This output is for the DDR3 clock, which we want at 333.333 MHz (the lowest valid DDR3 clock). Setting the divide factor to 3 results in 1000 MHz / 3 = 333.333 MHz.

    • CLKOUT2_DIVIDE =  This output is for the reference clock of IODELAYCTRL, used by the IODelay primitives in the UberDDR3 PHY. This clock must always be 200 MHz, so we set the divide factor to 5, resulting in 1000 MHz / 5 = 200 MHz.

    • CLKOUT3_DIVIDE = This output is for the 90-degree phase-shifted DDR3 clock, which should also be 333.333 MHz. The divide factor remains 3, resulting in 1000 MHz / 3 = 333.333 MHz.

    • CLKOUT3_PHASE = This is where we set that the CLOCKOUT3 phase which must be 90 degree phase shifted.

    • CLKIN1_PERIOD = This is the period of the source clock. For the Arty S7, the source clock is 100 MHz, which translates to 10 ns.

    Here is the code snippet of the clk_wiz.v:


This module is then instantiated in the top-level file of the demo:



IV. Example Demo with OpenXC7

Now, let's see UberDDR3 and OpenXC7 in action! We’ll run the example demo on hardware using the six FPGA boards shown below:


The videos below walk through the entire process:

  1. Running make with OpenXC7 to build the design

  2. Using make program to load the bitfile onto the FPGA

  3. Testing UART functionality

  4. Verifying the Built-In Self-Test (BIST) of UberDDR3, indicated by the LEDs turning on after bitfile loading.








Note on UART Behavior:

You might notice that when sending data via UART, the received lowercase letters don’t always match the number of uppercase letters sent. For example, if you send:

  • aaaaAAAAbbbbbBBBBBcccccCCCCCddddDDDD

You might expect to receive four of each letter:

  • aaaabbbbcccccdddd

However, when running the actual example demo, you may only get something like:

  • aaabbccd


This behavior isn't due to OpenXC7 — you’ll observe the same issue even when synthesizing the example demo in Vivado. The reason is that the current UART implementation does not include any FIFO for transmitting and receiving data.


As a result, some data gets lost due to overflow before reaching UberDDR3. For now, the example demo keeps the design as simple as possible by omitting FIFO buffers.


V. Limitations of OpenXC7

OpenXC7 relies on nextpnr for the place-and-route (PNR) stage. However, nextpnr's timing-driven PNR capabilities are still limited, making it difficult to run complex designs at high frequencies. This is why, in all the example demos, the DDR3 clock is set to the lowest valid frequency of 333 MHz, as specified in the JEDEC documentation.


At the moment, running UberDDR3 using OpenXC7 at higher speeds — such as 400 MHz (DDR3-800) or above — is not guaranteed. For instance, the Enclustra KX2-ST1 FPGA board is normally capable of running UberDDR3 at 800 MHz (DDR3-1600) using Vivado. However, when using OpenXC7, the Built-In Self-Test (BIST) only passes at 333 MHz.


That said, this is likely just a temporary limitation. The open-source FPGA toolchain continues to evolve, with many brilliant minds actively working to improve it over time. It’s exciting to see how far it will progress!


VI. Conclusion

With UberDDR3 now working with OpenXC7, we’re stepping into a fully open-source FPGA workflow—from synthesis to bitstream generation—without relying on Vivado. This marks a big milestone for open hardware development, making DDR3 memory controllers more accessible to the community.


By following this blog post, you can set up OpenXC7, configure the Makefile, and run your own UberDDR3 projects on Spartan-7, Artix-7, and Kintex-7 FPGAs. While OpenXC7 still has some limitations, it's a promising step toward breaking free from proprietary toolchains.


Got questions, feedback, or ideas? Drop a comment below or reach out on LinkedIn. And if you haven't already, subscribe to stay updated on more UberDDR3 developments.

That wraps up this post. Catch you in the next blog post!






 
 
 

Comentários


Computer Processor

Subscribe to Our Newsletter

Thanks for submitting!

SUBSCRIBE VIA EMAIL

  • LinkedIn
  • GitHub
  • Youtube

Thanks for submitting!

© 2024 by Angelo Jacobo

bottom of page