Modifying the FPGA project

Project modification can include changing the functionality of the design, adding new features, or fixing bugs. In this guide, we will focus on modifying the existing v0.94 FPGA project to change the behavior of the LED output and adding our own component. These two modifications will help you understand the process of working with FPGA projects and how to adapt them to your needs.


Prerequisites

This example project doesn’t have any specific prerequisites, but familiarity with Verilog and FPGA design concepts will be helpful.

Board model compatibility:

  • All Red Pitaya boards

Based on:

If you are looking for detailed description of the v0.94 FPGA project, click on the link above.

Note

Since Red Pitaya board models have slightly different variations of the v0.94 FPGA project due to the nature of the hardware, the exact code lines mentioned in the tutorial may differ slightly.


Removing extra design source files

In this step, we will remove any unnecessary design source files from the project to keep it clean and organized.

  1. Locate Design Sources: Inside the Sources panel in Vivado, locate the Design Sources directory.

    ../../../_images/Vivado-design-sources.png
  2. Disable Unnecessary Files: As we can see, the Design Sources contains a lot of different Verilog files, of which most are not needed in the v0.94 project (most of the files contain old functionality or alternative functionality of v0.94 project for different board models). The only file tree related to our project is at the top of the list, written in bold, with a small pyramid next to it. Shift Select all the other files. Right-click and select Disable file to “exclude” them from the project.

    ../../../_images/Vivado-design-sources-disable.png
  3. Repeat for Tree-Like Structures: As most of the disabled files have a tree-like file structure, repeat step number 2 until only the red_pitaya_top file tree remains.

    ../../../_images/Vivado-design-sources-disable-step2.png
    ../../../_images/Vivado-design-sources-disable-finish.png

Adding a custom component

To start, open the Vivado project you created in the previous section. If you haven’t created a project yet, please refer to the Creating an FPGA project in Vivado section.

Here, we will focus on adding a custom component/module named red_pitaya_proc to the existing v0.94 FPGA project and reroute the existing signals to it. This will help you understand how to integrate your own designs into the Red Pitaya FPGA project structure. The source and HDL language used to describe the custom component itself is not important as the nature of HDL languages enables us to view each module as a black box with defined input and output ports. As long as the “black box” has the expected ports (matching name, width and direction), it can be integrated into the existing design.

Our custom component will have the following functionality:

  • System bus connection. This will allow the component to communicate with the Red Pitaya system bus and receive commands from the host.

  • ADC. Interface with the ADC to receive input signals from IN1 and IN2 for processing.

  • DAC. Interface with the DAC to send processed output signals to OUT1 and OUT2.

  • GPIOs. Use GPIOs for general-purpose input/output operations.

  • LEDs. Control LEDs for status indication and debugging purposes.

As discussed in v0.94 FPGA project description, the system bus on Red Pitaya is split into eight sections. Generally, we could have connected the custom component to one of the free sections, but for this example, we will be replacing the existing PID component.


Changes to red_pitaya_top

  1. Removing an existing component: Removing an existing component is straightforward - we only have to remove the code that connects the unwanted component to the top module. In the red_pitaya_top.sv file, locate the MIMO PID controller section and comment out the entire section.

    ../../../_images/Vivado-tutorial-mimo-pid.png

    In some projects (for example, v0.94_250 meant for SIGNALlab 250-12), the PID component is already commented out, but there are a few lines of code that ensure the correct functionality of the system bus. Comment out the lines related to system bus, but leave the pid_dat signals intact. If we take a closer look at the signals connected to the PID component, we can already guess which signals will need to be connected to our custom component.

    ../../../_images/Vivado-tutorial-mimo-pid-250.png
    ../../../_images/Vivado-tutorial-mimo-pid-250-comment.png
  2. Add a custom component connection: We will add the custom component connection to the very end on the red_pitaya_top.sv file, just before the final endmodule statement. To start, we can copy over the existing PID component instantiation, which we will slowly modify to match our custom component’s interface.

    red_pitaya_proc #(
        // Generic parameters
    
    )
    i_proc(
        // Signals
        .clk_i          (adc_clk         ), // clock
        .rstn_i         (adc_rstn        ), // reset - active low
    
        // ADC
        .dat_a_in       (                ), // IN 1
        .dat_b_in       (                ), // IN 2
    
        // DAC
        .dat_a_out      (                ), // OUT 1
        .dat_b_out      (                ), // OUT 2
    
        // System bus
        .sys_addr       (sys[3].addr     ), // System address
        .sys_wdata      (sys[3].wdata    ), // Write data
        .sys_wen        (sys[3].wen      ), // Write enable
        .sys_ren        (sys[3].ren      ), // Read enable
        .sys_rdata      (sys[3].rdata    ), // Read data
        .sys_err        (sys[3].err      ), // Error
        .sys_ack        (sys[3].ack      ), // Acknowledge
    );
    

    As there are quite a few changes we need to make, we will go through them step by step.


GPIO and LEDs

By default, the GPIOs and LEDs are connected to the House Keeping module (red_pitaya_hk). We will have to disconnect them from there and connect them to our custom component instead.

../../../_images/Vivado-tutorial-housekeeping.png

The digital pins on the E1 connector are connected directly to the FPGA. Inside the FPGA, they first connect to an input-output buffer, which splits each pin’s signal into three digital signals:

  • Input data

  • Output data

  • Direction control (0 = input, 1 = output)

Furthermore, since there are two rows of GPIO pins, which can be treated as differential pairs (labelled P and N), this results in a grand total of 6 GPIO signals:

  • exp_p_in - input data for the P row

  • exp_p_out - output data for the P row

  • exp_p_dir - direction control for the P row

  • exp_n_in - input data for the N row

  • exp_n_out - output data for the N row

  • exp_n_dir - direction control for the N row

Each of these signals’ widths corresponds to the number of GPIO pins on the E1 connector (usually either 8 or 11). Each bit represents a single GPIO pin in the corresponding row (the LSB is pin 0).

  1. Copy the GPIO and LED signals to custom component: Copy the GPIO and LED signal connections from the House Keeping section and paste them into the custom component instantiation. The GPIOs will be connected to the exp_p_in, exp_p_out, exp_p_dir, exp_n_in, exp_n_out, and exp_n_dir signals, while the LEDs will be connected to the led_o signal. Then comment out the original connections in the House Keeping section.

    ../../../_images/Vivado-tutorial-housekeeping-comment.png
  2. Add generic parameter for GPIO width: We will also add a generic parameter DW (digital width) to determine the width of the GPIO signals. This will allow us to easily adapt the component to different board models with varying numbers of GPIO pins.

    Here is how our custom component instantiation should look like so far:

    red_pitaya_proc #(
        // Generic parameters
        .DW             (DWE             )  // GPIO bus width
    )
    i_proc(
        // Signals
        .clk_i          (adc_clk         ), // clock
        .rstn_i         (adc_rstn        ), // reset - active low
    
        // ADC
        .dat_a_in       (                ), // IN 1
        .dat_b_in       (                ), // IN 2
    
        // DAC
        .dat_a_out      (                ), // OUT 1
        .dat_b_out      (                ), // OUT 2
    
        // GPIO + LED
        .led_o          (led_o           ), // LED output
        .gpio_p_i       (exp_p_in        ), // GPIO P row input
        .gpio_p_o       (exp_p_out       ), // GPIO P row output
        .gpio_p_dir     (exp_p_dir       ), // GPIO P row direction
        .gpio_n_i       (exp_n_in        ), // GPIO N row input
        .gpio_n_o       (exp_n_out       ), // GPIO N row output
        .gpio_n_dir     (exp_n_dir       ), // GPIO N row direction
    
        // System bus
        .sys_addr       (sys[3].addr     ), // System address
        .sys_wdata      (sys[3].wdata    ), // Write data
        .sys_wen        (sys[3].wen      ), // Write enable
        .sys_ren        (sys[3].ren      ), // Read enable
        .sys_rdata      (sys[3].rdata    ), // Read data
        .sys_err        (sys[3].err      ), // Error
        .sys_ack        (sys[3].ack      ), // Acknowledge
    );
    

ADC and DAC

Now we will configure the ADC and DAC connections. The ADC and DAC signals are already defined in the top module, so we just need to connect them to our custom component.

Note

Some Red Pitaya board models (for example STEMlab 125-14 4-Input) have a different number of ADC and DAC channels. The code snippets in this tutorial are based on the 2-channel configuration, but they can be easily adapted to different board models by changing the signal names and widths accordingly. For example, for 4-channel ADC configuration, create 4 input ports in the custom component and connect them to the corresponding ADC signals in the top module.

  1. Add new ADC and DAC busses: First, we will add a new bus for the ADC and DAC signals, which will replace the pid_dat as the output of our component. Add the following lint near the ASG and PID bus declarations (around line 200). At the same time comment out the original PID bus declaration.

    // CUSTOM
    SBG_T [2-1:0]            dac_proc_o;    //! Add Modified DAC signal
    SBA_T [MNA-1:0]          adc_proc_dat;  //! Add Modified ADC signal
    
    // PID
    //SBA_T [2-1:0]            pid_dat;    //! disable PID
    
    ../../../_images/Vivado-tutorial-custom-buses.png
  2. Remove the PID signals: Next, we will remove the existing PID signals and connect the custom DAC bus in their place. This will allow us to send the processed output signals from our custom component to the DAC outputs. Head to the DAC IO section and change the following lines.

    // assign dac_a_sum = asg_dat[0] + pid_dat[0];      //! (disable PID)
    // assign dac_b_sum = asg_dat[1] + pid_dat[1];      //! (disable PID)
    
    assign dac_a_sum = dac_proc_o[0];   // Direct assign, to leave space for PID
    assign dac_b_sum = dac_proc_o[1];
    
    ../../../_images/Vivado-tutorial-reconfigure-dac-sigs.png
  3. Change the ADC (scope) connections: Change the ADC connections in the oscilloscope (rp_scope_com) component to connect to the new ADC bus instead of the original ADC signals. This will allow us to monitor the processed input signals from our custom component using the oscilloscope application.

    // ADC
    .adc_dat_i     ({adc_proc_dat[1], adc_proc_dat[0]}  ),    //! adc_dat
    
    ../../../_images/Vivado-tutorial-reconfigure-scope.png
  4. Connect the custom component: Finally, we can connect the adc_dat (adc_dat_sw for SIGNALlab 250-12), asg_dat and our two custom busses (adc_proc_dat and dac_proc_o) to the custom component.

    ////////////////////////////////////////////////////
    // Custom processing component
    ////////////////////////////////////////////////////
    
    red_pitaya_proc #(
        .DW             (DWE             )  // GPIO bus width
    )
    i_proc(
        // Signals
        .clk_i          (adc_clk         ), // clock
        .rstn_i         (adc_rstn        ), // reset - active low
    
        // ADC
        .adc_a_in       (adc_dat[0]      ), // IN 1 - ADC input
        .adc_b_in       (adc_dat[1]      ), // IN 2 - ADC input
        .adc_a_out      (adc_proc_dat[0] ), // IN 1 - to scope
        .adc_b_out      (adc_proc_dat[1] ), // IN 2 - to scope
    
        // DAC
        .dac_a_in       (asg_dat[0]      ), // OUT 1 - from signal generator (ASG)
        .dac_b_in       (asg_dat[1]      ), // OUT 2 - from signal generator (ASG)
        .dac_a_out      (dac_proc_o[0]   ), // OUT 1 - DAC output
        .dac_b_out      (dac_proc_o[1]   ), // OUT 2 - DAC output
    
        // GPIO + LED
        .led_o          (led_o           ), // LED output
        .gpio_p_in      (exp_p_in        ), // GPIO P row input
        .gpio_p_out     (exp_p_out       ), // GPIO P row output
        .gpio_p_dir     (exp_p_dir       ), // GPIO P row direction
        .gpio_n_in      (exp_n_in        ), // GPIO N row input
        .gpio_n_out     (exp_n_out       ), // GPIO N row output
        .gpio_n_dir     (exp_n_dir       ), // GPIO N row direction
    
        // System bus
        .sys_addr       (sys[3].addr     ), // System address
        .sys_wdata      (sys[3].wdata    ), // Write data
        .sys_wen        (sys[3].wen      ), // Write enable
        .sys_ren        (sys[3].ren      ), // Read enable
        .sys_rdata      (sys[3].rdata    ), // Read data
        .sys_err        (sys[3].err      ), // Error
        .sys_ack        (sys[3].ack      )  // Acknowledge
    );
    

    Note

    • Since we disconnected the GPIOs and LEDs from the House Keeping module, SCPI and API commands to control them will not work. It is possible to add custom registers for GPIO and LED control in the custom component (covered in a different tutorial).

    • The DAC signal from the custom component is summed with the output of the existing signal generator (if enabled). If you want to use only the custom component’s DAC output, you can modify the DAC IO section to remove the signal generator’s contribution or exclude the ASG component from the design.

  5. Confirm unknown component: After we save the changes to the red_pitaya_top.sv file, we will see Vivado add an unknown component to the Design Sources panel. This is because we haven’t created the custom component yet.

    ../../../_images/Vivado-tutorial-custom-component-new.png

Designing a new custom component

Due to the “black box” nature of HDL languages, we can write the custom component in any HDL language (Verilog, VHDL, etc.) and it will work as long as the input and output ports match the expected names, widths, and directions. We will create both a Verilog and a VHDL version of the custom component.

  1. Add a new Design Source: To add a new Design Source to the project, either click the + button in the Sources panel or right-click on the Design Sources directory and select Add Sources.

    ../../../_images/Vivado-tutorial-custom-add-sources.png
  2. As we are adding a new module, we need to select Add or create design sources.

    ../../../_images/Vivado-tutorial-custom-add-sources-panel.png

    If we already have the HDL code for the custom component, we can select Add Files and browse to the file location. In this case, we will create a new file, so select Create File.

    ../../../_images/Vivado-tutorial-custom-create-file.png
  3. Select the File type: Select the File type (Verilog or VHDL) and enter the File name (red_pitaya_proc). Click OK to create the new file.

    ../../../_images/Vivado-tutorial-custom-create-file-name.png
  4. Finish adding the new file: The new file will be added to the list of files to be added to the project. Click Finish to complete the process.

    ../../../_images/Vivado-tutorial-custom-create-file-finish.png
  5. Define the Module: As soon as the file is created, another window will pop up, asking for the Module definition parameters like entity name, architecture name and I/O ports. We will click OK as it is much easier to edit the ports in the code editor directly.

    ../../../_images/Vivado-tutorial-custom-define-module.png
  6. Confirm Module Definition: Click Yes.

    ../../../_images/Vivado-tutorial-custom-define-module-finish.png
  7. Open the new file: After Vivado finishes reloading, we will see the new file in the Design Sources panel. Double-click the file name to open it in the editor.

    ../../../_images/Vivado-tutorial-component-inserted.png
  8. Copy the code: Now, copy the following code into the file. We will go through its functionality step by step.

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;
    
    entity red_pitaya_proc is
    generic(
      DW                      :       integer := 8                        -- GPIO width
    );
    port (
      clk_i                   : in    std_logic;
      rstn_i                  : in    std_logic;                          -- bus reset - active low
    
      sys_addr                : in    std_logic_vector(31 downto 0);      -- bus address
      sys_wdata               : in    std_logic_vector(31 downto 0);      -- bus write data
      sys_wen                 : in    std_logic;                          -- bus write enable
      sys_ren                 : in    std_logic;                          -- bus read enable
      sys_rdata               : out   std_logic_vector(31 downto 0);      -- bus read data
      sys_err                 : out   std_logic;                          -- bus error indicator
      sys_ack                 : out   std_logic;                          -- bus acknowledge signal
    
      adc_a_in, adc_b_in      : in    signed(13 downto 0);                -- ADC 1 & 2 input
      adc_a_out, adc_b_out    : out   signed(13 downto 0);                -- to scope
    
      dac_a_in, dac_b_in      : in    signed(13 downto 0);                -- from signal generator (ASG)
      dac_a_out, dac_b_out    : out   signed(13 downto 0);                -- DAC 1 & 2 output
    
      led_o                   : out   std_logic_vector(   7 downto 0);    -- LED output
      gpio_p_in, gpio_n_in    : in    std_logic_vector(DW-1 downto 0);    -- GPIO input data
      gpio_p_out, gpio_n_out  : out   std_logic_vector(DW-1 downto 0);    -- GPIO output data
      gpio_p_dir, gpio_n_dir  : out   std_logic_vector(DW-1 downto 0)     -- GPIO direction
    );
    end red_pitaya_proc;
    
    architecture RTL of red_pitaya_proc is
    
    -- ### SIGNALS ### --
    constant ZERO           : std_logic_vector(31 downto 0) := (others => '0');         -- Padding registers
    constant ID_REG         : std_logic_vector(31 downto 0) := X"FEEDBACC";             -- ID register
    
    -- LED & GPIO --
    
    signal diop_in,  dion_in    : std_logic_vector(DW-1 downto 0);                      -- GPIO input
    signal diop_out, dion_out   : std_logic_vector(DW-1 downto 0) := (others => '0');   -- GPIO output
    signal diop_dir, dion_dir   : std_logic_vector(DW-1 downto 0) := (others => '0');   -- Direction in == 0, out == 1
    
    signal led                  : std_logic_vector(   7 downto 0) := (others => '0');   -- LED status
    
    begin
    
    -- ### Registers, write & control logic ### --
    -- Red Pitaya core clock frequency
    pbus: process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rstn_i = '0' then
          -- LED & GPIO --
          diop_dir <= (others => '0');
          dion_dir <= (others => '0');
          diop_out <= (others => '0');
          dion_out <= (others => '0');
          led <= (others => '0');
    
        else
          sys_ack <= sys_wen or sys_ren;                      -- acknowledge transactions
    
          -- decode address & write registers
          if sys_wen='1' then
            if    sys_addr(19 downto 0) = X"00010" then
              diop_dir <= sys_wdata(DW-1 downto 0);           -- Change direction P
    
            elsif sys_addr(19 downto 0) = X"00014" then
              dion_dir <= sys_wdata(DW-1 downto 0);           -- Change direction N
    
            elsif sys_addr(19 downto 0) = X"00018" then
              diop_out <= sys_wdata(DW-1 downto 0);           -- Change output P
    
            elsif sys_addr(19 downto 0) = X"0001C" then
              dion_out <= sys_wdata(DW-1 downto 0);           -- Change output N
    
            elsif sys_addr(19 downto 0) = X"00030" then
              led <= sys_wdata(7 downto 0);                   -- Change LEDs
    
            end if;
          end if;
        end if;
      end if;
    end process pbus;
    
    sys_err <= '0';
    
    -- ADC & DAC processing --
    adc_a_out <= adc_a_in;
    adc_b_out <= adc_b_in;
    dac_a_out <= dac_a_in;
    dac_b_out <= dac_b_in;
    
    -- GPIO I/O assignment --
    gpio_p_out <= diop_out;
    gpio_n_out <= dion_out;
    gpio_p_dir <= diop_dir;
    gpio_n_dir <= dion_dir;
    diop_in <= gpio_p_in;
    dion_in <= gpio_n_in;
    
    -- LED output assignment --
    led_o <= led;
    
    
    -- ### Decode address & read data ### --
    with sys_addr(19 downto 0) select
      sys_rdata <=                          ID_REG              when x"00000",      -- ID register
                  ZERO(32-1 downto DW)    & diop_dir            when x"00010",      -- GPIO P direction
                  ZERO(32-1 downto DW)    & dion_dir            when x"00014",      -- GPIO N direction
                  ZERO(32-1 downto DW)    & diop_out            when x"00018",      -- GPIO P output
                  ZERO(32-1 downto DW)    & dion_out            when x"0001C",      -- GPIO N output
                  ZERO(32-1 downto DW)    & diop_in             when x"00020",      -- GPIO P inputs
                  ZERO(32-1 downto DW)    & dion_in             when x"00024",      -- GPIO N inputs
                  ZERO(32-1 downto  8)    & led                 when x"00030",      -- LEDs
                  ZERO when others;
    
    end RTL;
    

The code of the custom component is relatively simple and has the following functionality:

  • ID register.

  • LED and GPIO control via the system bus.

  • Passthrough of ADC and DAC signals (input signals are sent to the scope, output signals are sent to the DAC).

As discussed in v0.94 FPGA project description, the system bus on Red Pitaya is split into eight sections. Each section has its own 20 bit address space. The custom component is connected to section 3 of the system bus, which has the base address 0x40300000. The upper twelve bits determine the section (0x403 for section 3), while the lower 20 bits are used for addressing registers inside the component. The custom component has the following registers:

  • 0x00000: ID register

  • 0x00010: Digital output direction (diop_dir)

  • 0x00014: Digital input direction (dion_dir)

  • 0x00018: Digital output data (diop_out)

  • 0x0001C: Digital input data (dion_out)

  • 0x00030: LED control (led)

The easiest way to interact with the custom FPGA registers from inside Linux OS is through the monitor command line tool.


  1. Save the changes: As soon as we save the changes, Vivado will update the project; our custom module red_pitaya_proc replaces the red_pitaya_pid module, which is now outside the top module.

    ../../../_images/Vivado-tutorial-custom-component-finish.png
  2. Proceed with Synthesis, Implementation, and Bitstream Generation as described in the Simple LED blink example above.

In the next chapter we will show how to change the FPGA image on the Red Pitaya board.