프로세스 내 프로시저로 VHDL FSM 설계 깔끔하게 만들기
VHDL에서 프로시저는 시그널이 범위 내에 있는 한 외부 시그널을 구동할 수 있습니다. 아키텍처에 선언된 프로시저는 컴파일 시점에 외부 시그널이 보이지 않아 접근할 수 없지만, 프로세스 내부에 정의된 프로시저는 해당 프로세스가 볼 수 있는 모든 시그널을 자유롭게 읽고 쓸 수 있습니다.
프로세스 내 프로시저를 사용하면 상태 머신이나 복잡한 프로토콜에서 반복되는 로직을 정리하는 효과적인 방법입니다. 타이밍 상태 전이 같은 공통 동작을 프로시저로 캡슐화하면 상용구 코드를 줄이고 가독성을 높이며 각 단계의 의도를 명확히 할 수 있습니다.
예를 들어 신호등 컨트롤러에서 연산을 ChangeState(ToState, Minutes, Seconds)로 이름 붙이면 내부 변수 할당을 파고들지 않고도 코드가 무슨 일을 하는지 바로 알 수 있습니다.
실습 예제
이전 튜토리얼에서는 비순수 함수를 써서 Counter 시그널을 구동하고 상태 변경을 결정했습니다. 하지만 VHDL은 함수 호출마다 반환값을 요구하므로 State 할당을 그 함수로 옮길 수 없었습니다. 반환값이 없는 프로시저가 이 용도에 딱 맞습니다.
아래는 프로세스 내 프로시저로 구동되는 신호등 FSM을 보여주는 완전한 테스트벤치와 모듈입니다.
테스트벤치
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity T23_ProcedureInProcessTb is end entity; architecture sim of T23_ProcedureInProcessTb is constant ClockFrequencyHz : integer := 100; -- 100 Hz constant ClockPeriod : time := 1000 ms / ClockFrequencyHz; signal Clk : std_logic := '1'; signal nRst : std_logic := '0'; signal NorthRed : std_logic; signal NorthYellow : std_logic; signal NorthGreen : std_logic; signal WestRed : std_logic; signal WestYellow : std_logic; signal WestGreen : std_logic; begin i_TrafficLights : entity work.T23_TrafficLights(rtl) generic map(ClockFrequencyHz => ClockFrequencyHz) port map ( Clk => Clk, nRst => nRst, NorthRed => NorthRed, NorthYellow => NorthYellow, NorthGreen => NorthGreen, WestRed => WestRed, WestYellow => WestYellow, WestGreen => WestGreen); Clk <= not Clk after ClockPeriod / 2; process is begin wait until rising_edge(Clk); wait until rising_edge(Clk); nRst <= '1'; wait; end process; end architecture;
신호등 모듈
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity T23_TrafficLights is generic(ClockFrequencyHz : integer); port( Clk : in std_logic; nRst : in std_logic; NorthRed : out std_logic; NorthYellow : out std_logic; NorthGreen : out std_logic; WestRed : out std_logic; WestYellow : out std_logic; WestGreen : out std_logic); end entity; architecture rtl of T23_TrafficLights is type t_State is (NorthNext, StartNorth, North, StopNorth, WestNext, StartWest, West, StopWest); signal State : t_State; signal Counter : integer range 0 to ClockFrequencyHz * 60; begin process(Clk) is procedure ChangeState(ToState : t_State; Minutes : integer := 0; Seconds : integer := 0) is variable TotalSeconds : integer; variable ClockCycles : integer; begin TotalSeconds := Seconds + Minutes * 60; ClockCycles := TotalSeconds * ClockFrequencyHz - 1; if Counter = ClockCycles then Counter <= 0; State <= ToState; end if; end procedure; begin if rising_edge(Clk) then if nRst = '0' then State <= NorthNext; Counter <= 0; NorthRed <= '1'; NorthYellow <= '0'; NorthGreen <= '0'; WestRed <= '1'; WestYellow <= '0'; WestGreen <= '0'; else NorthRed <= '0'; NorthYellow <= '0'; NorthGreen <= '0'; WestRed <= '0'; WestYellow <= '0'; WestGreen <= '0'; Counter <= Counter + 1; case State is when NorthNext => NorthRed <= '1'; WestRed <= '1'; ChangeState(StartNorth, Seconds => 5); when StartNorth => NorthRed <= '1'; NorthYellow <= '1'; WestRed <= '1'; ChangeState(North, Seconds => 5); when North => NorthGreen <= '1'; WestRed <= '1'; ChangeState(StopNorth, Minutes => 1); when StopNorth => NorthYellow <= '1'; WestRed <= '1'; ChangeState(WestNext, Seconds => 5); when WestNext => NorthRed <= '1'; WestRed <= '1'; ChangeState(StartWest, Seconds => 5); when StartWest => NorthRed <= '1'; WestRed <= '1'; WestYellow <= '1'; ChangeState(West, Seconds => 5); when West => NorthRed <= '1'; WestGreen <= '1'; ChangeState(StopWest, Minutes => 1); when StopWest => NorthRed <= '1'; WestYellow <= '1'; ChangeState(NorthNext, Seconds => 5); end case; end if; end if; end process; end architecture;
파형
시뮬레이션은 원래 FSM과 동일하며, 코드만 더 유지보수하기 쉽고 이해하기 쉽게 바뀌었습니다.
분석
타이밍과 상태 변경 로직을 단일 프로시저로 옮기면서 모듈 동작은 보존하고 가독성을 크게 높였습니다. 프로시저는 호출되는 곳마다 일관된 구현을 보장합니다.
핵심 요약
- 프로세스 내 프로시저는 해당 프로세스가 볼 수 있는 모든 시그널에 접근할 수 있다.
- 반복 로직을 프로시저로 캡슐화하면 코드 명확성과 유지보수성이 향상된다.