VHDL 문법 정리 (2): 타입/신호/변수, 동시·순차 문장, 프로세스
1편에서 설계 단위(entity/architecture/package)의 경계를 정리했다. 그 다음에 실제 RTL을 쓰기 시작하면 거의 반드시 두 가지 질문이 나온다. 첫째, std_logic_vector로 연산을 하면 왜 갑자기 변환이 필요해지는가. 둘째, signal과 variable을 섞어 쓰면 시뮬레이션 결과가 왜 달라지는가. 이 글은 그 질문을 문법 조각이 아니라 시간 모델(time model)과 타입 시스템(type system) 관점에서 정리한다.
기준은 VHDL-2008이며, 합성 가능한 RTL을 전제로 한다1. 일부 예시는 “합성 불가/시뮬레이션 전용”임을 명시한다.
타입 계층: std_logic_vector는 “비트열”이고 숫자가 아니다
std_logic_vector는 흔히 “버스”로 쓰이지만, 본질적으로는 비트의 배열이다. 숫자 연산 의미를 붙이는 순간, 연산의 주체 타입을 unsigned 또는 signed로 바꾸는 것이 일관된 스타일이다2.
std_logic_vector: 표현/인터페이스 계층unsigned/signed: 산술/의미 계층- 변환: 의미 ↔ 표현의 경계에서만 수행
이 분리를 지키면 코드가 길어지는 대신, 합성기/시뮬레이터의 해석 차이를 줄이고, 리뷰/디버깅이 쉬워진다.
전체 코드 예제: 조합 + 순차 혼합에서의 타입/변환
아래 예제는 입력 a, b를 더해서 누산하고, 결과를 std_logic_vector로 출력하는 형태다. 의도는 “숫자 연산은 unsigned로, 포트는 std_logic_vector로”를 눈에 보이게 만드는 것이다.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity adder_accum is
generic (
WIDTH : natural := 8
);
port (
clk : in std_logic;
rst_n : in std_logic;
a : in std_logic_vector(WIDTH-1 downto 0);
b : in std_logic_vector(WIDTH-1 downto 0);
y : out std_logic_vector(WIDTH-1 downto 0)
);
end entity;
architecture rtl of adder_accum is
signal acc : unsigned(WIDTH-1 downto 0);
signal sum : unsigned(WIDTH-1 downto 0);
begin
-- concurrent assignment: 조합 논리
sum <= unsigned(a) + unsigned(b);
-- sequential statements inside process: 순차 논리
process(clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
acc <= (others => '0');
else
acc <= acc + sum;
end if;
end if;
end process;
-- 표현 계층으로 변환
y <= std_logic_vector(acc);
end architecture;
여기서 “타입 경계”를 만드는 라인은 다음 세 군데다.
sum <= unsigned(a) + unsigned(b);
a, b는 포트에서 std_logic_vector이므로 숫자 의미를 가지지 않는다. 산술은 unsigned로 수행하고, 그 결과 sum은 산술 타입으로 유지한다.
acc <= acc + sum;
산술 타입끼리의 연산이라 변환이 개입하지 않는다. 이 상태가 유지될수록 RTL은 읽기 쉬워진다.
y <= std_logic_vector(acc);
출력 포트는 “인터페이스 계층”으로 유지하고 싶으므로 마지막에 표현으로 변환한다. 이 위치가 변환의 단일 지점이 된다.
동시(concurrent)와 순차(sequential)의 경계
architecture ... begin ... end 구간은 기본적으로 “동시 문장”이 배치되는 영역이다. 그 안에 process가 들어가면, process 내부만 “순차 문장”으로 평가된다.
- 동시 문장: 여러 식이 병렬로 존재하는 선언(조합 논리 모델)
- 순차 문장:
process안에서 위에서 아래로 실행되는 문장(시간 축을 갖는 모델)
이 차이를 모르면 “왜 이 줄이 바로 반영되지 않는가” 같은 문제가 반복된다. 그 대표 사례가 signal vs variable이다.
signal과 variable: “업데이트 시점”이 다르다
요약하면 다음과 같다.
signal <=: 델타 사이클 이후에 값이 반영되는 예약(scheduled update)variable :=: 그 시점에서 즉시 값이 바뀌는 즉시 업데이트(immediate update)
그래서 같은 프로세스 안에서도 signal만 쓰면 중간 계산이 꼬이는 것처럼 보일 수 있고, variable을 섞으면 “순간적으로” 의도에 맞는 계산이 가능해진다.
예제: 같은 프로세스 안에서의 누산(변수 사용)
아래는 흔히 쓰는 패턴이다. 같은 클럭 에지에서 여러 조건을 처리하면서 최종 결과를 한 번만 레지스터에 싣고 싶을 때, 내부 계산을 variable로 하고 마지막에 signal에 할당하는 방식이다.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity saturating_counter is
generic ( WIDTH : natural := 8 );
port (
clk : in std_logic;
rst_n : in std_logic;
en : in std_logic;
q : out std_logic_vector(WIDTH-1 downto 0)
);
end entity;
architecture rtl of saturating_counter is
signal r : unsigned(WIDTH-1 downto 0);
begin
process(clk)
variable next_r : unsigned(WIDTH-1 downto 0);
begin
if rising_edge(clk) then
if rst_n = '0' then
r <= (others => '0');
else
next_r := r;
if en = '1' then
if r /= (others => '1') then
next_r := r + 1;
end if;
end if;
r <= next_r;
end if;
end if;
end process;
q <= std_logic_vector(r);
end architecture;
핵심 라인은 두 개다.
variable next_r : unsigned(WIDTH-1 downto 0);
...
next_r := r;
...
r <= next_r;
next_r는 프로세스 내부의 계산 버퍼다. 조건 분기가 여러 번 있어도 마지막에 r <= next_r;만 남기면, 합성기는 이를 “다음 상태 계산 + 레지스터”로 쉽게 해석한다. 반대로 분기마다 r <= ...를 여러 번 쓰면, 시뮬레이션에서는 “마지막 할당만 유효”가 되어 의도와 다르게 읽힐 수 있다.
sensitivity list: VHDL-2008의 process(all)와 실무 감각
조합 프로세스에서 민감도 리스트가 빠지면 시뮬레이션과 합성 간 불일치가 생길 수 있다. VHDL-2008에서는 process(all)로 이를 완화할 수 있다1.
- 조합 논리 프로세스:
process(all)또는 모든 입력 신호를 민감도에 포함 - 순차 논리 프로세스:
process(clk)(비동기 리셋이 있으면process(clk, rst_n)등)
다만 많은 FPGA 툴이 민감도 리스트 누락을 경고로만 처리하기도 해서, “경고인데도 회로가 나오니 괜찮다”는 착시가 생긴다. 이 영역은 문법보다 검증 습관의 문제에 가깝다.
다음 편으로의 연결
2편까지 이해하면 “문법은 맞는데 회로가 이상하다” 수준의 문제 대부분은 피할 수 있다. 다음 단계는 합성 관점에서 안전한 클럭/리셋 패턴을 일관되게 쓰는 일이다. 3편에서는 rising_edge(clk) 기반의 순차 템플릿, 동기/비동기 리셋의 트레이드오프, 그리고 generate/generic으로 구조를 확장하는 방식을 다룬다.
References
-
IEEE Standard for VHDL Language Reference Manual -
process(all)등 VHDL-2008 문법 정의를 포함한다. ↩ ↩2 -
IEEE numeric_std (overview) -
unsigned/signed산술 의미를 표준으로 제공하는 패키지 계열이다. ↩