Home » Post » FPGA » UART Tx Verilog Module 살펴보기

UART Tx Verilog Module 살펴보기

UART Tx Verilog Module 살펴보기 프로젝트

목차

  1. UART verilog Testbench 살펴보기
  2. UART Tx Verilog Module 살펴보기(현재 포스팅)
  3. UART Rx Verilog Module 살펴보기
  4. APB Bus 살펴보기
  5. APB Register 설계하기
  6. Vivado UART 모듈 설정 하기
  7. Xilinx Zynq Firmware Code 짜보기

지난번 포스트에서는 하위 모듈이 잘 설계되었다고 가정하고 테스트 벤치를 살펴보았다.
간단하게만 보면 아래와 같다.

  1. Tx 모듈에다 보낼 데이터(byte)를 DV와 함께 보내준다.
  2. 시리얼로 데이터를 보낸다.
  3. Rx 모듈에서 시리얼로 데이터를 수신한다. (Testbench 상에서는 Rx와 Tx 모듈이 서로 물려있다.)
  4. 데이터가 모이면 DV와 함께 데이터를 뱉는다.

이번 포스팅에서는 각 모듈을 살펴보기로 한다.
이번에도 참고할 코드는 깃허브에 있다.

UART Tx Verilog Module

먼저 송신단부터 확인해보자.

UART Tx Verilog Module

input으로는 리셋, 클럭, 데이터 valid, 송신할 데이터
output으로는 TX ACTIVE, Serial 데이터, 완료 신호이다.

TX ACTIVE라인은 Serial이 동작 중일 때만 1이고 아니면 0으로 설정하는 것으로, Testbench단에서 이 신호를 보고 UART Module Line을 제어한다.

내부에 선언되어 있는 변수들은 아래와 같다.

variable in UART Tx Module

localparam은 state machine 용으로 선언되어 있다. `define 형태로 정의해도 되지만, 타 모듈에도 영향을 끼치지 않게 하려면 localparam이 적합하다.
r_SM_Main은 state machine이고, 클럭 카운트도 선언되어 있다. CLKS_PER_BIT을 log_2형태로 취하는 것을 볼 수 있는데, n비트 수가 취할 수 있는 범위는 2^n이기 때문이다.

range \quad of \quad 2^n => [n - 1\, :\, 0]\, count; \quad 0 \sim 2^{(n-1)}
range \quad of \quad num => [\log_2(num)\, :\, 0]\, count;

그 다음은 bit index가 있는데 byte 단위로 송신할거고 parity bit도 없기 때문에 0~7까지의 인덱스만 표현하면 된다. 마지막으로는 r_TX_DATA는 보낼 데이터를 저장하는 레지스터이다.

순서를 정하자면 r_TX_DATA로 보낼 데이터를 받음 -> 시리얼로 내보냄 -> 끝! 순일거다.

아래 always 문에서는 state machine으로 동작하는 것을 볼 수 있다.

아래 state machine을 확인할 때 전 포스팅에서 첨부했던 그림을 참고하는 것이 좋다.

State Machine

State Machine in UART Tx Module

UART Tx Module을 살펴볼 때 해당 예제에서는 모두 state machine으로 동작하는 것을 확인할 수 있다. 먼저 reset이 발생했을 때 스테이트 머신먼저 초기화를 해준다.

정상 동작할 때는 아래와 같이 case문으로 머신이 동작하게된다.

IDLE 상태일 경우 시리얼 데이터는 high 상태로 유지하고, 모든 카운터와 인덱스를 0으로 초기화한다.
이후에 DV와 함께 송신할 데이터가 수신되면 Byte를 읽고, Active상태로 변경한 다음 그 다음 State로 변경한다.

START BIT 상태의 경우 0의 상태를 1bit 유지를 해야한다. 해당 상태가 끝나면 송신 상태로 넘어간다.

시리얼 데이터를 전송할 때 UART는 Index가 0 부터 송신하게 되어 있다.
Index가 0부터 시작해서 6이 되면 마지막으로 index를 1 더 올려서 7인 상태로 TX_DATA_BITS가 동작한다.
모든 8비트의 데이터를 송신하면(index가 7이 되면) TX_STOP_BIT로 넘어가서 송신을 마무리할 준비를한다.

TX_STOP_BIT에서는 1인 상태를 CLKS_PER_BIT까지 기다려준다음에 IDLE로 넘어가게 된다.
예제에서는 1cycle 정도 여유를 주었다.
한가지 주목할 점은, TX_STOP_BIT에서 TX_Active도 0으로 설정하고, TX_Done도 1로 처리했다는 점이다.
TX_Done 신호를 인터럽트 신호 선으로 볼 수도 있다.

해당 과정이 모두 끝나면 IDLE로 넘어가서 계속 진행한다.

Tx 포스팅 끗!

“UART Tx Verilog Module 살펴보기”에 대한 13개의 생각

  1. 안녕하세요! FPGA 와 verilog 공부하고 있는 대학원생입니다.
    연구실에서 혼자 이 쪽을 공부하고 있다보니, 구글을 선생님으로 여기고 있었습니다.
    그런데 관련성이 높아보여 클릭해 들어오면 매번 민지홍 개발자님 사이트더라구요. 항상 감사히 생각하고 있습니다. 감사합니다.
    이번 UART 관련 포스팅도 잘 보았습니다!
    UART 관련하여 제가 원하는 방향에 대해 통찰이 깊으실 것으로 생각되어서 첨언을 구해보고자 합니다.

    저는 지금 원하는 동작코드를 보드에 올려 실행하고 있는 상태인데, 제가 올린 코드 동작을 하는 FPGA 보드 출력을 모니터로 확인하고 싶습니다.
    출력과, 가능하면 내부상태(testbench처럼)를 확인하고 싶다고 생각하시면 될 것 같습니다. (출력만 되어도 문제 없습니다.)
    UART 자체를 다루고 싶다기보다는, 통신 역할을 주고 싶은 것입니다. 보드를 사용하면서 UART 를 배우는 목적이 대부분 그럴 것이라 생각합니다.
    제가 현재 보드에 올린 것은,
    testbench 로는 확인할 수 없는, 보드마다 다른 FPGA의 특성을 추출하는 기능의 모듈입니다. ( 간단히 설명드렸지만, PUF 를 추출하고자 합니다. )
    그래서 무조건 보드에 올려야만 합니다.
    그런데 제가 아직 UART 를 충분히 자유자재로 이용하지 못해서, 해당 동작 bitstream을 보드에 올려서, 결과(숫자)를 보드의 LED에 할당해 나타내고 있습니다.
    하지만 LED로는 한계가 있기에, 모니터로 (숫자로) 확인하고 싶습니다.

    이러한 상황에서, UART 를 verilog 로 구현한 후, 이와 제가 원하는 모듈을 어떻게 합해야할까요? 합한다는게 참 추상적이긴한데,
    각각은 짤 수 있겠다만,(사이트 참고하여서) 마지막에 개발자님이 하신 최종 설정처럼 UART 와 Zynq PS 부분과 같은 부분을 이어주는 설정처럼 무언가가 필요할 것 같은데,
    어떤 방식으로 해야할지 잘 모르겠습니다.
    제가 알고 있는 것은 2022.08 기준 Vivado 에서 UART 구조를 verilog로 짠 다음, Vitis로 넘어가서 C로 간단한 입/출력 코드를 짜는 것입니다. (개발자님이 마지막으로 올려주신 펌웨어 코드 짜는 방식과 유사합니다.)
    그렇지만, 제가 원하는 동작은 verilog로 이뤄져야 하기에,
    (C로 짜서 logical하게 변경할 수는 있다고 알고 있으나, 이미 짜 두기도 했고, 원하는 게이트와 논리회로 요소가 분명히 있어서 이를 이용해서 짰기 때문에, C로 구현하는 것은 불가능할 것 같습니다.)
    제가 알던 방식으로는 UART를 활용하지 못할 것 같습니다.

    결론적으로 정리하자면, 제가 짠 verilog code(보드 내부 동작 구현) + UART(통신. 출력값 화면으로 보내주기) 를 어떻게 이어야 할지 여쭙고 싶습니다.
    제가 가진 보드는 ZYBO z-7020 이고, USB to UART 통신보드 커넥터도 있습니다.

    혹은, UART가 아니라 그냥 HDMI로 모니터를 연결하여서 출력하게 하는 것이 더 나은 방법일까요? 해당 보드와 HDMI 모니터 연결/출력 방법은 아직 공부 전입니다.
    이번 출력이 성공한다면, 다음 목표로 입력도 혹시 UART로 줄 수 있지 않을까 싶기도 하고,
    간단한 UART 동작 실험 (위 언급한 Vitis 방법. UART는 verilog, 간단 Hello World 와 같은 코드는 C로) 경험, C언어로 마이크로프로세서와 UART 통신(이 때는 verilog나 Vivado는 전혀 사용하지 않았고 헤더파일(.h )을 비롯한 다른 제약 파일들만 사용하여 보드에 올리고 UART 통신 했습니다.)은 해 본 경험이 있어서 일단 UART를 시도하고자 한 것인데,
    더 간단하거나 좋은 방법이 있다면 그 방법을 추천해 주셔도 정말 도움이 됩니다. 혹은 다른 참고 사이트 라던가 여러가지 모두 다 추천해시면 감사하겠습니다.

    복잡하지만서도 제대로 정보전달이 되었을지 모르겠는데, 혼자서 이리 저리 해보고, 찾아보았지만 답을 내기가 어려워 이렇게 여쭤보게 되었습니다.
    미흡하겠지만, 추가 디테일한 설명도 얼마든지 가능합니다.

    아무쪼록 긴 글 읽어주셔서 감사드리고, 고견 기다리겠습니다. 감사합니다.

    1. hwl님 안녕하세요. 답글을 읽어보니 어떤 고생을 하고 있는지 느껴집니다.
      아무래도 구현하는 입장에서 정리가 되지 않으면 간단한 기능도 복잡하게 느껴지기 마련이지요.
      말씀해주신 하드웨어 모델들을 디버깅하기 위해서 memory mapping이 되어 있는 특정 레지스터를 읽어서 보겠다는 것이 핵심으로 보입니다.
      여기서부터 출발해보도록 합시다.

      하드웨어 모델이 특정 Base address에 매핑이 되어있고 offset에따라 레지스터 들을 할당해 놓으셨을겁니다.
      예를 들어 32비트 bus width를 가지고 있다면
      base addr + 0(offset)번지는 state reg
      base addr + 4(offset)번지는 input addr reg
      base addr + 8(offset)번지는 output addr reg
      base addr + 12(offset)번지는 output data reg
      등등.. burst call이냐 아니면 single이냐에 따라서 구성은 달라질 수 있겠지만, 여하튼 이런식으로 매핑이 되어 있을겁니다.

      PS 코어에서 저기 지정된 주소에 있는 데이터를 읽어서 표기만 하면 되는거겠지요. 이걸 UART냐 HDMI로 출력하냐는 여러가지 방법 중 하나일 뿐입니다.
      통상 디버깅 목적으로는 UART를 많이 사용하니까 간단하게 3줄 요약하면 아래와 같을겁니다.

      1. 설계한 모델에 필요한 데이터를 읽는다.(지정된 어드레스에 접근해서 값을 읽는다.)
      2. UART로 그 값을 보내준다.
      3. 끗

      UART는 베릴로그로 만들 필요가 없습니다. 왜냐면 사용하는 zybo z7-20 보드의 경우 UART 모듈이 PS 코어에서 제어할 수 있도록 회로도로 구성이 되어있기 때문이죠
      제가 베릴로그로 만든건 그냥 제가 베릴로그를 안써봐서 공부해볼 겸 만들어본 겁니다.
      블로그는 대충 찾아보면 https://rubber-tree.tistory.com/60 같은 곳에서 잘 만들어져 있네요. zynq7 processing system에서 설정하시구요. 근데 아마 보드 파일 설정 잘 하셨으면 애초에 UART가 체크가 되어있을겁니다.
      코드같은건 hello world 불러와서 그냥 printf로 박아주면 되네요. 아주 쉽군요.

      저 위에 적은 세 줄 요약을 대충 코드 비스무리하게 한번 적어볼까요. output 데이터를 읽는다고 칩시다.
      1. declare temp
      2. temp = read(base_addr + offset);
      3. printf(“output : %d \r\n”, temp);

      아주 좋군요. 힘든 대학원 생활 잘 이겨내시길 바랍니다.

      아 혹시나 하드웨어, 그러니까 보드 회로도를 읽는 법이 서투르실까봐 미리 정리해서 말씀드리면, USB to UART 통신보드 커넥터 필요 없습니다. bitstream 올리려면 보드 케이블 물려서 하실텐데(microb usb cable), 여기 통해서 uart 통신이 되는겁니다. 그러니까 vitis 돌릴때 비트스트림 올리고, com port 찾고 그냥 하시면 됩니다.
      그럼 20000…

      1. 정성스러운 답변 감사드립니다.
        덕분에 어떤 방식으로 진행해야 할지 감이 잡혔습니다.
        커넥터가 필요 없다는 말씀도 감사합니다. 혹시 연결하지 않아서 안되는 건 아닐까.. (무지에서 비롯된 여러 짐작 중 하나 였습니다.) 해서 여쭤보았었는데 마침 말씀해주셔서 다행이었습니다.
        답글을 여러 번 읽어보며 이해를 완벽히 했다고 생각했는데, 막상 적용을 시작하려니 몇 가지 문제에서 덜컥 막혀버렸습니다.
        잘 설명해 주셨는데, 제가 기본 적인 것부터 부족한 것이 많은 것 같습니다. 번거로우시겠지만, 또 한 번 여쭤보려 합니다.

        UART는 베릴로그로 직접 만들 필요가 없다는 점, 알았습니다.
        알려주신 블로그를 보았고 간단히 Hello World 예제도 해 보았습니다.
        이는 Vivado에서 블록다이어그램으로 zynq7 PS 설정해준 후, Vitis로 넘어가서 C로 마무리 하는 형태입니다.

        1. 제가 짠 코드와 함께 사용할 경우에는 UART와 제가 짠 (.v) 파일을 다이어그램으로 연결을 해 주어야 할까요? 그렇다면, 어떻게 연결해 주어야 할지 고민입니다.
        사실 제가 짠 코드는 systemclock만을 입력으로 받도록 설계했고, (.xdc 파일로 기본 클락을 활성화 시켰습니다. PS는 UART만 활성화 시켰고, clock은 체크하지 않았습니다.) 출력이 UART통해서 화면으로 보기를 원하는 숫자인데, 이 출력 단은 말씀하신대로 주소값으로 불러들일 수 있으니 사실상 또 다른 어딘가에 연결을 해 줄 필요가 없다고 생각됩니다.
        그래서 PS 블록 하나, 제가 짠 코드파일들. 이렇게 둘을 그냥 연결 없이 전체적으로 wrapping 하면 되는 것이 맞나요?
        일단 이렇게 해 보았으나 2번의 문제때문에 이것이 맞는것인지 검증하지 못하였습니다.

        2. 그리고, 또 하나 발생한 문제점은 레지스터 주소에 관한 것입니다. 주소값을 불러들여서 printf 하는 것은 잘 이해했습니다.
        그런데 핀에 매핑되는 직접적인 주소값을 이제껏 스캐매틱/매뉴얼 참고해서 알아내었던 터라, z-7020 매뉴얼 파일등을 살펴보니 메모리 맵 에 관해서 알아낸 것은 PL AXI slave port #0 : 0x4000_0000 ~ 0x7FFF_FFFF 뿐이더군요.
        그리고 현재 알고있는 것은 implementation까지 했을 때 제가 짠 모듈의 아웃풋이 할당되는 pin 위치(이름) 입니다.
        제가 발생된 아웃풋 값을 따로 메모리에 저장했다거나 임의로 만든 register 모듈을 이용하여 저장하지는 않았습니다. 이것이 문제가 될까요?
        저는 해당 핀을 바로 연결하여 읽어올 수 있을 줄로만 알았습니다. 그런데 찾아보니 pin number로는 바로 주소값을 알 수 없고, 블록다이어그램으로 IP 만들어 준 후, AXI address 할당을 해 주어야 만들어진다고 하는데.. 제가 찾아본 정보가 정확한지도 아리송하고, 그래도 일단 해당 방법을 시도해보았으나 기본을 모르니 실패할 수 밖에 없었습니다. 주소와 오프셋 개념은 아는데, 이런 방식이 참 어렵게 느껴집니다. 그래서 제가 여쭈고 싶은 것은, 어떻게 주소값을 알 수 있을까요?

        답변을 읽으며 예상 외로 간단히 풀릴 것이라 기대했는데 아직 제가 많이 부족한 탓에 또 이렇게 질문을 남기게 되었습니다. 다시 한 번 감사합니다.

        1. hwl님 안녕하세요.
          먼저, UART는 PS Core 안에 들어있는 기능입니다. 따라서 UART 전용 클럭을 별도로 공급해줄 필요는 없을겁니다.
          따라서 UART와 PL로직을 연결한다기보단, Zynq processing system과 설계하신 PL 로직을 연결해야 내부 레지스터를 읽을 수 있겠지요.
          그리고 CPU와 PL Logic과 통신하는 bus는 AXI, AHB, APB 등이 있을건데, AXI나 다른 프로토콜로 설계해보신 경험이 없는 것으로 보이는군요. 그럼 Memory Map에 대한 경험도 없는 것으로 보입니다. 그렇다면 근본적으로 CPU와 PL Logic과 통신하는 방법에 대한 기반을 구현하지 못했다는 이야기므로, UART로 내부 상태를 출력하는 것도 불가능합니다. CPU가 PL 로직에 접근해서 값을 읽은 다음, UART로 출력하는 흐름이니까요.
          둘 째, PMOD에 있는 핀을 출력 핀으로 설정한 것을 UART로 출력한다는 것..가능할까요? CPU가 PL 로직이 어떻게 되어있는지 모르는 상황이면 근본적으로 불가능한 일일 겁니다.
          이를테면, 아두이노로 작성자가 LED fadein/out 코드를 작성한다고 가정해보겠습니다.
          그렇다면 PWM 출력을 내보낼텐데(analogWrite()함수, 0~255까지의 범위를 가짐) 그 인자로 넣어주는 것을 UART로 출력해서 본다면 몇 단계로 보내는 지 알 수가 있겠죠. 순간 돌아가고 있는 내부 변수를 가져다가 printf해버리면 되니까요.
          근데, 작성자가 모르는 어떤 사람이 짜놓아서 돌아가고 있는 아두이노를 보니 LED가 천천히 밝아졌다가 어두워지는데, 이게 몇 단계 출력으로 움직이는지 작성자가 알 수가 있을까요? 그 순간이 몇 단계로 출력하고 있는지는 알 수가 없을겁니다.
          예시가 좀 이상하지만..;; 정리해 두겠습니다.

          1. 출력을 레지스터에 저장해둔다.
          2. PL로직을 CPU와 연결한다.
          3. CPU가 PL 로직에 접근해서 값을 읽는다.(1번)
          4. UART로 출력한다.

          아니면 출력핀을 오실로 스코프로 찍어보던가(…) 다른 놈으로 adc나 gpio로 읽어서 보든가 해야지요.

          불금 화이팅입니다. 🙂

  2. 좋은 정보 감사합니다 혹시 case문 대신 if 문을 사용한다면 따로 기능을 추가하는 코드가 있을까요?

    1. 안녕하세요. if문을 사용한다면 별도의 코드를 추가해야한다거나 하지는 않습니다.
      다만 case문 사용을 권장합니다. 합성할 때 if문보단 case문이 면적이 더 적기 때문입니다.
      그 까닭은 if문은 if ~ else if ~ else문으로 구성되는데, 이런 경우 우선순위가 생기게 됩니다. 따라서 mux가 chain형태로 구성될 확률이 높구요. 반면에 case문은 우선순위가 없기 때문에 동시성이 보장됩니다.
      참고하세요.

  3. 안녕하세요.
    FPGA로 작은 프로젝트를 하고 있는데요, 좋은 글 써주신 덕분에 많은 도움이 되었습니다.
    감사합니다.

댓글 남기기

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.