52 / 52 · 개념
종합 실습: UART 송신기와 프레임 타이밍
분주 카운터·시프트 레지스터·흐름 제어를 결합해 8N1 프레임을 만들고 비트 경계를 검증합니다.
내용은 무료로 볼 수 있습니다. 수강 신청하면 학습 기록을 저장할 수 있어요.
프레임 명세를 먼저 고정합니다
선수 지식: 카운터, 시프트 레지스터, valid/ready, 동기식 리셋.
UART의 8N1 형식은 시작 비트 1개, 데이터 8개, parity 없음, 정지 비트 1개입니다. idle과 stop은 1, start는 0이며 데이터는 LSB부터 보냅니다. 모든 비트는 동일한 길이로 유지합니다. 이 실습은 송신만 다루며 같은 클록의 데이터 제공자를 가정합니다.
48MHz에서 115200 baud를 목표로 하면 정수 분주값 D=417을 선택할 수 있습니다. 실제 속도는 약 115108 baud, 오차는 약 -0.080%입니다. 수신 측까지 포함한 허용 오차는 장치 사양으로 검토해야 합니다.
0x53을 실제 전송 순서로 펼치기
0x53의 표기는 01010011이지만, 선로에는 낮은 비트부터 1, 1, 0, 0, 1, 0, 1, 0 순서로 나갑니다. 그림에서 한 칸은 시스템 클록 한 주기가 아니라 D클록으로 이루어진 비트 기간입니다. 앞뒤 idle 구간은 비교를 위해 한 칸씩 표시했습니다.
파형 데이터 보기
| 신호 | 파형 | 버스 값 |
|---|---|---|
| tx | 101.0.10101. | |
| phase | 234523452345 | idle → start → d0 → d1 → d2 → d3 → d4 → d5 → d6 → d7 → stop → idle |
start부터 stop까지는 정확히 10D클록입니다. 연속된 1이나 0도 각각 한 비트 기간을 차지하며, 선로에 전이가 없다고 비트가 사라지는 것은 아닙니다.
데이터 한 바이트를 한 번만 캡처합니다
아래 구현은 CLKS_PER_BIT가 1 이상의 상수라는 계약입니다. busy일 때 입력은 수락하지 않고, 프레임 시작 시 캡처한 data만 사용합니다. bit-clock을 새 클록으로 만들지 않고 원래 클록 안에서 카운터로 이동 시점을 제어합니다.
module uart_tx #(
parameter int unsigned CLKS_PER_BIT = 417,
localparam int CW = (CLKS_PER_BIT <= 1) ? 1 : $clog2(CLKS_PER_BIT)
) (
input logic clk, rst,
input logic in_valid,
output logic in_ready,
input logic [7:0] in_data,
output logic tx, busy, done
);
logic [9:0] frame;
logic [3:0] bits_left;
logic [CW-1:0] ticks_left;
assign in_ready = !busy;
always_ff @(posedge clk) begin
if (rst) begin
tx <= 1'b1;
busy <= 1'b0;
done <= 1'b0;
bits_left <= 4'd0;
ticks_left <= '0;
end else begin
done <= 1'b0;
if (!busy) begin
if (in_valid) begin
frame <= {1'b1, in_data, 1'b0};
tx <= 1'b0;
busy <= 1'b1;
bits_left <= 4'd10;
ticks_left <= CW'(CLKS_PER_BIT-1);
end
end else if (ticks_left != 0) begin
ticks_left <= ticks_left - 1'b1;
end else if (bits_left == 4'd1) begin
tx <= 1'b1;
busy <= 1'b0;
done <= 1'b1;
end else begin
frame <= {1'b1, frame[9:1]};
tx <= frame[1];
bits_left <= bits_left - 1'b1;
ticks_left <= CW'(CLKS_PER_BIT-1);
end
end
end
endmodule입력 수락 에지를 E0라고 하면 tx가 start=0으로 바뀝니다. 이후 정확히 D개 클록 간격으로 다음 비트로 이동합니다. E0+9D에서 stop 비트가 시작되고 E0+10D에서 busy가 내려가며 done이 한 사이클 발생합니다. 마지막 stop 구간도 D주기를 채워야 합니다.
이 구현은 종료 에지 직전까지 in_ready=0이므로 다음 프레임은 가장 빨라도 그 다음 에지에 수락합니다. 따라서 연속 프레임 수락 간격은 클록입니다. 이 한 클록의 idle 간격도 정상 동작의 일부입니다.
비트열을 손으로 예측합니다
8'h53의 데이터 비트는 LSB부터 1,1,0,0,1,0,1,0입니다. 전체 프레임은 다음 순서입니다.
| 순서 | start | d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 | stop |
|---|---|---|---|---|---|---|---|---|---|---|
| tx | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 |
검증에서는 tx가 바뀐 횟수만 세면 안 됩니다. 연속된 같은 값의 비트에서는 전환이 없기 때문입니다. 프레임 시작을 기준으로 각 비트 구간의 중앙을 샘플링해 예상 비트와 대조하고, start·stop의 길이도 확인합니다. 빠른 단위 검증에는 D=1, 2, 3 같은 작은 값도 사용하세요.
FIFO와 연결하는 확장 설계
등록형 읽기 FIFO는 pop 수락 에지 직후 dout과 dout_valid를 갱신합니다. 같은 클록의 다음 회로는 그 다음 상승 에지에서 응답을 샘플링합니다. pop을 수락하는 바로 그 에지의 dout을 UART에 보내면 이전 값일 수 있습니다. 반환된 바이트를 보관하는 1칸 버퍼와 pending 상태를 두고, UART가 ready일 때 정확히 한 번 전달하도록 연결하세요.
최종 검증에는 00·FF·01·80·55·53, busy 중 바뀌는 입력, 연속 프레임, 프레임 중 reset을 포함합니다. reset은 전송을 중단하고 tx를 idle=1로 되돌린다는 정책입니다. 수신기는 중단된 프레임을 정상 데이터로 취급하면 안 됩니다.
직접 생각해 보기
CLKS_PER_BIT=4이고 E0에서 데이터를 수락했습니다. d0 시작, stop 시작, done 발생 에지를 각각 구하세요. E40에서 새 in_valid=1이면 그 에지에 다음 바이트가 수락됩니까?
해설 보기
d0는 E4, stop은 E36, done은 E40 직후입니다. E40 직전에는 busy=1이어서 in_ready=0이므로 새 바이트는 아직 수락되지 않습니다. 송신자는 valid와 data를 유지해야 하며, 다음 바이트는 E41에서 수락할 수 있습니다. 각 프레임은 40클록이고 수락 간격은 최소 41클록입니다.