Stapsgewijze Handleiding: Je Eerste FPGA LED-knipperder Bouwen
Stapsgewijze Handleiding: Je Eerste FPGA LED-knipperder Bouwen
Deel 1: VHDL of Verilog Ontwerpen
In deze leer leer je hoe je VHDL- en Verilog-code schrijft die een LED aanstuurt op een door de gebruiker gedefinieerde frequentie. Kies de taal die het beste past bij je workflow.
Bij het schrijven van HDL moet je verifiëren dat het ontwerp zich gedraagt zoals bedoeld. Fouten zijn onvermijdelijk, dus simulatie is onmisbaar. Deze tutorial is verdeeld in twee cruciale fasen:
- HDL-ontwerp
- HDL-simulatie
Het overslaan van simulatie kan leiden tot dure debugging op de hardware. Behandel simulatie als een verplichte controlepunt.
Projectvereisten
Schrijf HDL dat een LED laat knipperen op 100 Hz, 50 Hz, 10 Hz of 1 Hz met een duty cycle van 50%. Twee schakelaars selecteren de gewenste frequentie, en een extra LED_EN-schakelaar moet hoog zijn om de LED in te schakelen. De FPGA draait op een 25 MHz-oscillator.
Waarheidstabel voor de frequentieselector:
| Enable | Schakelaar 1 | Schakelaar 2 | LED-aanstuurfrequentie |
|---|---|---|---|
| 0 | – | – | uitgeschakeld |
| 1 | 0 | 0 | 100 Hz |
| 1 | 0 | 1 | 50 Hz |
| 1 | 1 | 0 | 10 Hz |
| 1 | 1 | 1 | 1 Hz |
Signaaloverzicht:
| Signaalnaam | Richting | Beschrijving |
|---|---|---|
| i_clock | Invoer | 25 MHz klok |
| i_enable | Invoer | Inschakelschakelaar (logica 0 = LED uit) |
| i_switch_1 | Invoer | Frequentieselectorschakelaar 1 |
| i_switch_2 | Invoer | Frequentieselectorschakelaar 2 |
| o_led_drive | Uitvoer | LED-aanstuursignaal |
Vier gelijktijdige tellerprocessen monitoren de 25 MHz-klok en genereren toggles voor elke doelfrequentie. Zelfs wanneer een bepaalde frequentie niet geselecteerd is, blijft de teller lopen – een kernprincipe van hardware-parallelisme.
De schakelaars vormen een multiplexer die de gekozen toggle doorstuurt naar de LED-uitgang. Multiplexers zijn puur combinatorische logica, dus ze werken zonder klok.
Hieronder is een blokdiagram dat de architectuur illustreert:

VHDL-implementatie
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tutorial_led_blink is
port (
i_clock : in std_logic;
i_enable : in std_logic;
i_switch_1 : in std_logic;
i_switch_2 : in std_logic;
o_led_drive : out std_logic
);
end tutorial_led_blink;
architecture rtl of tutorial_led_blink is
-- Constanten om de benodigde frequenties te creëren:
-- Formule is: (25 MHz / 100 Hz * 50% duty cycle)
-- Dus voor 100 Hz: 25.000.000 / 100 * 0.5 = 125.000
constant c_CNT_100HZ : natural := 125000;
constant c_CNT_50HZ : natural := 250000;
constant c_CNT_10HZ : natural := 1250000;
constant c_CNT_1HZ : natural := 12500000;
-- Deze signalen zijn de tellers:
signal r_CNT_100HZ : natural range 0 to c_CNT_100HZ;
signal r_CNT_50HZ : natural range 0 to c_CNT_50HZ;
signal r_CNT_10HZ : natural range 0 to c_CNT_10HZ;
signal r_CNT_1HZ : natural range 0 to c_CNT_1HZ;
-- Deze signalen togglen op de benodigde frequenties:
signal r_TOGGLE_100HZ : std_logic := '0';
signal r_TOGGLE_50HZ : std_logic := '0';
signal r_TOGGLE_10HZ : std_logic := '0';
signal r_TOGGLE_1HZ : std_logic := '0';
-- Eén bit select-draad.
signal w_LED_SELECT : std_logic;
begin
-- Alle processen togglen een specifiek signaal op een andere frequentie.
-- Ze lopen allemaal continu, zelfs als de schakelaars
-- hun specifieke uitvoer niet selecteren.
p_100_HZ : process (i_clock) is
begin
if rising_edge(i_clock) then
if r_CNT_100HZ = c_CNT_100HZ-1 then
-- -1, omdat teller begint op 0
r_TOGGLE_100HZ <= not r_TOGGLE_100HZ;
r_CNT_100HZ <= 0;
else
r_CNT_100HZ <= r_CNT_100HZ + 1;
end if;
end if;
end process p_100_HZ;
p_50_HZ : process (i_clock) is
begin
if rising_edge(i_clock) then
if r_CNT_50HZ = c_CNT_50HZ-1 then
-- -1, omdat teller begint op 0
r_TOGGLE_50HZ <= not r_TOGGLE_50HZ;
r_CNT_50HZ <= 0;
else
r_CNT_50HZ <= r_CNT_50HZ + 1;
end if;
end if;
end process p_50_HZ;
p_10_HZ : process (i_clock) is
begin
if rising_edge(i_clock) then
if r_CNT_10HZ = c_CNT_10HZ-1 then
-- -1, omdat teller begint op 0
r_TOGGLE_10HZ <= not r_TOGGLE_10HZ;
r_CNT_10HZ <= 0;
else
r_CNT_10HZ <= r_CNT_10HZ + 1;
end if;
end if;
end process p_10_HZ;
p_1_HZ : process (i_clock) is
begin
if rising_edge(i_clock) then
if r_CNT_1HZ = c_CNT_1HZ-1 then
-- -1, omdat teller begint op 0
r_TOGGLE_1HZ <= not r_TOGGLE_1HZ;
r_CNT_1HZ <= 0;
else
r_CNT_1HZ <= r_CNT_1HZ + 1;
end if;
end if;
end process p_1_HZ;
-- Een multiplexer maken op basis van schakelaarinvoeren
w_LED_SELECT <= r_TOGGLE_100HZ when (i_switch_1 = '0' and i_switch_2 = '0') else
r_TOGGLE_50HZ when (i_switch_1 = '0' and i_switch_2 = '1') else
r_TOGGLE_10HZ when (i_switch_1 = '1' and i_switch_2 = '0') else
r_TOGGLE_1HZ;
-- Alleen o_led_drive laten aansturen wanneer i_enable hoog is (en-poort).
o_led_drive <= w_LED_SELECT and i_enable;
end rtl;
Verilog-implementatie
module tutorial_led_blink (
i_clock,
i_enable,
i_switch_1,
i_switch_2,
o_led_drive
);
input i_clock;
input i_enable;
input i_switch_1;
input i_switch_2;
output o_led_drive;
// Constanten (parameters) om de benodigde frequenties te creëren:
// Invoerkok is 25 kHz, willekeurig gekozen.
// Formule is: (25 kHz / 100 Hz * 50% duty cycle)
// Dus voor 100 Hz: 25.000 / 100 * 0.5 = 125
parameter c_CNT_100HZ = 125;
parameter c_CNT_50HZ = 250;
parameter c_CNT_10HZ = 1250;
parameter c_CNT_1HZ = 12500;
// Deze signalen zijn de tellers:
reg [31:0] r_CNT_100HZ = 0;
reg [31:0] r_CNT_50HZ = 0;
reg [31:0] r_CNT_10HZ = 0;
reg [31:0] r_CNT_1HZ = 0;
// Deze signalen togglen op de benodigde frequenties:
reg r_TOGGLE_100HZ = 1'b0;
reg r_TOGGLE_50HZ = 1'b0;
reg r_TOGGLE_10HZ = 1'b0;
reg r_TOGGLE_1HZ = 1'b0;
// Een bit select
reg r_LED_SELECT;
wire w_LED_SELECT;
begin
// Alle always-blokken togglen een specifiek signaal op een andere frequentie.
// Ze lopen allemaal continu, zelfs als de schakelaars
-- hun specifieke uitvoer niet selecteren.
always @ (posedge i_clock) begin
if (r_CNT_100HZ == c_CNT_100HZ-1) // -1, omdat teller begint op 0
begin
r_TOGGLE_100HZ <= !r_TOGGLE_100HZ;
r_CNT_100HZ <= 0;
end else
r_CNT_100HZ <= r_CNT_100HZ + 1;
end
always @ (posedge i_clock) begin
if (r_CNT_50HZ == c_CNT_50HZ-1) // -1, omdat teller begint op 0
begin
r_TOGGLE_50HZ <= !r_TOGGLE_50HZ;
r_CNT_50HZ <= 0;
end else
r_CNT_50HZ <= r_CNT_50HZ + 1;
end
always @ (posedge i_clock) begin
if (r_CNT_10HZ == c_CNT_10HZ-1) // -1, omdat teller begint op 0
begin
r_TOGGLE_10HZ <= !r_TOGGLE_10HZ;
r_CNT_10HZ <= 0;
end else
r_CNT_10HZ <= r_CNT_10HZ + 1;
end
always @ (posedge i_clock) begin
if (r_CNT_1HZ == c_CNT_1HZ-1) // -1, omdat teller begint op 0
begin
r_TOGGLE_1HZ <= !r_TOGGLE_1HZ;
r_CNT_1HZ <= 0;
end else
r_CNT_1HZ <= r_CNT_1HZ + 1;
end
// Een multiplexer maken op basis van schakelaarinvoeren
always @ (*) begin
case ({i_switch_1, i_switch_2}) // Concatentatie-operator
2'b11 : r_LED_SELECT <= r_TOGGLE_1HZ;
2'b10 : r_LED_SELECT <= r_TOGGLE_10HZ;
2'b01 : r_LED_SELECT <= r_TOGGLE_50HZ;
2'b00 : r_LED_SELECT <= r_TOGGLE_100HZ;
endcase
end
assign o_led_drive = r_LED_SELECT & i_enable;
// Alternatieve manier om multiplexer te ontwerpen (zoals boven):
// Meer compact, maar moeilijker te lezen, vooral voor nieuwkomers in Verilog
// assign w_LED_SELECT = i_switch_1 ? (i_switch_2 ? r_TOGGLE_1HZ : r_TOGGLE_10HZ) :
//(i_switch_2 ? r_TOGGLE_50HZ : r_TOGGLE_100HZ);
// assign o_led_drive = w_LED_SELECT & i_enable;
end
endmodule
Volgende stap: Simuleer dit ontwerp in VHDL of Verilog om correct gedrag te bevestigen voordat je het implementeert.
VHDL
- Formele verificatie van VHDL-modules met PSL meesteren
- Stapsgewijze Handleiding: Je Eerste FPGA LED-knipperder Bouwen
- Procedures in VHDL gebruiken: je ontwerp vereenvoudigen met herbruikbare code
- In-process procedures benutten voor schonere VHDL FSM-ontwerpen
- Willekeurige getallen genereren in VHDL: Van Uniform naar OSVVM
- Een klokgetriggerd proces in VHDL bouwen: een praktische gids
- Test je VHDL-basis – Interactieve Quiz Deel 1
- VHDL Variabelen Uitgelegd: Praktische Voorbeelden & Regels voor Betrouwbaar Ontwerp
- Gratis VHDL-simulator & editor setup: Een studentengids
- VHDL Records Gebruiken voor Schone, Herbruikbare FIFO-interfaces
- FPGA & VHDL Cursus: Bouw een Dot-Matrix LED-controller vanaf nul