Getting Started with FPGA: DE10-Lite and Verilog, from Your First Blinking Circuit

🌏 繁體中文版:[學習FPGA] DE10-Lite + Verilog:從長出第一條電路開始

This post is about the "lowest-level" kind of programming there is — not instructions that run on a CPU, but a hardware description language that grows actual circuits. My learning platform is the Terasic DE10-Lite board, and the code examples below are written in Verilog, the hardware description language I used for my own lab work on it.

The Terasic DE10-Lite board
The Terasic DE10-Lite board

FPGA vs CPU: same silicon, two ways of working

A CPU is a general-purpose engine: one chip runs any program by following a stream of instructions. An FPGA (Field Programmable Gate Array) is more like a blank substrate — a grid of tens of thousands of configurable blocks (look-up tables plus flip-flops). Your code is translated into actual circuitry: once you write it, compile it, and program the board, the counter you described physically exists in the silicon. There is no fetch-and-decode step; on every clock cycle, all the circuits run at the same time.

That's why a 50 MHz clock can feed a counter directly. Verilog isn't a "program" in the usual sense; it's a hardware description. You describe the structure of a circuit and what happens on each clock edge, not a list of steps to execute in order.

What's on the DE10-Lite

  • Intel (Altera) MAX 10 FPGA (10M50DAF484C7G): about 50,000 logic elements, plenty for every beginner project
  • Six 7-segment displays (HEX0–HEX5): each one has its own pins and is active-low. They're the most direct way to see values inside your design
  • 10 slide switches (SW), 10 red LEDs (LEDR), 2 push buttons (KEY0, KEY1). The buttons are active-low: they read 0 when pressed
  • 50 MHz clock (MAX10_CLK1_50): the standard clock source for every starter project
  • SDRAM, VGA output, an accelerometer, Arduino-style headers, and a 40-pin GPIO header — material for later projects (framebuffers, UART, video)
The slide switches, LEDs, and push buttons along the bottom edge of the board
The slide switches, LEDs, and push buttons along the bottom edge of the board

The toolchain: Quartus Prime

Intel's Quartus Prime is the "compiler" for this board: Verilog → synthesis → placement & routing → a .sof configuration file → programmed into the FPGA. The free Lite edition supports MAX 10 and is all you need for learning. The workflow is always the same:

  1. Project → Add File: add your .v file and select device 10M50DAF484C7G.
  2. Pin Planner: map each port to a pin on the board — clock, LEDR[9..0], SW, KEY, HEX0–HEX3. (Terasic's System Builder tool can generate a project with all pins pre-assigned, which saves a lot of typing.)
  3. Compilation: run a full compile and check the timing report. 50 MHz means a 20 ns period, and starter designs usually have plenty of margin.
  4. Programmer: program over USB. Only now does your "circuit" actually start running.

Circuit #1: blink an LED

A 50 MHz clock is far too fast to see (50 million cycles per second), so the first step is always a clock divider. Count to 25 million and flip a bit: the bit toggles every 0.5 s, so the LED blinks at 1 Hz, which you can actually see.

module led_blink (
    input  wire       clk,          // 50 MHz (MAX10_CLK1_50)
    output wire [9:0] led           // LEDR
);
    reg [24:0] cnt    = 0;          // 2^25 > 25M, wide enough
    reg        toggle = 0;

    always @(posedge clk) begin
        if (cnt == 25'd24_999_999) begin   // toggle every 25M cycles = every 0.5 s -> 1 Hz blink
            cnt    <= 0;
            toggle <= ~toggle;
        end else begin
            cnt <= cnt + 1'b1;
        end
    end

    assign led = {10{toggle}};
endmodule

Three things to notice. (1) All the logic lives inside always @(posedge clk) and uses non-blocking <= assignments — the basic rule of synchronous design. (2) When the counter hits its limit, it resets to zero and flips a bit in the same clock edge. (3) {10{toggle}} (replication) wires all ten LEDs to the same signal at once. The moment you see ten LEDs blinking in perfect lockstep, the difference between an FPGA and a CPU becomes concrete.

Circuit #2: a counter on the 7-segment displays

This one is a milestone: three functions — a timing tick, a counter, and a display decoder — cooperate in one module, which is the beginning of modular design. The counter advances 10 times per second and shows its value in hex on HEX3–HEX0. Holding KEY0 resets it, and the switches are echoed to the LEDs.

module counter (
    input  wire       clk,          // 50 MHz (MAX10_CLK1_50)
    input  wire [1:0] key,          // KEY[0] = reset, active-low
    input  wire [9:0] sw,
    output wire [9:0] led,
    output wire [6:0] hex0, hex1, hex2, hex3   // segments g..a, active-low
);
    reg [22:0] div   = 0;           // 0 .. 4_999_999
    reg        tick  = 0;           // one-cycle pulse, 10 times per second
    reg [15:0] count = 0;

    // hex digit -> segments (bit 6 = g ... bit 0 = a), 0 = segment on
    function [6:0] to7seg(input [3:0] d);
        case (d)
            4'h0: to7seg = 7'b1000000;
            4'h1: to7seg = 7'b1111001;
            4'h2: to7seg = 7'b0100100;
            4'h3: to7seg = 7'b0110000;
            4'h4: to7seg = 7'b0011001;
            4'h5: to7seg = 7'b0010010;
            4'h6: to7seg = 7'b0000010;
            4'h7: to7seg = 7'b1111000;
            4'h8: to7seg = 7'b0000000;
            4'h9: to7seg = 7'b0010000;
            4'hA: to7seg = 7'b0001000;
            4'hB: to7seg = 7'b0000011;
            4'hC: to7seg = 7'b1000110;
            4'hD: to7seg = 7'b0100001;
            4'hE: to7seg = 7'b0000110;
            default: to7seg = 7'b0001110;   // F
        endcase
    endfunction

    // 10 Hz tick: a clock ENABLE, not a new clock
    always @(posedge clk) begin
        if (div == 23'd4_999_999) begin
            div  <= 0;
            tick <= 1'b1;
        end else begin
            div  <= div + 1'b1;
            tick <= 1'b0;
        end
    end

    // counter: +1 on each tick, KEY0 pressed (= 0) resets
    always @(posedge clk) begin
        if (!key[0])
            count <= 0;
        else if (tick)
            count <= count + 1'b1;
    end

    assign led  = sw;
    assign hex0 = to7seg(count[3:0]);
    assign hex1 = to7seg(count[7:4]);
    assign hex2 = to7seg(count[11:8]);
    assign hex3 = to7seg(count[15:12]);
endmodule

Two design choices are worth calling out. First, the slow "tick" is a clock enable: everything still runs on the one 50 MHz clock, and the counter only advances on cycles where tick is high. Generating a slow signal and using it as a new clock works in simple cases, but it's a classic source of timing problems, so it's a good habit to avoid it from day one. Second, try deriving the 7-segment table yourself: for each digit, work out which of segments a–g light up, then compare with the table above. It sticks much better than memorizing.

On the DE10-Lite each display has its own pins, so no multiplexing is needed. On boards where several digits share segment lines (common on cheaper boards), you'd scan the digits one at a time — the same persistence-of-vision trick as an LED cube.

My own lab results: switches in, numbers out

My lab exercises on this board used the same building blocks: reading the slide switches as a binary input, converting it into a number shown in decimal on the 7-segment displays, and using a push button to toggle the LEDs. The red LEDs above each switch mirror the input bits, so it's easy to check the conversion by eye.

Switch inputs (bottom) converted and shown on the 7-segment displays
Switch inputs (bottom) converted and shown on the 7-segment displays
Another switch setting and its displayed result
Another switch setting and its displayed result

Converting binary to decimal in hardware is a nice next step after the counter above: instead of four hex digits, you split the value into hundreds, tens, and ones (for example with the "double dabble" shift-and-add-3 algorithm) and feed each digit through the same 7-segment decoder.

A learning path, from easy to hard

  1. LED blink → two counters → traffic light (your first finite state machine)
  2. Clock-enable trees → timing constraints
  3. Synchronous vs asynchronous reset (on this board the keys are active-low; getting the polarity backwards doesn't raise an error, it just makes the design misbehave)
  4. VGA output with a framebuffer → your first external interface
  5. Advanced: UART / SPI / I²C → working alongside a microcontroller

Four classic beginner mistakes

  • Writing Verilog like C: Verilog looks like C, but every always block and assign runs concurrently, and sequential C-style thinking can produce combinational loops in hardware. Picture the circuit first, then write the code.
  • Mixing up = and <=: in clocked blocks use non-blocking <=, so every register updates from the values before the clock edge. Blocking = in a clocked block is the most common source of designs that simulate one way and behave another.
  • Logic outside the clock edge: counters, decoders that feed registers, and anything with state belong in an always @(posedge clk) block. Otherwise you get timing violations, and even a design that programs fine will glitch.
  • Forgetting active-low: on the DE10-Lite both the 7-segment segments and the push buttons are active-low. Drive a segment low to light it; a pressed key reads 0. Get it backwards and the displays go dark or show garbage, and beginners often assume the board is broken when it's just polarity.

Wrapping up

From the moment the DE10-Lite powers up, the subject shifts from "writing programs" to "growing circuits". It's a small, sturdy board: get the LED blink and the 7-segment counter working, and everything after that gets much easier.


This post is adapted from the Traditional Chinese original on Vic's Iteration Notes.

Comments

Popular posts from this blog

Gear Ratio Calculator: Multi-Stage Ratio, Output Speed, Torque and Direction

Mecanum-Wheel Pick-and-Place Robot: From Arduino UNO to Mega2560 + ESP32-CAM

Voltage Divider Calculator: Output Voltage, or the Best E24 Resistor Pair for a Target Voltage