하만 과정 공부/Verilog - Zynq7000

9-15 베릴로그 합성 문제 관련 배운 내용과 생각 정리

바쁜 취준생 2024. 9. 15. 01:48

내가 베릴로그를 배울 때 시뮬레이션은 잘 돌아갔으나, 

합성이 안되는 경우가 있었다.

그래서 합성이 잘 되는 교수님의 코드를 살펴보니 몇가지 특징이 있어서 정리하려 한다.

그동안 배운 내용도 같이 적는다.

 

1. 결국 베릴로그는 하나의 기능을 하는 모듈을 설계한다.

설계의 기본 단위는 모듈이고, 이는 인풋과 아웃풋이 있다.

이 외에 기본적으로 들어가는 리셋과 클럭이 있다.

그래서 인풋, 리셋, 클럭을 가지고서 기능을 동작하게 만든다.

 

2. 일단 모듈의 시작은 타이밍 계산

이 부분을 좀 늦게 알아서 힘들었다.

결국 우리가 회로 설계할 때 주요하게 보는 것은 

신호의 타이밍이다. 이것을 알려면 순차회로와 조합회로를 알아야 된다.

베릴로그 회로는 크게 순차 회로(클럭이용)과 조합회로(논리 게이트 이용)으로 구성된다.

 

3. 먼저 조합회로는 순수한 전기적 입력을 바로 논리 게이트에 통과 시켜서 출력을 내보낸다.

디지털 논리회로에서 배운 카르노 맵이나 불대수 같은 것이 여기서 이용된다.

Xilinx FPGA에서는 이를 LUT(Look Up Table)이라는 것을 활용해서 작동시킨다.

LUT는 실제로는 진리표(Truth table)을 실제로 구현한 것이다. 

내부에 SRAM이 들어가서 전원이 들어오고 이런 입력이 들어올 때 진리표 테이블에서 Look Up 해서 출력을 내보내는 것이다.

엑셀의 vlookup과 비슷한 느낌이다.

그리고 SRAM을 사용하기에, 전원이 꺼지면 진리표가 초기화 된다.

그래서 별도의 플래시 메모리를 장착해야 전원이 꺼져도 동작한다고 한다.

조합회로를 정리하면 얘는 그냥 입력이 들어오면 게이트의 딜레이를 제외하면 바로 출력으로 내보낸다.

입력이 없으면 출력도 없다. 클럭 상관 없이 들어오면 출력을 내보낸다.

 

3. 그러면 디지털회로의 핵심인 순차회로를 보자.

순차회로는 클럭에 맞춰서 순차적으로 작동해서 이런 이름이 붙은 것 같다. 확실하지는 않다.

결국 클럭이 있을 때만 그 값이 바뀐다.

이를 위해서 플립플롭과 이를 병렬로 연결한 레지스터를 가진다.

다시 말하면, 클럭 있으면 계산후 출력 값 변경, 클럭 없으면 현재 계산값을 그대로 출력한다.

여기서 RTL설계라 부르는지 추측해본다.

RTL(Regiter Transfer Level)인데,

말 그대로 한 레지스터의 출력을 다른 레지스터로 전달하는 수준에서 설계를 해서 그런 것 같다.

조합회로는 입력이 없으면 동작하지 않는다. 따라서 입력이 꾸준히 들어와야 출력이 꾸준히 나간다.

즉 입력 값은 레지스터에서 나온 출력이 조합회로를 거치든, 바로 들어오든 해서 입력으로 들어가는 것이다.

따라서 조합회로 입력의 출발점은 어떤 레지스터의 출력이다.

그리고 또한 조합회로의 출력도 다른 모듈의 인풋이 되기에,

이 출력 또한 조합회로를 거치고 순차회로를 거치면서 마지막은 레지스터에 입력으로 들어간다.

이래서 입력의 출발점과 출력의 도착점이 모두 레지스터라

레지스터간 전달하는 레벨로 설계한다는 의미에서 RTL로 생각한다. 정확한 것은 아니다.

 

3. 결국 디지털 신호는 시간을 나눠서 정보의 전달량을 늘렸다. 이 시간 기준은 클럭이다.

따라서 클럭에 의해서 데이터를 구분하고 처리하게 된다. 그래서 순차회로가 중요하다.

 

4. 조합회로는 그냥 식만 입력 잘 하면된다.

 

5. 문제는 순차회로다.

앞서서 디지털은 시간을 나눠서 데이터량을 늘렸고, 그 기준은 클럭이다.

따라서 클럭이 바뀔 때마다 데이터가 바뀌고 출력이 바뀐다.

그렇기에 데이터 입력에 따른 출력을 바꿀 타이밍을 맞추기 위해서 클럭을 쓴다.

 

6. 내가 이 부분에서 합성이 잘 안되어서 고민했는데,

일단 합성이 잘 되기 위한 코드 규칙을 정리하면

1) 일단, 여러 always 구문을 만드는 것을 두려워하지 말자.

하나의 always 구문에는 하나의 신호만 들어가야 잘 만들어준다.

2, 3개 넣어도 만들어 주기는 하지만 잘 안될 수도 있으니 가능한 쪼개자.

합성 툴이 그렇게 엄청 똑똑하지 않아서 대충 만들면 이상하게 만든다.

 

2) 결국 클럭을 이용한 순차회로는 타이밍을 계산해야 된다.

반대로 타이밍에 맞는 구조만 만들면 상황에 맞는 데이터 출력만 만들어주면 회로가 완성된 것이다.

 

3) 그리고 각 모듈은 개별적이다.

아까 모듈의 입력은 인풋, 클럭, 리셋이 있었다. 그리고 각 모듈은 개별적으로 동작하기에

일단 시작은 클럭 입력에서 기준점인 엣지를 감지해서 신호를 만드는 것이 처음 할 일이다.

이후 그 클럭을 분주해서(1초에 1000번 반복되는 주기적인 신호이면 한 주기는 1ms이고, 이를 8번 세면 8ms인 125Hz가 나온다. 나는 클럭 주파수를 나눈다고 표현한다.) 원하는 타이밍 주기를 맞춘다.

그리고 이렇게 만든 신호를 가지고서 인풋 신호를 받아들여서

값을 저장하고 조합회로로 신호를 처리하고, 최종적으로 출력을 내보낸다.

 

4) 더 많은 코딩 방식이 있지만, 일단 엣지 디텍터와 카운터로 시간 주기를 나누는 것을 배웠다.

앞서 말한대로 결국 모듈은 인풋과 클럭만으로 동작하기에 

먼저 클럭의 엣지를 감지해서 데이터의 변화와 출력의 변화를 제어한다.

이때 사용하는 것이 엣지 디텍터이다.

이유는 모르나 클럭은 posedge쓰면 엣지를 감지해서 작동한다고 한다.

아마도 high와 low로는 상태만 표현해서 시간을 감지할 수 없어서 그런 것 같기도 하다.

 

결국 클럭을 사용해서 신호의 엣지를 검출한다.

엣지 디텍터는 2개의 레지스터를 사용해서 한 레지스터에 신호가 한클럭씩 늦게 들어가게 만든다.

0000111100001111 이런 신호가 오면 

input => reg0 => reg1 이런 순서로 클럭에 맞춰서 들어온다.

그럼 이때 0에서 1로 바뀌는 순간 reg0는 먼저 들어와서 1이고 reg1은 늦게 들어와서 0인 구간이 있다.

그러면 이때를 상승 에지로 감지하고, 반대로 reg0가 0이고 reg1이 1이면 하강에지로 감지한다.

그럼 이제 이 순간을 조건문을 통해서 감지한다. 

결국 레지스터는 상태값이므로, reg0가 1이고 reg1이 0이 된다. 그러면 이 입력을 조합회로에 넣는다.

조합회로는 클럭과 상관없이 동작하므로 클럭보다 빨리 계산을 해준다.

그러면 출력을 맡는 F/F에 미리 입력을 넣는다.

이후 시스템 클럭으로 한 클럭 뒤에 FF이 입력값을 바탕으로 출력값을 갱신하면서

상승 엣지를 시스템 클럭 기준으로 한 클럭 뒤에 감지한다.

그럼 이것을 가지고서 다시 다른 신호 생성의 기준으로 삼아서 

내부 신호나 출력 신호를 만들어 낼 수 있다.

 

 

5) 결과적으로 회로는 엣지 디텍터(레지스터 2개), 카운터, MUX, LUT로 구성된다. 일단 내가 배운 한에서는 그렇다.

레지스터는 엣지 디텍터나 값을 저장하고 출력하는데 중점을 둔다.

카운터는 말 그대로 카운터이다. 레지스터와 합해서 특정 신호가 오면 그 횟수를 저장한다.

주로 클럭이나, 입력 신호를 카운트 해서 타이밍을 맞출 수 있다.

MUX는 선택기나 분배기이다. 선택기는 여러 입력중 하나를 출력으로 내보내고, 분배기는 하나의 입력을 어디로 출력할지 선택한다. 각각 입력:출력 기준으로 n:1 과 1:n이다.

이는 if구문으로 구현되거나, case 구문으로 구현된다.

case는 균등 딜레이 , if는 else 이후가 될수록 나중에 판단된다. 따라서 딜레이된 신호는 else구문 뒤쪽으로 배치하면 좋다.

마지막으로 아까 말했던 논리회로를 구현한 LUT가 있다.

 

6) if구문을 사용할 때는 모든 조건에 출력 값을 할당하기

만약 if구문을 사용할 때, 아무 조건에 해당하지 않아서 값이 바뀌지 않으면

이전 값을 출력해야 된다. 그 소리는 값을 저장해야 될 레지스터나 래치가 필요하다.

래치는 클럭 상관 없는 저장 소자이고, 레지스터는 클럭에만 저장된다.

레지스터 있으면 상관 없지만, 래치는 조합회로에서 클럭과 상관없는 출력이 나올 때 생성 될 수 있고

클럭과 상관이 없어서 타이밍이 달라질 수 있어서 의도와 다른 결과를 출력 할 결과가 있어서

가능하면 모든 if문에는 always구문에서 출력하기로 한 신호를 넣어야 된다.

 

7. 중간에 너무 두서없이 설명했는데 정리하면

if문 쓸 때는 always 구문안에서 사용하는데, 이건 MUX로 바뀌기에, 모든 경우에 대해서 출력이 있어야 좋다.

모듈은 독립적이라, 인풋과 클럭만 가지고서 신호를 만들기 시작해서 타이밍을 맞춰야 된다.

출력은 타이밍이 만들어지면 끼워 넣으면된다.

내가 만든 신호를 기준으로 같은 always 구문에서 다시 if 판단 조건으로 사용해도 문제 없다.

다만, 하나의 always구문에 여러 신호를 넣지 말자. 

그러면 특정 조건에서는 한 신호만 바뀌고, 다른 신호는 바뀌면 안되서 신호를 하나만 쓰면 합성이 안된다.

그래서 차라리 타이밍의 기준이 되는 신호를 먼저 만들고 그 신호를 기준으로 다른 신호를 만들어야 좋다.

 

모듈끼리 연결하는 것을 보면 결국 시작은 인풋과 클럭으로 시작해서 여러 모듈을 거쳐서 입력이 들어오고

아웃풋으로 출력이 나간다.

 

8. 설계과정 설명

목적에 따라 기능을 구현한다.

타이밍 설계를 할 때는 일단 필요한 신호를 먼저 그려보고

미리 만들어진 신호 중에서 어떤 신호를 기준으로 쓸지 고민하면된다.

이후 타이밍 그래프가 만들어지면 그대로 만들면 된다.

 

클럭을 기반으로 인풋중 선이 1개라서 데이터가 아닌 타이밍 신호만 보내는 신호선에서

인풋 에지 디텍트를 진행하면 타이밍을 맞출 수 있다.

이후 새로운 신호와 카운터로 데이터를 분리하고 

논리식으로 처리후 

다시 카운터와 새로운 신호나 클럭으로 신호를 내보낼 타이밍을 계산해서 내보낸다.

 

뭔가 난잡하지만 내가 배운 내용의 전부이다.

이 외에는 툴 사용법에 가깝다.

 

설계 과정을 잘 기억해두자.