Semiconductor/0. FPGA

FPGA를 이용한 시계 만들기 (AXI4-Lite를 이용한 입력에 따른 Freq 조절)

잇(IT) 2024. 10. 4. 16:58

https://insoobaik.tistory.com/711

 

이전 글을 통해 Vivado에서 제공하는 AXI4-Lite Interface를 알아보았다.

 

이번 시간에는 AXI4-Lite를 이용하여 PS(Vitis)에서 값을 입력하면 해당 Freq가 PL(FPGA)의 Clock 모듈의 Clock으로 동작하여 시계가 동작하도록 구현할 것이다.


* Clock의 경우 100MHz를 사용한다.

 

Block Diagram

 

Watch_Top.v

먼저 시계를 담당하는 Module의 경우

 

Input

- clk

- rst

- run_en : HIGH일 때 시계 동작

- freq : 현재 Clock이 100MHz이고, 입력 freq를 설정하여 시계의 속도를 조절한다. (freq = 100000000일 경우 1초에 해당한다.)

 

Output

- sec

- min

- hour

Output을 LED에 연결하여 시각적으로 시계의 속도를 확인할 수 있다.


Watch_Top.v 내부 모듈

초, 분, 시를 측정할 방법은 여러가지가 있겠지만 가장 효율적인 측정 방법은 Cascading 방법이다.

(Cascading은 하나의 모듈 또는 신호가 다음 단계의 모듈 또는 신호에 영향을 주는 방식으로, 단계적으로 연결된 구조를 말한다.)

 

Watch_Top.v 내부에는 입력된 freq값을 기준으로 100MHz의 Clock을 통해 1초를 생성해낸다. (100MHz의 경우 freq가 100000000로 입력될 경우 실제 1초와 동일한 1초를 생성해낸다.)

 

Tick을 생성하는 모듈 하나를 기준으로 Parameter 값을 다르게 전달하여 (min(60sec), hour(60min)) 각 모듈에서 1 Tick을 발생하면 다음 모듈에 영향을 주도록 구성한다.

(one_sec_tick -> 1 sec_gen -> 60 sec tick = 1 min tick / 60 min tick = 1 hour tick)

 

sec, min, hour tick을 Output으로 내보내고 LED와 연결하여 추후에 시각적으로 확인할 것이다.

 

One_Sec_Gen

더보기
one_sec_gen
# (
    .P_COUNT_BIT    (P_COUNT_BIT)
) u_one_sec_gen(
    .clk                (clk            ),
    .reset              (reset          ),
    .i_run_en           (i_run_en       ),
    .i_freq             (i_freq         ),
    .o_one_sec_tick     (w_one_sec_tick )
);

1초를 생성하는 모듈을 통해 1초를 측정한다.

 

Tick_Gen

더보기
tick_gen
# (
    .P_DELAY_OUT    (2),
    .P_COUNT_BIT    (P_SEC_BIT),
    .P_INPUT_CNT    (60) // 60 sec
) u_tick_gen_sec(
    .clk                (clk            ),
    .reset              (reset          ),
    .i_run_en           (i_run_en       ),
    .i_tick             (w_one_sec_tick ),
    .o_tick_gen         (w_one_min_tick ),
    .o_cnt_val          (o_sec          )
);

위와 같이 tick_gen이라는 모듈 하나를 가지고 sec, min, hour를 계산할 수 있다.

one_sec_gen을 통해 생성된 1 tick을 60번 측정하게 되면 1 min을 만들 수 있고, 1 min이 60번 tick하게 되면, 1 hour를 측정할 수 있다.


전체 TOP.v 모듈

Address(Hex) Register Name Access Type Default Value Description
0x00 freq R/W 0x0 Clock Frequency 설정
0x04 time R 0x0 assign w_time = {15'b0, o_hour, o_min, o_sec);

unsigned value

[5:0] sec;
[5:0] min;
[4:0] hour;

PS와 통신하는 PL의 모듈은 위와 같다.

 

AXI Register

1. 0x00의 주소는 Read/Write가 가능하고, freq에 대한 Data를 저장하게 된다. PS에서 freq에 대한 입력이 들어오면 해당 Register에 저장하고, Watch 모듈에서 해당 freq를 참고하여 시계의 속도를 조절하게 된다.

2. 0x04의 주소는 Read만 가능하고, 시간을 실시간으로 전달받게 된다. freq Data를 전달받아 시계가 실시간으로 동작하게 되고, 시, 분, 초를 32bit Register에서 6bit, 6bit, 5bit(60초, 60분, 24시간)이 계산되어 각 bit에 저장된다. 저장된 값은 PS에서 Read 통해 Data를 전달받아 Uart 통신으로 Console 창을 통해 시간을 보여주게 된다.

 

Top Module

1. Top Module을 통해 100MHz의 Clock, Reset, Switch에 의한 Enable 값이 전달된다. 해당 신호들은 Watch_Top 모듈로 전달되어 Watch를 동작시키게 된다. (100MHz는 ZYNQ에서 설정한 Clock 값이다.)

2. ZYNQ에서 설정한 Clock값은 Top 전체 모듈을 관리하게 되고, AXI4-Lite의 경우에도 해당 Clock에 따라 동작하게 된다.

3. PS에서 freq에 대한 값을 입력하게 되면 AXI4-Lite Interface를 통해 0x00 AXI4-Lite Register에 저장된다. 저장된 freq 값은 Watch_Top 모듈에 의해 시계 속도를 조절하게 된다.

4. PL에서 freq를 받아 동작하는 시계의 sec, min, hour의 값은 PS에서 오청하게 되는데 이때 0x04에 저장된 time Data를 불러와 Uart 통신을 통해 Console 창에 띄우게 된다.

 

Time Read

더보기
always @(*)
    begin
          // Address decoding for reading registers
          case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
            2'h0   : reg_data_out <= slv_reg0;
            //2'h1   : reg_data_out <= slv_reg1;
            2'h1   : reg_data_out <= i_time;
            2'h2   : reg_data_out <= slv_reg2;
            2'h3   : reg_data_out <= slv_reg3;
            default : reg_data_out <= 0;
          endcase
    end

AXI4-Lite의 두번째 Address인 0x04번지를 통해 Time을 Read하도록 코드를 변경하였다.

 

Freq Read/Write

 

Write

더보기

printf("plz input freq (1 <= input <= 1GHz)\n");
     scanf("%d",&data);
     Xil_Out32((XPAR_TOP_0_BASEADDR) + (0*AXI_DATA_BYTE), (u32)(data));
     printf("BIS (hour : min : sec)\n");

Vitis를 통해 0x00 Address에 Xil_Out32 함수를 통해 freq 입력 값을 저장한다.

 

Read

더보기
always @(*)
    begin
          // Address decoding for reading registers
          case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
            2'h0   : reg_data_out <= slv_reg0;
            //2'h1   : reg_data_out <= slv_reg1;
            2'h1   : reg_data_out <= i_time;
            2'h2   : reg_data_out <= slv_reg2;
            2'h3   : reg_data_out <= slv_reg3;
            default : reg_data_out <= 0;
          endcase
    end
 
    assign o_freq = slv_reg0;
    // User logic ends

위에서 slv_reg0에 있는 값을 freq 값으로 전달하여 Watch_Top 모듈에 전달한다.


시연

1. FREQ : 100000000 입력

FREQ를 100000000를 입력하게 되면 Clock이 100MHz이기 때문에 실제 1초에 맞게 시계가 동작하는 것을 확인할 수 있다.

 

2. FREQ : 100000 입력

FREQ를 100000를 입력하게 되면 Clock이 100MHz이기 때문에 실제 1초보다 1000배 빠른 속도로 시계가 동작하는 것을 확인할 수 있다.


Cascading의 문제점

 

더보기

tick_gen
# (
.P_DELAY_OUT (0),
.P_COUNT_BIT (P_MIN_BIT),
.P_INPUT_CNT (60) // 60 min
) u_tick_gen_min(
    .clk (clk ),
    .reset (reset ),
.i_run_en (i_run_en ),
.i_tick (w_one_min_tick ),
    .o_tick_gen (w_one_hour_tick),
    .o_cnt_val (o_min )
);

Cascading 방식을 사용할 경우 sec 모듈의 tick이 min 모듈에 사용이 되고, min 모듈의 tick이 hour 모듈에 사용된다.

 

D/FF를 통해 저장된 값이 전달 되는 것이기 때문에 값을 전달 받을 때 (1) 한 Clock이 필요하고, 값을 전달 할 때 (2) 한 Clock이 필요하기 때문에 Cascading을 통해 Data를 전달하게 되면 한 Clock씩 Delay가 발생하게 된다. 

100MHz 중에 한 클럭의 Delay이기 때문에 아주 사소한 Delay라고 생각할 수 있지만 ns 단위로 Data 교환이 이루어 지는 Chip에서는 이러한 Delay가 누적되면 치명적인 에러를 발생 시킬 수 있다.

 

- sec -> min

1 Clock만큼 Delay

- min -> hour

1 Clock만큼 Delay

- sec -> hour

2 Clock만큼 Delay가 발생하기 때문에 

 

각 모듈에 Input 값에 대해 해당 신호가 Output으로 전달되는 시점에 Delay를 주고 되면 Cascading 방식에서 발생하는 Delay를 해결할 수 있다.

더보기

reg [P_COUNT_BIT-1:0] r_cnt_val_d [P_DELAY_OUT-1:0];
always @(posedge clk) begin
r_cnt_val_d[0] <= r_cnt_val;
end
for(gi = 1; gi < P_DELAY_OUT; gi = gi + 1) begin : gen_delay
always @(posedge clk) begin
r_cnt_val_d[gi] <= r_cnt_val_d[gi-1];
end
end
assign o_cnt_val = r_cnt_val_d[P_DELAY_OUT-1];

위 코드와 같이 Delay 값을 각 모듈마다 Parameter로 전달받아 내부에 별도의 FF을 생성하여 값을 한 번 거쳐 Output으로 전달하게 되면 들어오는 신호에 대해 출력을 같은 타이밍에 전달할 수 있게 된다.

 

728x90