반응형

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

 

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

Verilog modules required to get the OV7670 camera working - westonb/OV7670-Verilog

github.com

를 참고했다.

 

SCCB가 어떻게 움직이는지 보기 위해 modelsim 으로 SCCB_interface_tb.v 를 시뮬레이션 돌려 보았다.

 

봐야 할 코드 부분은 다음과 같다.

 

SCCB_interface 
    #(.CLK_FREQ(800), .SCCB_FREQ(20)) 
    dut1
    (
    .clk(clk),
    .start(start),
    .address(address),
    .data(data),
    .ready(ready),
    .SIOC_oe(SIOC_oe),
    .SIOD_oe(SIOD_oe)
    );
    
    always #5 clk=~clk;
    
    initial begin
        clk = 0;
        start = 0;
        address = 0;
        data = 0;
        
        #100;
        address = 8'hAA;
        data = 8'h77;
        start = 1;
        
        #10;
        start = 0;
        address = 0;
        data = 0;
    end

 

시뮬레이션용으로 Clock freq를 800, bus freq를 20으로 설정해 뒀다.

클럭은 10ns 마다 pos edge 를 가진다.

 

처음 clk, start, address, data 가 0이고

 

100 ns 가 지나면 address에 AA, data에 77, 시작 값을 가진다.

 

그리고 그 후 10ns 가 지나면 start, address, data를 0으로 돌린다.

 

 이런 경우 아마 내부에 ready 플래그를 둬서 start 입력에도 내부 동작이 끝나지 않았다면 동작하지 않게 만들 가능성이 크다.

 

SCCB interface 코드를 한 번 보도록 한다.

 

module SCCB_interface
#(
    parameter CLK_FREQ = 25000000,
    parameter SCCB_FREQ = 100000
)
(
    input wire clk,
    input wire start,
    input wire [7:0] address,
    input wire [7:0] data,
    output reg ready,
    output reg SIOC_oe,
    output reg SIOD_oe
    );
    
    localparam CAMERA_ADDR = 8'h42;
    localparam FSM_IDLE = 0;
    localparam FSM_START_SIGNAL = 1;
    localparam FSM_LOAD_BYTE = 2;
    localparam FSM_TX_BYTE_1 = 3;
    localparam FSM_TX_BYTE_2 = 4;
    localparam FSM_TX_BYTE_3 = 5;
    localparam FSM_TX_BYTE_4 = 6;
    localparam FSM_END_SIGNAL_1 = 7;
    localparam FSM_END_SIGNAL_2 = 8;
    localparam FSM_END_SIGNAL_3 = 9;
    localparam FSM_END_SIGNAL_4 = 10;
    localparam FSM_DONE = 11;
    localparam FSM_TIMER = 12;
    
    
    initial begin 
        SIOC_oe = 0;
        SIOD_oe = 0;
        ready = 1;
    end
    
    reg [3:0] FSM_state = 0;
    reg [3:0] FSM_return_state = 0;
    reg [31:0] timer = 0;
    reg [7:0] latched_address;
    reg [7:0] latched_data; 
    reg [1:0] byte_counter = 0;
    reg [7:0] tx_byte = 0;
    reg [3:0] byte_index = 0;
    
    
    always@(posedge clk) begin
        
        case(FSM_state) 
        
            FSM_IDLE: begin
                byte_index <= 0;
                byte_counter <= 0;
                if (start) begin 
                    FSM_state <= FSM_START_SIGNAL;
                    latched_address <= address;
                    latched_data <= data;
                    ready <= 0;
                end
                else begin
                    ready <= 1;
                end
            end
            
            FSM_START_SIGNAL: begin //comunication interface start signal, bring SIOD low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_LOAD_BYTE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
                SIOD_oe <= 1;
            end
            
            FSM_LOAD_BYTE: begin //load next byte to be transmitted
                FSM_state <= (byte_counter == 3) ? FSM_END_SIGNAL_1 : FSM_TX_BYTE_1;
                byte_counter <= byte_counter + 1;
                byte_index <= 0; //clear byte index
                case(byte_counter)
                    0: tx_byte <= CAMERA_ADDR;
                    1: tx_byte <= latched_address;
                    2: tx_byte <= latched_data;
                    default: tx_byte <= latched_data;
                endcase
            end
            
            FSM_TX_BYTE_1: begin //bring SIOC low and and delay for next state 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_2;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 1; 
            end
            
            FSM_TX_BYTE_2: begin //assign output data, 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_3;
                timer <= (CLK_FREQ/(4*SCCB_FREQ)); //delay for SIOD to stabilize
                SIOD_oe <= (byte_index == 8) ? 0 : ~tx_byte[7]; //allow for 9 cycle ack, output enable signal is inverting
            end
            
            FSM_TX_BYTE_3: begin // bring SIOC high 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_4;
                timer <= (CLK_FREQ/(2*SCCB_FREQ));
                SIOC_oe <= 0; //output enable is an inverting pulldown
            end
            
            FSM_TX_BYTE_4: begin //check for end of byte, incriment counter
                FSM_state <= (byte_index == 8) ? FSM_LOAD_BYTE : FSM_TX_BYTE_1;
                tx_byte <= tx_byte<<1; //shift in next data bit
                byte_index <= byte_index + 1;
            end
            
            FSM_END_SIGNAL_1: begin //state is entered with SIOC high, SIOD high. Start by bringing SIOC low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_2;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 1;
            end
            
            FSM_END_SIGNAL_2: begin // while SIOC is low, bring SIOD low 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_3;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOD_oe <= 1;
            end
            
            FSM_END_SIGNAL_3: begin // bring SIOC high
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_4;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
            end
            
            FSM_END_SIGNAL_4: begin // bring SIOD high when SIOC is high
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_DONE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOD_oe <= 0;
            end
            
            FSM_DONE: begin //add delay between transactions
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_IDLE;
                timer <= (2*CLK_FREQ/(SCCB_FREQ));
                byte_counter <= 0;
            end
            
            FSM_TIMER: begin //count down and jump to next state
                FSM_state <= (timer == 0) ? FSM_return_state : FSM_TIMER;
                timer <= (timer==0) ? 0 : timer - 1;
            end
        endcase
    end
    
    
endmodule

 

위의 테스트벤치에서 CLK_FREQ를 800, SCCB_FREQ를 20으로 제한했다.

 

initial begin 
        SIOC_oe = 0;
        SIOD_oe = 0;
        ready = 1;
    end

내부 파라미터는 넘어가고, SIOC_oe, SIOD_oe, ready 를 init 에서 할당해주고 있다. 순차적으로 안 해도 될 것 같은 부분이다.

 

State machine 을 한 번 본다.

FSM_IDLE: begin
                byte_index <= 0;
                byte_counter <= 0;
                if (start) begin 
                    FSM_state <= FSM_START_SIGNAL;
                    latched_address <= address;
                    latched_data <= data;
                    ready <= 0;
                end
                else begin
                    ready <= 1;
                end
            end

 

IDLE 상태에서 입력(start)이 들어오면 다음 상태로 변화시키는 코드다.

기본 값들은 초기화 혹은 리셋 시켜주면서, 입력이 들어오면 현재 address, data 를 레지스터에 저장한다.

뒤에서 이 값들(latched_address, latched_data)을 변화시키는 부분이 없었으므로 계속 값을 가져갈 것으로 보인다.

 

FSM_START_SIGNAL: begin //comunication interface start signal, bring SIOD low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_LOAD_BYTE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
                SIOD_oe <= 1;
            end

 

START_SIGNAL 부분은 IDLE에서 start 입력이 들어오면 다음 클럭에서 state가 변화되는 부분이다.

특이하게 return state 를 지정하고 있는데, 이는 다음 state 인 TIMER 로 인해 필요한 부분이다.

또, timer 는 FPGA의 클럭과 버스를 동기화하기 위해 사용하고, 

SIOC_oe(SCL) 은 0을 유지하고, SIOD_oe(SDA) 는 1로 올라간다. 이는 SCCB의 start signal이다.

I2C 와 다른 인터페이스인 이유이기도 하다.

 FSM_TIMER: begin //count down and jump to next state
                FSM_state <= (timer == 0) ? FSM_return_state : FSM_TIMER;
                timer <= (timer==0) ? 0 : timer - 1;
            end

 

자주 등장하는 TIMER state 를 본다.

다른 건 없고, 시간 때우기 용으로 넣어 놓은 state 라고 보면 좋다.

timer 가 0이 될 때 까지 현재 상태를 유지하는데, 이 때 SCL, SDA 값도 유지된다.

위의 waveform 에서 FSM_state 가 1100(decimal 값으로 12)에서 길게 유지하는 것을 볼 수 있다.

 실제로 파형을 보면 timer 이외에는 FSM_state 가 1100(binary) 인 때는 움직이지 않고 유지하는 것을 볼 수 있음.

 

timer가 0이 되어 return_state 로 돌아가도록 한다.

현재 return_state 는 FSM_LOAD_BYTE 로 지정되어 있다.

FSM_LOAD_BYTE: begin //load next byte to be transmitted
    FSM_state <= (byte_counter == 3) ? FSM_END_SIGNAL_1 : FSM_TX_BYTE_1;
    byte_counter <= byte_counter + 1;
    byte_index <= 0; //clear byte index
    case(byte_counter)
        0: tx_byte <= CAMERA_ADDR;
        1: tx_byte <= latched_address;
        2: tx_byte <= latched_data;
        default: tx_byte <= latched_data;
    endcase
end

 

FSM_state 가 byte_counter 가 3이면 분기하는 조건이 있는걸 봐선 여기서 바이트 카운터를 올리는 것이 있던지, 다른 state 에서 바이트 카운터를 올리는 옵션이 있을 것이다.

실제로 다음 코드에서 바이트코드를 증가시키는 코드가 있다.

그리고 뭔지 모르겠지만 바이트 인덱스를 0으로 만든다. 다른 곳에서 바이트 인덱스를 사용하고 여기 다시 돌아와서 뭔가 한다는 의미일 확률이 높겠다.

그리고 바이트카운터에 따라서 tx_byte 의 값을 조정한다.

 

CAMERA_ADDR 은 localparam 에서 42로 지정했다.

 

현재 LOAD_BYTE 에서의 값은 다음과 같다.

 

현재 막 LOAD_BYTE에 도착했다.

다음 클럭에서 바뀌는 값들을 보도록 한다.

FSM_state 는 byte_counter 가 3이 아니므로(0임) FSM_TX_BYTE_1

byte_counter 은 다음 클럭에 1로

byte_index 는 다음 클럭에 0으로

tx_byte 는 다음 클럭에 CAMERA_ADDR 의 값을 가짐.

non-blocking(<=) 이기 때문에 순차적 처리가 아닌 병렬 처리를 함.

 

 

실제 다음 클럭의 값임.

 

이번에 온 TX_BYTE_1 을 살펴본다.

 

FSM_TX_BYTE_1: begin //bring SIOC low and and delay for next state 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_2;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 1; 
end

 

다음 클럭에 SCL 을 변화시킨다. 실제로 통신을 시작하려는 부분임.

이 부분에서 SCL = 1, SDA = 1 이 되므로 통신 시작, 이제 8비트의 데이터를 보내고 1비트의 ACK 를 수신해야 함.

하지만 다음 state 가 TIMER 로 다시 간다.

이로 인해 SCCB 인터페이스와 FPGA 사이의 클럭 차이가 굉장히 많이 나기 때문에 클럭 손실이 크다.

따라서 I2C 를 느려터진 인터페이스라고 하는 이유다.

 

현재 코드 상에서는 시뮬레이션이 아닌, 기기에서의 클럭을 25Mhz, SCCB freq 를 100Khz 로 정의해 놓았다.

timer 에서 SCCB 주파수에 4를 곱하는 이유는 아마 I2C 처럼 고속 모드(400Khz) 지원을 위함이지 싶다.

아무튼 저 값을 나누면 62.5 가 나오는데 소수 버림으로 62 가 나온다.

아무것도 안하고 벌써 62 클럭을 2번이나 낭비했다.

 

아무튼 return state 를 TX_BYTE_2 로 정의하고 다시 timer 에 갔다 온다.

 

FSM_TX_BYTE_2: begin //assign output data, 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_3;
    timer <= (CLK_FREQ/(4*SCCB_FREQ)); //delay for SIOD to stabilize
    SIOD_oe <= (byte_index == 8) ? 0 : ~tx_byte[7]; //allow for 9 cycle ack, output enable signal is inverting
end

 

timer 에 갔다 다시 돌아온 state 다.

근데 다음 state 가 다시 timer 이다.

여기서는 잘 보니, 한 바이트에 대해 처리하고 있는 것을 볼 수 있다.

SDA 에 byte index 가 8(9번째 비트, ACK) 가 아니면 tx_byte[7]을 반전시켜 넣고 있다.

 

return state 를 TX_BYTE_3으로 지정하고 다시 timer 로 간다. (62*3)

 

FSM_TX_BYTE_3: begin // bring SIOC high 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_4;
    timer <= (CLK_FREQ/(2*SCCB_FREQ));
    SIOC_oe <= 0; //output enable is an inverting pulldown
end

 

이번에도 다음 state 는 TIMER 이다.

여기서는 SCL 을 0으로 바꾼다. 

다른 건 없다.

return state 를 TX_BYTE_4 로 지정하고 다음 timer 로 간다. (62*4)

 

 

 

FSM_TX_BYTE_4: begin //check for end of byte, incriment counter
    FSM_state <= (byte_index == 8) ? FSM_LOAD_BYTE : FSM_TX_BYTE_1;
    tx_byte <= tx_byte<<1; //shift in next data bit
    byte_index <= byte_index + 1;
end

 

byte index 가 8이 아니면(ack) 다시 TX_BYTE_1 로 돌아간다. 아니면 LOAD_BYTE 로 간다.

tx_byte 는 하나 shift 하는데, 이는 이미 우리가 반전시킨 tx_byte[7] 을 SDA 로 보냈기 때문이다.

그리고 byte_index 를 1 증가시킨다.

 

대략적으로 다음과 같은 구조가 되었다.

 

결국 TX_BYTE_1 ~ TX_BYTE_4 를 돌면서 1 Bit 를 보낸 것과 같다.

근데 TX_BYTE 에 사용한 클럭은 

LOAD, TX1, TX2, TX3, TX4, TIMER*3 = 5 + 186 = 191 clock 이다.

25Mhz cpu 를 사용하면 7,640 ns = 7.64us 가 소요된 것이다.

이걸 이제 byte_index 가 8이 될 때 까지, 총 9번 실행해 준다.

그럼 이제 CAMERA_ADDRESS 를 SCCB interface 로 보내는 것이 완료 된 것이다.

 

그리고 다시 LOAD_BYTE 로 오게 된다.

byte_counter 에 따라, 3이 될 때 까지 반복한다. (기기 address, 내부 레지스터 address, 쓸 값)

 

3이 되었으면, END_SIGNAL_1 로 간다.

 

FSM_END_SIGNAL_1: begin //state is entered with SIOC high, SIOD high. Start by bringing SIOC low
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_2;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 1;
end

 

여기서는 통신을 종료하기 위해 STOP 시그널을 만드려는 쪽 같다.

SCL 을 1로 만든다.

return state 를 END_SIGNAL_2 로 지정하고, timer 로 간다.

파형으로 보면 저 위치이다.

 

자세하게 보면 이렇다.

 

FSM_END_SIGNAL_2: begin // while SIOC is low, bring SIOD low 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_3;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOD_oe <= 1;
end

 

이번엔 SDA를 1로 만든다.

return state 를 END_SIGNAL_3 으로 지정하고 timer 로 간다.

파형은 다음과 같다.

 

FSM_END_SIGNAL_3: begin // bring SIOC high
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_4;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 0;
end

 

이번에 SCL 을 다시 0으로 만든다.

return state 를 END_SIGNAL_4 로 지정하고 timer 로 간다.

파형은 다음과 같다.

 

 

FSM_END_SIGNAL_4: begin // bring SIOD high when SIOC is high
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_DONE;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOD_oe <= 0;
end

 

마지막으로 SDA 를 0으로 내린다.

주석 적어놓은 거 보면 I2C 를 완전히 반대로 구현해 놓은 것과 동일한 것 같다.

timer 를 지나고, 마지막 done state 로 간다.

 

I2C를 보게 되면 마지막이 모두 HIGH로 끝나고, SCCB 는 정 반대이다.

 

FSM_DONE: begin //add delay between transactions
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_IDLE;
    timer <= (2*CLK_FREQ/(SCCB_FREQ));
    byte_counter <= 0;
end

 

Done state 는 다음 트랜잭션이 오기 전의 딜레이 역할을 한다.

딱히 byte_counter 를 초기화 안 해도 동작에는 지장이 없을 것 같다.(IDLE에서 초기화를 하기 때문)

시간이 저렇게 표기되어 있는데 작동에 준수해야 되는 최소 시간인지는 잘 모르겠다.

 

 

반응형

'Verilog' 카테고리의 다른 글

[Verilog] OV7670 - more2  (0) 2024.05.28
[Verilog] SCCB Interface & OV7670  (0) 2024.04.16
반응형

오늘은 Vivado tool을 이용해 Sim & Run 까지 실행 해 본다.

먼저, 새로 프로젝트를 생성한다.

 

 

이름은 led_pwm 으로 지정해 주었다.

프로젝트 타입 : RTL

소스 X

보드 선택 : xc7z010clg400-1

 

 

프로젝트를 생성했다.

led_pwm.v 파일을 생성해 준다.

Design source 추가 > Create file

 

생성한 led_pwm 파일을 열어준다.

소스 코드는 다음과 같이 작성했다.

 

led_pwm.v

module led_pwm(
    input i_Clk,
    input i_Rst,
    output o_LED
    );

// 시뮬레이션용으로 [7:0], 1ms sim run
reg [21:0] c_Cnt, n_Cnt;
// 시뮬레이션용으로 [4:0]
reg [9:0] c_pwm_period, n_pwm_period, pwm_counter;
reg r_LED;
    
always@(posedge i_Clk, negedge i_Rst)
if(!i_Rst) begin
    c_Cnt <= 0;
    c_pwm_period <= 0;
end else begin
    c_Cnt <= n_Cnt;
    c_pwm_period <= n_pwm_period;
end    
        
always@* begin
    n_Cnt <= c_Cnt + 1;
    n_pwm_period <= c_pwm_period;
    if(&c_Cnt)  n_pwm_period <= c_pwm_period + 10;    
end

always@(posedge i_Clk, negedge i_Rst) 
if(!i_Rst) begin
    pwm_counter <= 0;
end else begin
    pwm_counter <= pwm_counter + 1;
    if(pwm_counter < c_pwm_period)  r_LED <= 1;
    else                            r_LED <= 0; 
    if(&pwm_counter) pwm_counter <= 0;  
end

assign o_LED = r_LED;    




endmodule

 

 

 

 

simulation 에 사용할 tb_led_pwm.v 도 생성해 준다.

 

Testbench 파일 생성을 위해 simulation sources 를 만든다.

이름은 tb_led_pwm 으로 지정한다.

 

 

내용은 다음과 같다. (simulation sources 에 파일이 있다)

tb_led_pwm.v

module tb_led_pwm();

reg clk, rst;
wire led;

led_pwm U0(clk, rst, led);

always
#10 clk = ~clk;

initial begin
    clk <= 0; 
    rst <= 0; 
    @(negedge clk) rst <= 1;
end

endmodule

 

/  \

| 시뮬레이션  |

\_ _ _ _ _ _ _ /

시뮬레이션은 왼쪽의 SIMULATION > Run Simulation > Run behavior simulation 에서 가능하다.

누르면 다음과 같은 창이 나온다.

 

보기 편하도록 objects, scope 탭을 최소화한다.

 

위의 메뉴 탭을 보면 이게 시뮬레이션에서 사용하는 도구들이다.

Start(T) 버튼, 가장 오른쪽의 Relaunch simulation,  가장 왼쪽의 Restart 만 일단 써 보도록 한다.

처음에 10 us 로 지정이 되어 있을텐데 1 ms로 바꿔 주고 Start(T) 를 눌러준다.

 

다음의 Zoom fit 버튼을 눌러준다.

 

 

그러면 시뮬레이션이 실행되는데 이렇게 되어 있으면 정확하게 보고 판단할 수 없으므로 Zoom 을 이용해 세부 값을 알아본다.

나는 보통 왼쪽 Ctrl 을 누르고 마우스 휠을 돌리는게 편해서 자주 그렇게 사용한다.

 

 

보게 되면 led가 변하는 모습을 확인할 수 있다. 코드가 주기성을 가지므로 시뮬레이션 결과도 주기성을 가진다.

만약 버그로 led 표기가 제대로 되지 않는다면 relaunch simulation 버튼을 눌러 다시 실행한다.

코드를 수정해도 relaunch를 해 줘야 바뀐 코드가 정상 적용된다. Restart 로는 불가능하다.

 

 

다음은 보드에서 실행되는 모습을 확인한다.

합성 진행해 준다.

I/O 포트는 다음과 같다.

 

 

Implementation 진행 해 준다.

이후 bitstream 만들고 HW manager 켜서 테스트까지 한다.

 

 

잘 동작하는 것을 볼 수 있다.

조금 더 세부 조정을 위해서는 카운팅하는 값을 내리면 된다.

반응형

+ Recent posts