Building our First Agilex 3 Application
- Adam Taylor
- 13 minutes ago
- 6 min read
On our journey of Agilex 3 exploration to date we have provided an overview of the Agilex 3 family and examined key aspects we need to understand to design in an Agilex 3 FPGA as a chip-down design.
Now it is time to start looking at how we develop applications for the Agilex 3 on the Terasic A3 Nano.
Let's start with a look at the toolchain. For this development we will be using Quartus Prime 25.1. Within Quartus Prime we will find not only the synthesis and implementation tools but also several other tools which significantly aid development, including:
Platform Designer – System integration tool which enables system integration based around both Altera and Custom IP.
Signal Tap Logic Analyzer – In-chip logic debugging which enables analysis of the design at run-time.
Questa Altera FPGA Edition – A version of the popular Questa Simulator specifically optimised for Altera devices.
To get started creating an application, the first step is to download and install Quartus 25.1 from the Altera web site. The installation tool enables us to customise exactly the installation that we desire on our system.
For this installation I will be installing Quartus Prime Pro, Agilex 3, Agilex 5 and Cyclone 10 GX devices along with the Power and Thermal Calculator and Quartus programmer. I will not be installing the Questa simulator as I already have a separate full license for Questa. On my machine this installed in a little under an hour including the download.

With Quartus installed I wanted to get started on a simple design. I like visual demonstrations so instead of blinking a LED I decided that I would write a VGA test pattern and output it over the TMDS ports using a Pmod VGA. I know the board has an HDMI and that is something I want to explore in a later blog, but for now, I wanted a simple solution to pipe-clean the process.
You can write a simple VGA test pattern with a couple of counters and counter decoding for the test pattern. I opted for an 800 pixel by 600 line image at 72Hz as this uses a 50 MHz pixel clock. It just happens the clocks on the A3 Nano are 50 MHz which makes such a solution nice and simple.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- 50.000 MHz pixel clock in, VGA 800x600@72 out (12-bit RGB + HS/VS)
entity vga_test_800x600_50mhz is
port (
i_clk_50 : in std_logic; -- 50.000 MHz pixel clock
--i_resetn : in std_logic; -- active-low reset
o_vga_r : out std_logic_vector(3 downto 0);
o_vga_g : out std_logic_vector(3 downto 0);
o_vga_b : out std_logic_vector(3 downto 0);
o_vga_hs : out std_logic; -- HS positive
o_vga_vs : out std_logic -- VS positive
);
end entity;
architecture rtl of vga_test_800x600_50mhz is
------------------------------------------------------------------------
-- VESA SVGA 800x600 @ 72 Hz timing @ 50.000 MHz pixel clock
-- H: 800 active, 56 fp, 120 sync, 64 bp -> 1040 total (fH ≈ 48.077 kHz)
-- V: 600 active, 37 fp, 6 sync, 23 bp -> 666 total (fV ≈ 72.189 Hz)
------------------------------------------------------------------------
constant c_H_ACTIVE : integer := 800;
constant c_H_FP : integer := 56;
constant c_H_SYNC : integer := 120;
constant c_H_BP : integer := 64;
constant c_H_TOTAL : integer := c_H_ACTIVE + c_H_FP + c_H_SYNC + c_H_BP; -- 1040
constant c_V_ACTIVE : integer := 600;
constant c_V_FP : integer := 37;
constant c_V_SYNC : integer := 6;
constant c_V_BP : integer := 23;
constant c_V_TOTAL : integer := c_V_ACTIVE + c_V_FP + c_V_SYNC + c_V_BP; -- 666
constant c_HS_POL_POS : boolean := true;
constant c_VS_POL_POS : boolean := true;
-- Counters (0..1039) and (0..665)
signal s_h_cnt : unsigned(10 downto 0) := (others => '0'); -- 11 bits
signal s_v_cnt : unsigned(9 downto 0) := (others => '0'); -- 10 bits
signal s_active_video : std_logic;
-- Syncs
signal s_hs, s_vs : std_logic;
-- Colors (4:4:4)
signal s_r, s_g, s_b : std_logic_vector(3 downto 0);
begin
------------------------------------------------------------------------
-- H/V counters
------------------------------------------------------------------------
p_counters : process(i_clk_50)
begin
if rising_edge(i_clk_50) then
if s_h_cnt = to_unsigned(c_H_TOTAL-1, s_h_cnt'length) then
s_h_cnt <= (others => '0');
if s_v_cnt = to_unsigned(c_V_TOTAL-1, s_v_cnt'length) then
s_v_cnt <= (others => '0');
else
s_v_cnt <= s_v_cnt + 1;
end if;
else
s_h_cnt <= s_h_cnt + 1;
end if;
end if;
end process;
s_active_video <= '1' when (s_h_cnt < to_unsigned(c_H_ACTIVE, s_h_cnt'length) and s_v_cnt < to_unsigned(c_V_ACTIVE, s_v_cnt'length))
else '0';
------------------------------------------------------------------------
-- Sync generation (registered)
------------------------------------------------------------------------
p_syncs : process(i_clk_50)
variable v_h : integer;
variable v_v : integer;
variable v_hs_pulse, v_vs_pulse : std_logic;
begin
if rising_edge(i_clk_50) then
v_h := to_integer(s_h_cnt);
v_v := to_integer(s_v_cnt);
v_hs_pulse := '1' when (v_h >= c_H_ACTIVE + c_H_FP) and
(v_h < c_H_ACTIVE + c_H_FP + c_H_SYNC) else '0';
v_vs_pulse := '1' when (v_v >= c_V_ACTIVE + c_V_FP) and
(v_v < c_V_ACTIVE + c_V_FP + c_V_SYNC) else '0';
s_hs <= v_hs_pulse when c_HS_POL_POS else not v_hs_pulse;
s_vs <= v_vs_pulse when c_VS_POL_POS else not v_vs_pulse;
end if;
end process;
o_vga_hs <= s_hs;
o_vga_vs <= s_vs;
------------------------------------------------------------------------
-- Test pattern (12-bit RGB)
-- Top 0..199: Red gradient across X
-- Middle 200..399: 8 vertical color bars (100 px each)
-- Bottom 400..599: 32×32 checkerboard (bit toggles)
------------------------------------------------------------------------
p_pattern : process(i_clk_50)
variable v_x, v_y : integer;
begin
if rising_edge(i_clk_50) then
if s_active_video = '1' then
v_x := to_integer(s_h_cnt);
v_y := to_integer(s_v_cnt);
if v_y < 200 then
-- Red gradient using s_h_cnt[9:6] -> 16 steps across width
s_r <= std_logic_vector(s_h_cnt(9 downto 6));
s_g <= (others => '0');
s_b <= (others => '0');
elsif v_y < 400 then
-- 8 vertical color bars (100 px each across 800)
if v_x < 100 then s_r <= x"F"; s_g <= x"F"; s_b <= x"F";
elsif v_x < 200 then s_r <= x"F"; s_g <= x"F"; s_b <= x"0";
elsif v_x < 300 then s_r <= x"0"; s_g <= x"F"; s_b <= x"F";
elsif v_x < 400 then s_r <= x"0"; s_g <= x"F"; s_b <= x"0";
elsif v_x < 500 then s_r <= x"F"; s_g <= x"0"; s_b <= x"F";
elsif v_x < 600 then s_r <= x"F"; s_g <= x"0"; s_b <= x"0";
elsif v_x < 700 then s_r <= x"0"; s_g <= x"0"; s_b <= x"F";
else s_r <= x"0"; s_g <= x"0"; s_b <= x"0";
end if;
else
-- Checkerboard: toggle every 32 px using bit 5 of counters
if (s_h_cnt(5) xor s_v_cnt(5)) = '0' then
s_r <= x"8"; s_g <= x"8"; s_b <= x"8"; -- light gray
else
s_r <= x"2"; s_g <= x"2"; s_b <= x"2"; -- dark gray
end if;
end if;
-- Optional 1-pixel white border
if (v_x = 0) or (v_x = c_H_ACTIVE-1) or (v_y = 0) or
(v_y = c_V_ACTIVE-1) then
s_r <= x"F"; s_g <= x"F"; s_b <= x"F";
end if;
else
s_r <= (others => '0');
s_g <= (others => '0');
s_b <= (others => '0');
end if;
end if;
end process;
o_vga_r <= s_r;
o_vga_g <= s_g;
o_vga_b <= s_b;
end architecture;
With the code written, the next step is to open Quartus 25.1 and create a new project. Opening Quartus for the first time will walk you through the licensing process to obtain the free Agilex 3 License.



With a license available, Quartus will open and we are able to get started with our first project. The first element of the new project creation wizard allows us to enter a project location and name for the project to be created.

The next step is to select the component to be used for the project. On the A3 Nano board we have the A3CZ135BB18AE7S device.

The final step of the project creation process is to add in any source code files we have created, e.g., our VGA RTL file. Leave the tool settings unchanged and you will see a summary of the project to be created. Click finish.

As we have a project created with an RTL file, we are then able to compile the design and open the pin planner to assign the I/O to the pins on the FPGA. In this case it is pretty straightforward. We are able to assign the pin locations and I/O standards.

The final thing we need to do is define the constraints for the FPGA design. For this one it is straightforward; we only have one clock at 50MHz. We can create an SDC file and add in the constraints necessary.
create_clock -name sys_clk -period 20.000 [get_ports {i_clk_50}]
We are now able to implement the design and generate the programming file.

Once the design has completed its implementation, we can open Timing Analyzer and ensure there is timing closure. This is a pretty simple design so we should be able to close timing.

All that remains now is to try the design on the actual board and with a VGA display connected.
Running this shows the resultant image below.


We can see this simple design and project flow worked as intended. In our next blog we are going to explore the integration of the HDMI/DVI display and examine how we can look and debug in our FPGA so that we understand the flow when we get to more complex projects.
UK FPGA Conference
FPGA Horizons - October 7th 2025 - THE FPGA Conference, find out more here.
Sponsored by Altera