這一次要學的是「最底層」的語言——不是寫在 CPU 上的指令,而是直接長出電路的語言:VHDL(VHSIC Hardware Description Language)。載體是手上這塊 Terasic DE10-Lite 開發板:
FPGA 與 CPU:同一塊矽,兩種玩法
CPU 是「通用引擎」:同一顆晶片靠指令流跑任何程式。FPGA(Field Programmable Gate Array)則是「白地」:一整片由數萬個可配置閘(LUT + 觸發器)組成的矩陣,你的程式碼會被翻譯成一條條真實的電路——寫完、編譯、燒錄之後,計數器就真的「存在」晶片的矽上了。沒有「取指令、解譯」這回事:每個時脈週期,所有電路同時在動。
這就是為什麼 50MHz 的時脈可以直接餵給計數器:VHDL 寫的不是「程式」,而是硬體描述——你描述的是「電路長什麼樣、每個時脈週期發生什麼」,不是「依序做哪些事」。
DE10-Lite 上有哪些資源
- Altera MAX 10 FPGA(10M02SAE144I7G):約 1.2 萬個 logic element,學完所有基礎專題綽綽有餘
- 4 顆 7 段顯示器(HEX0~3):16 進位,是看「內部數字」最直接的方法
- 10 顆開關 SW、10 顆 LED、4 顆按鍵 KEY0~3(KEY0 = Reset、KEY1 = Clock,注意低有效)
- 50MHz 時脈(CLOCK_50):所有入門專題的標準心跳
- SDRAM、USB、VGA、100-pin 排針——進階專題(framebuffer、UART、影像)留給後面
工具鏈:Quartus Prime
Altera(現 Intel)自家的 Quartus Prime 是這塊板子的「編譯器」:VHDL → 綜合(synthesis)→ 佈線(placement & routing)→ 產生 .sof 設定檔 → 燒進 FPGA。Lite 版編 MAX 10 免費可用,學生完全夠。固定流程:
- Project → Add File 放進 .vhd,Device 選 10M02SAE144I7G
- Pin Planner 把 port 對到板子腳位:CLOCK_50、LEDR[9..0]、SW[7..0]、HEX0..3
- Compilation 全套編譯,看 timing 報告——50MHz 要求 20ns 週期,入門設計通常裕量很大
- Programmer 走 USB 燒錄;上電後你的「電路」才會真正跑起來
第一個電路:LED 閃爍
50MHz 的時脈,人眼完全看不見(一秒翻轉 5000 萬次)。所以第一步永遠是時脈分頻:數到 2500 萬再翻一次位 = 2Hz,眼睛就看得見了。
library IEEE;
use IEEE.std_logic_1164.ALL;
entity led_blink is
Port (
clk : in std_logic; -- 50MHz
led : out std_logic_vector(9 downto 0) -- LEDR
);
end entity;
architecture behave of led_blink is
signal cnt : integer range 0 to 24_999_999 := 0;
signal toggle : std_logic := '0';
begin
process(clk)
begin
if rising_edge(clk) then
if cnt = 24_999_999 then -- 50MHz / 2 * 25M = 2Hz
cnt <= 0;
toggle <= not toggle;
else
cnt <= cnt + 1;
end if;
end if;
end process;
led <= (others => toggle);
end architecture;
三個重點:(1) 所有邏輯都寫在 process(clk) 的 rising_edge 裡——這是「同步電路」的鐵律;(2) 計數器數到上限歸零、同時翻一位;(3) (others => toggle) 一次把十顆 LED 綁到同一個信號——看到「十顆齊閃」的那一刻,FPGA 和 CPU 的差別就有了身體感。
第二個電路:計數 + 7 段顯示
這是一道分水嶺:三個功能(分頻、計數、解碼顯示)在同一個檔案協作——已經有「模塊化」的味道了:
library IEEE;
use IEEE.std_logic_1164.ALL;
use IEEE.numeric_std.ALL;
entity counter is
Port (
clk : in std_logic; -- 50MHz
rst : in std_logic; -- KEY[0] Reset(低有效)
sw : in std_logic_vector(7 downto 0);
seg : out std_logic_vector(3*7 downto 0); -- HEX3..HEX0
an : out std_logic_vector(3 downto 0); -- HEEn 低有效
led : out std_logic_vector(7 downto 0)
);
end entity;
architecture behave of counter is
signal clk_1k : std_logic := '0'; -- 50MHz 分頻到 1kHz
signal cnt1k : integer range 0 to 49_999 := 0;
signal count : unsigned(15 downto 0) := (others => '0');
type t7seg is array (7 downto 0) of std_logic_vector(6 downto 0);
constant SEGTAB : t7seg := (
"1000000", -- 0 (a~g)
"1100001", -- 1
"0100100", -- 2
"0100001", -- 3
"0110000", -- 4
"0010011", -- 5
"0010111", -- 6
"1000000", -- 7
"0000000", -- 8
"0010000", -- 9
"0001000", -- A
"0011111", -- b
"0111011", -- C
"1111001", -- d
"0111111", -- E
"0011111");-- F
begin
-- 1kHz 心跳
process(clk)
begin
if rising_edge(clk) then
if cnt1k = 49_999 then
cnt1k <= 0;
clk_1k <= not clk_1k;
else
cnt1k <= cnt1k + 1;
end if;
end if;
end process;
-- 計數(每 1kHz +1)
process(clk_1k)
begin
if rising_edge(clk_1k) then
if rst = '1' then
count <= (others => '0');
else
count <= count + 1;
end if;
end if;
end process;
led <= sw;
-- 四顆7段顯示
process(clk_1k, count)
variable nib : unsigned(3 downto 0);
begin
if rising_edge(clk_1k) then
for i in 0 to 3 loop
nib := count((i*4+3) downto i*4);
seg((i*7+6) downto i*7) <= SEGTAB(conv_integer(nib));
end loop;
end if;
an <= "0111"; -- 全亮(簡單版;動態掃描見進階篇)
end process;
end architecture;
7 段解碼表建議自己手推一遍:0~9 每個數字,七段 a~g 哪些點亮、哪些熄,推完再對照常數表——比死背快三倍。這裡先用「四顆同時常亮」的簡接法;真正的動態掃描(一顆一輪詢、用 1kHz 換「四顆都亮」的錯覺)留給進階篇——那裡會第一次遇到「時序錯配」的真實問題。
學習路線(由淺入)
- LED 閃爍 → 雙計數器 → 交通號誌(Finite State Machine 的入口)
- 7 段動態掃描 → 分頻樹 → timing 約束(timing constraint)
- 同步 vs 非同步 Reset 的差別(這塊板 KEY0 低有效,極性寫反不報錯、但會亂動)
- VGA framebuffer(SDRAM)→ 第一次碰「外部介面」
- 進階:UART / SPI / I²C 軟硬體協定 → 與微控制器協作
新手最容易摔的三坑
- 把 VHDL 當 C 寫:預設是「併行」,C 語言順敘寫法在硬體上會變成組合邏輯迴路。先想電路、再寫程式,是唯一解法。
- 用 50MHz 餵組合邏輯:計數、解碼、顯示都要掛在
rising_edge上,否則編譯出一堆 timing violation,燒錄成功但行為亂跳。 - 7 段選通(HEEn)是低有效:拉低才亮——接反了 = 全滅。新手最容易懷疑「晶片壞了」,其實只是極性。
結語
從 DE10-Lite 上電的那刻起,學習的主題就從「寫程式」變成「長電路」。這塊板夠小、夠耐玩——先把 LED 閃爍跟 7 段計數跑通,往後的路就順了。


沒有留言:
張貼留言