Semiconductor/0. ASIC Flow

ASIC Flow - SPI 설계, 신호 검증 (ILA) & (Cadence) Synthesis ~ POST SIM

잇(IT) 2024. 7. 10. 10:04

https://insoobaik.tistory.com/571

 

SPI - Serial Peripheral Interface 통신

보호되어 있는 글입니다. 내용을 보시려면 비밀번호를 입력하세요.

insoobaik.tistory.com

SPI 이론과 관련된 내용은 위 글을 참조한다.


SPI Slave (FPGA)

Slave로 들어오는 bit는 총 32bit가 들어오게 된다.

0~1 bit : ID 값 전달 bit

2 bit : READ/WRITE 값 전달 bit (0 READ / 1 WRITE)

3 bit : ADDRESS 값 전달 bit (현재 간단한 통신 테스트를 위해 2개의 16bit Register를 사용하는 예시에 해당하며, 0일경우 0번 Register, 1일경우 1번 Register에 접근

4~15 bit :  TMP bit로 추후 추가될 조건에 대한 데이터를 담기위해 만든 임시 데이터 bit

16~31 bit : 실제 Data 값 전달 bit 

spi_slave 모듈을 통해 총 5개의 Input Pin을 통해 값이 전달되며, 1개의 Output Pin을 통해 Master로 값을 전달하게 된다.

 

spi_slave의 FSM의 경우 3가지 상태가 존재한다.

1. READY

2. READ

3. WRITE

* (아무일도 일어나지 않는 IDLE 상태도 추가해주는 것이 좋지만 현재는 READY 상태로 바로 넘어가도록 코드를 작성하였다.)

 

1. READY의 경우  CS가 High에서 Low로 떨어진 뒤 Master와 Slave가 통신을 시작하고, Read, Write 모드가 정해지기 전, ID를 받고, ID가 맞는지 확인하는 상태에 해당한다. Slave ID가 일치하는 경우 다음 상태가 변경되며, 일치하지 않을 경우 초기 상태로 돌아가게 된다. 

2. READ의 경우 ADDRESS bit를 통해 전달받은 Register에 저장되어 있는 DATA를 받아오는 상태에 해당한다.

3. Write의 경우 ADDRESS bit를 통해 전달받은 Register에 입력 받은 DATA를 저장하는 상태에 해당한다.

 

spi_slave.v (전체 코드는 글 마지막 부분에 있습니다.)

FSM State에 맞게 Input으로 들어오는 값에 대해 counter 비트에 따라 READ, WRITE를 진행하게 된다.

bitcnt 변수를 통해 bitcnt == 2일 때, ID 값, bitcnt == 3일 때 READ/WRITE 값, bitcnt == 4일 때 ADDRESS 값을 읽어오고 bitcnt == 3일 때 결정된 READ/WRITE 상태에 따라 bitcnt == 4일 때 전달되는 값을 통해 ADDRESS에 해당하는 Register에 접근하여 DATA를 READ or WRITE하게 된다.

이후 bitcnt >= 16부터 한 bit씩 전달되는 값을 Reigster에 READ, WRITE를 하게되며, miso Pin을 통해 전달한다.

 

tb_spi_slave.v (전체 코드는 글 마지막 부분에 있습니다.)

Test Bench 코드의 경우 실제 마스터에서 신호를 보내는 것처럼 Input 신호를 직접 입력하여 spi_slave 코드에게 전달하게 된다.

총 2번의 Write와 2번의 Read가 실행되도록 코드를 작성하였다.


Testbench Simulation

1. 처음 CS 신호가 0으로 떨어지는 순간 Slave와 Master가 통신을 시작하게 된며 mosi를 통해 Master로부터 전달되는 데이터를 해석하기 위해 bitcnt가 1씩 커지기 시작한다.

2. bitcnt == 2 일 때 mosi로 전달된 2bit값을 통해 slave의 id 값과 일치하는지 확인한 뒤 일치하게 되면 id_check를 1(ID가 일치했다는 의미)로 변경한다. id_check를 통과하지 못하면 Ready 상태로 되돌아가게 된다.

3. bitcnt == 3(sclk posedge 상승 엣지)일 때(빨간색) mosi를 통해 들어오는 bit를 통해 READ/WRITE 값을 지정하고, 다음 FSM 상태 (next_state)에 READ/WRITE에 맞는 상태를 저장한다. (현재 Simulation에서는 next_state == 2 즉, WRITE 상태로 변경된다.)

bitcnt == 3->4(sclk negedge 하강 엣지)일 때(파란색) current_state (현재 FSM 상태)를 next_state로부터 받아와 변경한다.

bitcnt == 4(sclk posedge 상승 엣지)일 때 bitcnt == 4가 될 때 mosi 신호로 들어온 ADDRESS 값을 읽어 READ/WRITE를 수행할 Memory ADDRESS 값을 지정한다.

4. 현재 위 Simulation은 WRITE 상태에 해당하고, 아래 작성된 spi_slave 코드를 확인하게 되면 WRITE 상태일 때 bitcnt == 32인 상태에서 sclk가 상승 엣지일 때, mosi를 통해 한비트씩 들어온 32비트 중 뒤 16bit 데이터를 received_data라는 레지스터에 저장해서 상승이 일어나는 순간 ADDRESS로 지정된 Memory에 해당 16bit 데이터를 저장하게 된다. (현재 1번 Memory로 지정된 상태)

 

5. 32비트 전송 뒤 CS가 다시 1이 되면 Master / Slave 통신이 끝나게 되며, 기존의 값들이 전부 초기화 된며, CS값이 다시 0이 되는 순간 Master / Slave 통신을 다시 시작하게 된다.

6. 이전에 동작한 것과 동일하게 (첫번째 빨간 네모) ID 값을 Check하고, (두번째 빨간 네모) 그 다음 posedge에서 State 값을 받아오고, (세번째 빨간 네모) negedge에서 State 값을 변경한다. (네번째 빨간 네모)에서 ADDRESS 값을 받아 저장하게 된다. 

5. 위 Simulation은 READ 상태에 해당하며 WRITE 상태에서 저장한 6666의 값을 miso를 통해 전달하고 있는 모습이다. Register에 저장된 6666 (0110 0110 0110 0110) 값의 신호를 전달하는 것을 볼 수 잇다 miso를 통해 READ 상태에서의 Data를 전부 전송하고 난 뒤 CS는 다시 1이 되고, 상태와 값이 전부 초기화 되는 것을 확인할 수 있다.


RTL ANALYSIS

위에서 작성한 spi_slave 코드를 VIVADO 툴을 통해 게이트 수준으로 변경한 화면이다.

그 중 일부를 보게 되면

1. bitcntO_i의 경우 [5:0]의 크기를 가지고 있고, 풀업 저항을 통해 1씩 계속 더해지는 것을 볼 수 있다.

2. received_dataO_i의 경우 I0[5:0]의 값과 I1[5:0](100001 == 32)의 값과 비교하여 참이면 1을 거짓이면 0의 값을 전달하고 있다.bitcnt_i mux를 통해 received_dataO_i에서 나온 값에 따라 0이 mux를 통해 전달되면 bitcntO_i로부터 전달 받은 값을 내보내고, 1이 전달되면 I1[5:0]의 값을 내보내게 된다.

3.  그 뒤 bitcnt_reg[5:0] D Filp/Flop을 통해 전달 받은 값을 저장하고, sclk 클럭에 따라 새롭게 들어온 값을 저장하거나, 기존의 값을 계속해서 저장한채로 내보내게 된다.


FPGA / 아두이노 연결

아두이노와 FPGA SPI 통신을 위해 위와 같이 연결한다. (FPGA의 포트의 경우 추후에 합성 후 포트를 지정하게 된다.)

 

VIVADO툴을 통해 SYNTHESIS, IMPLEMENTATION을 완료한 뒤 Generate BitStream을 통해 실행 파일을 생성하여 FPGA 보드에 해당 실행 파일을 전달한다.


ILA( Integrated Logic Analyzer )를 통한 아두이노 신호 확인

 

VIVADO에 내장되어 있는 ILA IP를 활용하여 Input, Output으로 지정한 핀에 대한 신호를 확인할 것이다.

    //ila
    ila_0 U0(
        .clk(clk),
        .probe0(cs),
        .probe1(sclk),
        .probe2(mosi),
        .probe3(miso),
        .probe4(bitcnt),
        .probe5(address),
        .probe6(current_state),
        .probe7(REG_D0)
    );

총 8개의 신호를 확인할 것이다.

 

SPI Master (아두이노)

위 그림 처럼 총 32비트 [ID(2), R/W(1), ADDRESS(1), TMP(12), DATA(16)] 비트를 입력받아 Slave에게 전달한다.

 

아두이노 내장 SPI 함수를 통해 SPI 통신이 가능하며, 전체 코드는 아래 있다.

 

Console 창을 통해 ID, R/W, ADDRESS, TMP, DATA 값을 차례대로 입력받고, 총 32비트의 입력이 들어오게 되면 SPI 트랜잭션을 통해 32비트 데이터를 전달하고, 전송이 끝나게 되면 Transaction을 끝낸다.

위와 같이 아두이노 콘솔창을 통해 전달된 값을 확인할 수 있다.

WRITE 상태 신호를 아두이노에서 전달할 경우 ILA 신호를 확인해보면, mosi 신호 중간에 한번씩 신호가 글리치가 발생하는 것을 볼 수 있다.

전체 32비트를 전달하지만 아두이노 특성상 8비트씩 전달하기 떄문에 8비트 전달 이후 발생하는 글리치에 해당한다. (실제 통신에는 위와 같은 글리치 신호도 해결해야 하지만 현재는 해결하지 않고 넘어간다...)

READ 상태인 경우 Register에 저장된 값을 miso Pin을 통해 전달하는 것을 볼 수 있다. 현재 0808 (0000 1000 0000 1000) 신호를 miso Pin을 통해 전달하는 것을 확인할 수 있다.


Synthesis

(합성에 필요한 스크립트 파일의 경우 아래 있습니다.)

 

위에서 작성한 spi_slave 코드와 sdc(Synopsys Design Constraints) 파일을 통해 netlist를 생성한다.

 

sdc 파일은 디지털 회로 설계에서 타이밍 및 기타 물리적 제약 조건을 최적화하는 데 필요하다.

sdc 파일은 크게 엄격한 타이밍 제약 조건과 완화된 타이밍 제약 조건이 있다. 클럭 주기에 따라 달라지게 되는데 클럭에 따라 생성되는 netlist가 달라지게 된다.

1. 엄격한 타이밍 제약 조건으로 합성하게 될 경우 고성능이 필요한 경우 사용한다.

2. 완화된 타이밍 제약 조건으로 합성하게 될 경우 저전력 설계가 필요한 경우 사용한다.

 

현재 저전력 설계를 지향하고 있기 때문에 클럭 주기가 10인 sdc 파일을 합성에 사용할 것이다.

 

Library 파일의 경우 반도체 공정사에서 제공하며, 해당 공정 기술에 맞춘 게이트와 셀에 대한 정보를 포함하고 있다. (합성에 사용하는 Library 파일은 교육용 일반적인 Library 파일에 해당한다.)

 

결과 : RTL 설계 코드와 sdc 파일을 통해 합성하게 되면 그에 해당하는 netlist 파일이 생성된다.


Pre Layout Simulation

Synthesis를 통해 생성된 1. netlist 파일 2. 합성에 사용한 Library 파일의 .lib 파일명과 동일한 .v 파일 3. Testbench 파일 4. sdf(Stamdard Delay Format) 파일을 통해 PreLayout Simulation을 실행한다.

1. netlist 파일의 경우 합성 결과로 나온 파일

2. .v 파일의 경우 해당 Library의 셀 정의와 관련된 Verilog 소스 코드 파일이다. 해당 파일은 아래와 같이 각 셀의 기능 및 타이밍의 특성을 정의한다.

...
// type:  
`timescale 1ns/10ps
`celldefine
module AND3X4 (Y, A, B, C);
	output Y;
	input A, B, C;

	// Function
	and (Y, A, B, C);

	// Timing
	specify
		(A => Y) = 0;
		(B => Y) = 0;
		(C => Y) = 0;
	endspecify
endmodule
`endcelldefine
...

3. sdf 파일은 각 요소에 대한 지연 시간, 하강 시간, 상승 시간 등을 정의하고 있으며 Simulation에서 타이밍 검증을 수행한다.

4. Testbench 코드는 기능 합성 할 때와 동일한 코드에 해당하며, 각 요소에 타이밍 정보가 들어간 상태에서도 정상적으로 동작해야 한다.

Cadence SimVision 툴을 사용하여 Pre Layout Simulation을 실행해보면 위와 같이 기능 합성에서는 확인할 수 없었던 딜레이가 적용된 것을 확인 할 수 있다.

 

*(글리치 신호 발생)

신호를 자세히 들여다보면 중간에 20ps 시간동안의 글리치가 발생하는 것을 확인할 수 있다.

글리치는 1. 비동기 신호 2. 전파 지연 3. 경합 조건에 의해서 발생하게 되는데 이를 해결하기 위해선 메타스테이빌리티를 줄여야 한다. 

메타스테이빌리티를 줄이기 위해선 동기화 회로를 사용하거나, 글리치 필터를 추가하거나, 클럭 도메인 크로싱 기법을 사용하여 해결할 수 있다.

// 동기화 블록
    always @(posedge sclk) begin
        cs_sync1 <= cs;
        cs_sync2 <= cs_sync1;
        reset_sync1 <= reset;
        reset_sync2 <= reset_sync1;
    end

예를 들어 위와 같이 두 단계 플립플롭을 사용하여 신호를 동기화하게되면 글리치를 줄일 수 있다.

(매우 작은 시간(20ps)의 글리치는 위 방법으로 잡히지 않는 것 같다. 추가로 위와 같이 매우 작은 신호의 글리치는 전체 실행에 영향을 주지 않기 때문에 우선 넘어가도록 한다... 추후에 방법을 찾으면 해결해보겠다...)

 

딜레이가 조금씩 발생하는 것을 확인했지만 전체 흐름에는 영향을 끼치지 않는 것을 PreLayout Simulation을 통해 확인하였다.


PnR (Place and Route)

https://insoobaik.tistory.com/622

 

Innovus - P&R (SETUP ~ GDSII)

보호되어 있는 글입니다. 내용을 보시려면 비밀번호를 입력하세요.

insoobaik.tistory.com

Cadence의 Innovus를 사용한 PnR의 기본적인 내용은 위 글을 참고한다.

 

위와 같이 DRC 체크시 0 Viols가 뜬다면 PNR이 정상적으로 완료된 것을 확인할 수 있다.

PNR을 마친 뒤 새롭게 생성된 netlist, sdf을 통해 Post Layout Simulation을 확인한다.


Post Layout Simulation

Pre Layout Simulation과 동일하게 Post Layout Simulation을 실행하면 된다.

1. PNR을 통해 생성된 netlist 파일

2. 합성에 사용한 Library 파일의 .lib 파일명과 동일한 .v 파일

3. Testbench 파일

4. PNR을 통해 생성된 sdf 파일

위 4가지 파일을 통해 Post Layout Simulation을 실행시켜 준다.

PNR 이후 Post Layout Simulation을 확인해보면 Pre Layout Simulation과는 조금 다르게 신호들이 입력되는 것을 확인할 수 있고, 테스트 벤치에 적용된 sdf 파일에 의해 마찬가지로 Delay가 발생한 것을 확인할 수 있다.

 

세세하게는 각각 신호 전달에 있어서 Delay가 발생했지만 위와 같이 전체 Post Layout Simulation을 보게되면 전체적인 코드 실행에서는 문제가 없는 것을 확인할 수 있다.


전체 코드


Verilog

 

spi_slave.v

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/08 19:35:58
// Design Name: 
// Module Name: spi_slave
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

`define ESTA_READY  2'b00
`define ESTA_READ   2'b01
`define ESTA_WRITE  2'b10
                
module spi_slave(clk, sclk, cs, id, mosi, miso, reset);              
    input clk, sclk, cs, mosi, reset;
    input [1:0] id;
    output reg miso;
    
    //received_data
    reg [31:0] received_data;
    //bitcnt
    reg [5:0]  bitcnt;
    
    always @(negedge sclk) begin
        if(cs) begin
            received_data[31:0] <= 0;
        end
        else begin
            if(bitcnt < 33) begin
                received_data[31:0] <= {received_data[30:0], mosi};
            end 
            else begin
                received_data[31:0]<= received_data[31:0];
            end
        end
    end
    
    always @(negedge sclk) begin
        if (cs) begin
            bitcnt <= 0;
        end
        else begin
            if(bitcnt < 33) begin
                bitcnt <= bitcnt + 1;
            end
            else begin
                bitcnt <= 0;
            end
        end
    end
 
    //id_check
    reg id_check;

    always @(posedge sclk) begin
        if(cs) begin
            id_check <= 0;
        end
        else begin
            if(bitcnt == 2 && id[1:0] == received_data[1:0])
                id_check <= 1;
            else if(bitcnt == 2 && id[1:0] != received_data[1:0])
                id_check <= 0;
            else
                //���� ������ ��� �Ʒ��� ���� ���� ���� ��� �־���� �Ѵ� 
                id_check <= id_check;
        end
    end

    //current_state
    reg [1:0] current_state;
    //next_state
    reg [1:0] next_state;
    
    always @(negedge sclk) begin
        if(cs) begin
            current_state <= 0;
        end
        else
            current_state <= next_state;
    end
    

    always @(posedge sclk) begin
        if(cs) begin
            next_state <= 0;
        end
        else begin
            case(current_state)
                `ESTA_READY : begin
                    if(bitcnt == 3 && received_data[0] == 1'b1 && id_check == 1)
                        next_state <= `ESTA_READ;
                    else if(bitcnt == 3 && received_data[0] == 1'b0 && id_check == 1)
                        next_state <= `ESTA_WRITE;
                    else
                        next_state <= next_state;
                end
                `ESTA_READ : begin
                    if(bitcnt == 0)
                        next_state <= `ESTA_READY;
                    else
                        next_state <= next_state;
                    end
                `ESTA_WRITE : begin
                    if(bitcnt == 0)
                        next_state <= `ESTA_READY;
                    else
                        next_state <= next_state;
                    end
            endcase
        end
    end

    //address    
    reg [1:0] address;

    always @(posedge sclk) begin
        if(cs) begin
            address <= 0;
        end
        else begin
            if(bitcnt == 4 && received_data[0] == 0) begin
                address <= 2'b01;
            end
            else if(bitcnt == 4 && received_data[0] == 1) begin
                address <= 2'b10;
            end
            else
                address <= address;
        end
    end

    //register D0, D1 
    reg [15:0] REG_D0;
    reg [15:0] REG_D1;
    
    always @(posedge sclk) begin
        if(reset) begin
            REG_D0[15:0] <= 0;
        end
        else begin
            if(current_state == `ESTA_WRITE && address == 2'b01 && bitcnt == 32)
                REG_D0[15:0] <= received_data[15:0];
            else begin
                REG_D0[15:0] <= REG_D0[15:0];
            end    
        end
    end

    always @(posedge sclk or posedge reset) begin
        if(reset) begin
            REG_D1[15:0] <= 0;
        end
        else begin
            if(current_state == `ESTA_WRITE && address == 2'b10 && bitcnt ==32)
                REG_D1[15:0] <= received_data[15:0];
            else begin
                REG_D1[15:0] <= REG_D1[15:0];
            end    
        end
    end

    //miso
    always @(posedge sclk) begin
        if(cs)
            miso <= 0;
        else if(current_state == `ESTA_READ && address == 2'b01) begin
            case(bitcnt)
                5'd16 : miso <= REG_D0[15];
                5'd17 : miso <= REG_D0[14];
                5'd18 : miso <= REG_D0[13];
                5'd19 : miso <= REG_D0[12];
                5'd20 : miso <= REG_D0[11];
                5'd21 : miso <= REG_D0[10];
                5'd22 : miso <= REG_D0[9];
                5'd23 : miso <= REG_D0[8];
                5'd24 : miso <= REG_D0[7];
                5'd25 : miso <= REG_D0[6];
                5'd26 : miso <= REG_D0[5];
                5'd27 : miso <= REG_D0[4];
                5'd28 : miso <= REG_D0[3];
                5'd29 : miso <= REG_D0[2];
                5'd30 : miso <= REG_D0[1];
                5'd31 : miso <= REG_D0[0];
                default : miso <= 0;
            endcase
        end
        else if(current_state == `ESTA_READ && address == 2'b10)begin
            case(bitcnt)
                5'd16 : miso <= REG_D1[15];
                5'd17 : miso <= REG_D1[14];
                5'd18 : miso <= REG_D1[13];
                5'd19 : miso <= REG_D1[12];
                5'd20 : miso <= REG_D1[11];
                5'd21 : miso <= REG_D1[10];
                5'd22 : miso <= REG_D1[9];
                5'd23 : miso <= REG_D1[8];
                5'd24 : miso <= REG_D1[7];
                5'd25 : miso <= REG_D1[6];
                5'd26 : miso <= REG_D1[5];
                5'd27 : miso <= REG_D1[4];
                5'd28 : miso <= REG_D1[3];
                5'd29 : miso <= REG_D1[2];
                5'd30 : miso <= REG_D1[1];
                5'd31 : miso <= REG_D1[0];
                default : miso <= 0;
            endcase
        end
        else
            miso <= 0;
    end
    
    //ila
//    ila_0 U0(
//        .clk(clk),
//        .probe0(cs),
//        .probe1(sclk),
//        .probe2(mosi),
//        .probe3(miso),
//        .probe4(bitcnt),
//        .probe5(address),
//        .probe6(current_state),
//        .probe7(REG_D0)
//    );

endmodule

 

tb_spi_slave.v

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2024/07/08 19:36:52
// Design Name:
// Module Name: tb_spi_slave
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////


module tb_spi_slave();
  reg sclk, reset, cs, mosi;
  reg [1:0] id;
  wire miso;
  
  initial begin
  reset = 0;
  #40 reset = 1;
  #40 reset = 0;
  end
  
  initial begin
  sclk = 0;
  mosi = 0;
  cs = 1;
  id = 2'b01;
  
  //id check
  #42 cs = 0;
  SPI_data(32'b1011_0000_0000_0000_1100_1100_1100_1101);
  #42 cs = 1;
  
  #42;
  id = 2'b11;
  #42 cs = 0;
  SPI_data(32'b1100_0000_0000_0000_0000_1111_0000_1111);
  #42 cs = 1;
  
  #42;
  id = 2'b01;
  #42 cs = 0;
  SPI_data(32'b0110_0000_0000_0000_0000_0000_0000_0000);
  #42 cs = 1;
  
  #42;
  id = 2'b11;
  #42 cs = 0;
  SPI_data(32'b1101_0000_0000_0000_0000_1010_0000_1010);
  #42 cs = 1;
  
  #42;
  id = 2'b10;
  #42 cs = 0;
  SPI_data(32'b1011_1111_1111_1111_1100_1100_1100_1101);
  #42 cs = 1;
  
  
  #100 $finish;
  end
  
  always #5 sclk = ~sclk;
  
  task SPI_data(input [31:0] data);
  integer i;
  begin
  for(i = 31; i >= 0; i = i - 1) begin
  @(posedge sclk)
  mosi <= data[i];
  end
  end
  endtask
  
    spi_slave u0(.sclk(sclk), .reset(reset), .cs(cs), .mosi(mosi), .id(id), .miso(miso));
  
  endmodule

Arduino

 

spi_master (아두이노 코드)

#include <SPI.h>


enum InputState {
    WAITING_FOR_MODE,
    WAITING_FOR_ID,
    WAITING_FOR_ADDRESS,
    WAITING_FOR_TMP,
    WAITING_FOR_DATA
};

InputState currentState = WAITING_FOR_MODE;

bool rwMode;
bool address;
uint16_t tmp;
uint16_t data; // 데이터
uint8_t id; // ID 값 (2비트)

const int CS_PIN = 10; // SS 핀 (CS 핀) 고정값 변경 못함
const int SCLK_PIN = 13; // SCLK 핀

void setup() {
    Serial.begin(9600);
    SPI.begin(); // SPI 초기화
    SPI.setClockDivider(SPI_CLOCK_DIV2); // 클럭 속도 설정
    SPI.setDataMode(SPI_MODE2); // SPI 모드 설정 (모드 2)

    // SPI 핀 모드 설정
    pinMode(CS_PIN, OUTPUT); // SS 핀을 출력 모드로 설정
    digitalWrite(CS_PIN, HIGH); // 슬레이브 비선택 상태
    pinMode(SCLK_PIN, OUTPUT); // SCLK 핀을 출력 모드로 설정
    digitalWrite(SCLK_PIN, LOW); // 초기 상태 LOW

    // 시작 프롬프트 출력
    Serial.println("Enter read/write mode (1 for read, 0 for write):"); // 콘솔에 값 출력
}

void loop() { // 반복문 
    if (Serial.available() > 0) { //시리얼 통신에 값이 있으면
      String input = Serial.readStringUntil('\n');
      input.trim();

    if (currentState == WAITING_FOR_MODE) {
      // 모드 입력 받기
      if (input.equals("1") || input.equals("0")) {
        rwMode = input.equals("0"); // 수정: 0을 입력하면 true(0), 1을 입력하면 false(1) 0이 WRITE니까 입력해야 하니까 상태 변화
        currentState = WAITING_FOR_ID; // ID 입력 대기 상태로 전환
        Serial.println("Enter ID value (0 to 3):");  // WRITE일 경우 ID 값 받기 위한 Serial 출력
      } else {
        Serial.println("Invalid input. Enter 1 for read, 0 for write:"); //READ는 그냥 넘어가고 0, 1 이외의 값은 그냥 끝내기
      }
    } else if (currentState == WAITING_FOR_ID) { // WRITE 입력하면 상태가 얘로 바뀌니까 반복문에 의해서 얘가 실행
      // ID 입력 받기
      int idValue = input.toInt();
      if (idValue >= 0 && idValue <= 3) {
        id = idValue;
        currentState = WAITING_FOR_ADDRESS;
        Serial.println("Enter address (1 for REG_D1, 0 for REG_D0):");
      } else {
        Serial.println("Invalid input. Enter ID value (0 to 3):");
      }
    } else if (currentState == WAITING_FOR_ADDRESS) {
      // upper/lower 입력 받기
      if (input.equals("1") || input.equals("0")) {
        address = input.equals("1");
        currentState = WAITING_FOR_TMP;
        Serial.println("Enter tmp (in hex):");
      } else {
        Serial.println("Invalid input. Enter address (1 for REG_D1, 0 for REG_D0):");
      }
    } else if (currentState == WAITING_FOR_TMP) { // ADDRESS인데 그냥 그 빈공간임
      // 주소 입력 받기
      tmp = strtol(input.c_str(), NULL, 16);
      currentState = WAITING_FOR_DATA;
      Serial.println("Enter data (in hex):");
    } else if (currentState == WAITING_FOR_DATA) {
      // 데이터 입력 받기
      data = strtol(input.c_str(), NULL, 16);

      // 32비트 데이터 구성
uint32_t dataToSend = ((uint32_t)id << 30) | ((uint32_t)(!rwMode) << 29) | ((uint32_t)address << 28) | ((uint32_t)tmp << 16) | data;

  // SPI 데이터 전송
  SPI.beginTransaction(SPISettings(800, MSBFIRST, SPI_MODE2)); // SPI 설정 (모드 2)
  digitalWrite(CS_PIN, LOW); // 슬레이브 선택

byte bytesToSend[4];
  bytesToSend[0] = (dataToSend >> 24) & 0xFF;
  bytesToSend[1] = (dataToSend >> 16) & 0xFF;
  bytesToSend[2] = (dataToSend >> 8) & 0xFF;
  bytesToSend[3] = dataToSend & 0xFF;
      
uint32_t receivedData = 0;
  for (int i = 0; i < 4; ++i) {
    receivedData = (receivedData << 8) | SPI.transfer(bytesToSend[i]);
  }

  digitalWrite(CS_PIN, HIGH); // 슬레이브 비선택
  SPI.endTransaction(); // SPI 트랜잭션 종료

  // 결과 출력
  Serial.print("Sent: ID(");
  Serial.print(id, BIN); // ID 값을 이진수로 출력
  Serial.print("), ");
  Serial.print(rwMode ? "Write" : "Read");
  Serial.print(", ");
  Serial.print(address ? "REG_D1" : "REG_D0");
  Serial.print(", TMP: 16'b");
  Serial.print(tmp, BIN);
  Serial.print(", Data: 16'b");
  Serial.print(data, BIN);
  Serial.println();

  // 읽기 모드일 때 수신된 데이터 출력
  if (!rwMode) {
    Serial.print("Received: 0x");
    Serial.println(receivedData, HEX);
  }

  // 다시 모드 입력 상태로 전환
  currentState = WAITING_FOR_MODE;
  Serial.println("Enter read/write mode (1 for read, 0 for write):");
    }
  }
}

Synthesis

 

spi.tcl

set_db init_lib_search_path ./LIB/
set_db init_hdl_search_path ./RTL/

read_libs slow_vdd1v0_basicCells.lib
read_hdl spi_slave.v

elaborate
read_sdc ./SDC/constraints.sdc
set_db syn_generic_effort medium
set_db syn_map_effort medium
set_db syn_opt_effort medium

syn_generic
syn_map
syn_opt

#Reports
report_timing > reports/report_timing.rpt
report_power  > reports/report_power.rpt
report_area   > reports/report_area.rpt
report_qor    > reports/report_qor.rpt

#Outputs
write_hdl > outputs/spi_slave_netlist.v
write_sdc > outputs/spi_slave.sdc
write_sdf -timescale ns -nonegchecks -recrem split -edges check_edge  -setuphold split > outputs/spi_slave_delays.sdf
728x90