- 이전 글

(1) RTL : https://chonh0531.tistory.com/2

(2) Synthesis, P&R : https://chonh0531.tistory.com/3

목차

1. 과제 개요

2. 소스코드

3. 실습 결과

 

 

1. 과제 개요

이전에 구현해 본 SPI_Slave를 테스트해 보기로 하였고,

Master 역할로 Arduino Uno 보드를 이용하여

FPGA에서 Motor를 제어해보도록 합니다.

 

1. 컴퓨터에서 UART 통신으로 Arduino Uno 보드에 명령어를 전달
2. Arduino Uno는 FPGA에 SPI 통신으로 명령을 전달
3. FPGA는 data 부분의 마지막 4비트를 모터 속도제어에 이용

 

 

 

2. 소스코드

- SPI_Slave

`timescale 1ns / 1ps

module top(
    input i_clk, i_SCLK, i_CS, i_rst_H, i_MOSI,
    output o_out, o_nsleep, o_MISO 
    );
    
    wire [3:0] w_duty;
    wire [4:0] w_bitcnt;
                                          
    SPI_Slave U0 (.i_clk(i_clk), .i_SCLK(i_SCLK), .i_CS(i_CS), .i_rst(i_rst_H), .i_MOSI(i_MOSI), .o_MISO(o_MISO), .o_duty(w_duty), .o_bitcnt(w_bitcnt));
    motor U1 (.i_clk(i_clk), .i_rst(i_rst), .i_duty(w_duty), .o_out(o_out), .o_nsleep(o_nsleep));

endmodule

 

module motor(
    input i_clk,
    input i_rst_H,
    input [3:0] i_duty,
    output reg o_out,
    output reg o_nsleep
    );

    reg [18:0] r_cnt;
    reg [4:0] r_duty;
    reg r_clk2;

    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H)
            o_nsleep <= 0;
        else 
            o_nsleep <= 1;
    end

    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H) begin
            r_cnt <= 0;
            r_clk2 <= 0;
        end
        else if (r_cnt == 500_000) begin
            r_clk2 <= ~r_clk2;
            r_cnt <= 0;
        end
        else
            r_cnt <= r_cnt + 1;
    end
    
    always @(posedge r_clk2 or posedge i_rst_H) begin
        if (i_rst_H)
            r_duty <= 0;
        else if (r_duty >= 16)
            r_duty <= 0;
        else
            r_duty <= r_duty + 1;
    end
    
    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H)
            o_out <= 0;
        else if (r_duty < i_duty)
            o_out <= 1;
        else
            o_out <= 0;
    end
endmodule

 

`timescale 1ns / 1ps

`define IDLE 2'b00
`define READ 2'b10
`define WRITE 2'b11

module SPI_Slave(
    // Internal input
    input i_clk,
    input i_rst_H,
    input [1:0] i_ID,

    // SPI Interface
    input i_SCLK,
    input i_CS,
    input i_MOSI,
    output reg o_MISO,

    // Additional output for duty cycle
    output reg [3:0] o_duty
    );

    reg [5:0] bitcnt = 6'd0;
    reg [1:0] current_state = 2'b00;
    reg [1:0] next_state = 2'b00;
    
    reg [15:0] D0;
    reg [15:0] D1;

    reg addr;    
    reg [31:0] data_RX;
    reg id_ok;

// FSM part
    always @(negedge i_SCLK or posedge i_CS) begin
        if (i_CS) begin
            current_state <= `IDLE;
            next_state <= `IDLE;
        end
        else begin
            current_state <= next_state;
            case(current_state)
                `IDLE : begin
                    if (bitcnt == 6'd3 && id_ok == 1 && data_RX[0] == 1)
                        next_state <= `READ;
                    else if (bitcnt == 6'd3 && id_ok == 1 && data_RX[0] == 0)
                        next_state <= `WRITE;
                    else
                        next_state <= next_state;
                end
                `READ : begin
                    if (bitcnt == 6'd0)
                        next_state <= `IDLE;
                    else
                        next_state <= next_state;
                end
                `WRITE : begin
                    if (bitcnt == 6'd0)
                        next_state <= `IDLE;
                    else
                        next_state <= next_state;
                end
                default : next_state <= next_state; // no cases. just for synthesis
            endcase
        end
    end

    always @(posedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            id_ok <= 0;
        end
        else if (bitcnt == 6'd2 && data_RX[1:0] == i_ID) begin
            id_ok <= 1;
        end
        else
            id_ok <= id_ok;
    end

// Receive data
    always @(negedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            bitcnt <= 6'd0;
            data_RX <= 32'd0;
        end
        else begin
            bitcnt <= bitcnt + 6'd1;
            data_RX <= {data_RX[30:0], i_MOSI};
        end
    end

// Read address
    always @(negedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            addr <= 1'd0;
        end
        else if (current_state == `IDLE && bitcnt == 6'd4) begin
            addr <= data_RX[0];
        end
        else
            addr <= addr;
    end

// Write data
    always @(posedge i_SCLK or posedge i_rst_H) begin
        if(i_rst_H) begin
            D0 <= 16'd0;
            D1 <= 16'd0;
        end
        else if(current_state == `WRITE && addr == 1'b0 && bitcnt == 6'd32) begin
            D0 <= data_RX[15:0];
        end
        else if(current_state == `WRITE && addr == 1'b1 && bitcnt == 6'd32) begin
            D1 <= data_RX[15:0];
        end
        else begin
            D0 <= D0;
            D1 <= D1;
        end
    end

// Update o_duty from D0 or D1
always @(posedge i_SCLK or posedge i_rst_H) begin
    if (i_rst_H)
        o_duty <= 4'd0;
    else if (current_state == `WRITE && bitcnt == 6'd32) begin
        if (addr == 1'b0)
            o_duty <= D0[3:0]; // Update duty cycle from the lower 4 bits of D0
        else
            o_duty <= D1[3:0]; // Update duty cycle from the lower 4 bits of D1
    end
    else
        o_duty <= o_duty;
end

// READ Mode: data transfer
    always @(posedge i_SCLK) begin
        if(current_state == `READ && addr == 1'b0) begin
            case(bitcnt)
                6'd16 : o_MISO <= D0[15];
                6'd17 : o_MISO <= D0[14];
                6'd18 : o_MISO <= D0[13];
                6'd19 : o_MISO <= D0[12];
                6'd20 : o_MISO <= D0[11];
                6'd21 : o_MISO <= D0[10];
                6'd22 : o_MISO <= D0[9];
                6'd23 : o_MISO <= D0[8];
                6'd24 : o_MISO <= D0[7];
                6'd25 : o_MISO <= D0[6];
                6'd26 : o_MISO <= D0[5];
                6'd27 : o_MISO <= D0[4];
                6'd28 : o_MISO <= D0[3];
                6'd29 : o_MISO <= D0[2];
                6'd30 : o_MISO <= D0[1];
                6'd31 : o_MISO <= D0[0];
                default : o_MISO <= 0;
            endcase
        end else if(current_state == `READ && addr == 1'b1) begin
            case(bitcnt)
                6'd16 : o_MISO <= D1[15];
                6'd17 : o_MISO <= D1[14];
                6'd18 : o_MISO <= D1[13];
                6'd19 : o_MISO <= D1[12];
                6'd20 : o_MISO <= D1[11];
                6'd21 : o_MISO <= D1[10];
                6'd22 : o_MISO <= D1[9];
                6'd23 : o_MISO <= D1[8];
                6'd24 : o_MISO <= D1[7];
                6'd25 : o_MISO <= D1[6];
                6'd26 : o_MISO <= D1[5];
                6'd27 : o_MISO <= D1[4];
                6'd28 : o_MISO <= D1[3];
                6'd29 : o_MISO <= D1[2];
                6'd30 : o_MISO <= D1[1];
                6'd31 : o_MISO <= D1[0];
                default : o_MISO <= 0;
            endcase
        end else
            o_MISO <= 1'b0;
    end

endmodule

 

기존 코드에서, 입력으로 받은 데이터중 하위 4비트 데이터를 duty로 전달하였습니다.

motor 제어 부분은 간단하므로, 추가로 설명하지 않았습니다.

 

 

- SPI_Master에 사용할 Arduino 코드

#include <SPI.h>


enum InputState {
  WAITING_FOR_MODE,
  WAITING_FOR_ID,  // ID 입력 대기 상태 추가
  WAITING_FOR_UPPER_LOWER,
  WAITING_FOR_ADDRESS,
  WAITING_FOR_DATA
};

InputState currentState = WAITING_FOR_MODE;

bool writeMode;
bool upperData; // 4번째 비트 (Upper/Lower)
bool address;
uint16_t addr; // 주소
uint16_t data; // 데이터
uint8_t id; // ID 값 (2비트)

const int CS_PIN = 10; // SS 핀 (CS 핀) 고정값 변경 못함
const int SCLK_PIN = 13; // SCLK 핀
const int ID_PIN_1 = 40; // ID 핀 1
const int ID_PIN_2 = 42; // ID 핀 2

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

  // ID 핀 모드 설정
  pinMode(ID_PIN_1, OUTPUT);
  pinMode(ID_PIN_2, OUTPUT);
  digitalWrite(ID_PIN_1, LOW);
  digitalWrite(ID_PIN_2, 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")) {
        writeMode = 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 값 받기
      } 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;
        digitalWrite(ID_PIN_1, id & 0x01); // id가 입력한 값으로 들어온 만약 id로 3을 입력하면 0000 0011이랑 0000 0001이란 &연산 = 0000 0001
        digitalWrite(ID_PIN_2, (id >> 1) & 0x01); // 0000 0011 >> 1 => 0000 0001 & 0000 00001
        currentState = WAITING_FOR_UPPER_LOWER;
        Serial.println("Enter upper or lower (1 for upper, 0 for lower):");
      } else {
        Serial.println("Invalid input. Enter ID value (0 to 3):");
      }
    } else if (currentState == WAITING_FOR_UPPER_LOWER) {
      // upper/lower 입력 받기
      upperData = input.toInt();
      // if (input.equals("1") || input.equals("0")) {
        //upperData = input.equals("1");
      if(upperData == 1 || upperData == 0) {
        currentState = WAITING_FOR_ADDRESS;
        Serial.println("Enter address (in hex):");
      } else {
        Serial.println("Invalid input. Enter 1 for upper, 0 for lower:");
      }
    } else if (currentState == WAITING_FOR_ADDRESS) { // ADDRESS인데 그냥 그 빈공간임
      // 주소 입력 받기
      addr = 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)(!writeMode) << 29) | ((uint32_t)upperData << 28) | ((uint32_t)addr << 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(writeMode ? "Write" : "Read");
      Serial.print(", ");
      Serial.print(upperData ? "Upper" : "Lower");
      Serial.print(", Address: 16'b");
      Serial.print(addr, BIN);
      Serial.print(", Data: 16'b");
      Serial.print(data, BIN);
      Serial.println();

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

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

 

upper / lower는 각각 D1, D0 데이터에 넣는다는 의미입니다.

 

3. 실습결과

FPGA는 ZYNQ-7000의 것을 사용하였고,

Tool은 Vivado를 사용하였습니다.

Arduino의 제어 Tool은 Arduino IDE를 사용하였습니다.

 

 

데이터 "1000_0000_0000_0000_0000_0000_1001_1001" 이 전송되며,

10은 ID, 0은 writemode, 0이므로 D0에 0000_0000_1001_1001이 저장됩니다.

또한 최하위 1001은 duty로 전달되며, 모터 동작 속도를 결정하게 됩니다.

 

결과적으로, SPI_Slave 설계 프로젝트는 약식이지만 모든 테스트를 통과하였고,

Tape-out 단계 전까지 완료되었습니다.

 

'RTL, Synthesis, P&R' 카테고리의 다른 글

UART 통신 - (2) Arm보드로 FPGA LCD 제어  (0) 2024.09.03
UART 통신 - (1) RTL  (0) 2024.09.03
32-bit SPI Interface - (2) Synthesis, P&R  (0) 2024.09.03
32-bit SPI Interface - (1) RTL  (0) 2024.09.03
4-digit Counter with FPGA  (0) 2024.08.30

- 이전 글

(1) RTL : https://chonh0531.tistory.com/2

 

SPI 통신 - (1) RTL

목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경모든 칩은 통신이 필요하기 때문에 통신 모듈이 들어가있습니다.그중에서 좀 기본적인 통신 모듈중 하나인 SPI통신을 이

chonh0531.tistory.com

 

 

 

 

목차

1. 합성 코드

2. Pre-Layout Simulation

3. P&R

4. Post-Layout Simulation

 

 

1. 합성 코드

리눅스 환경에서 진행하였으며, 디렉토리는 다음과 같습니다.

SPI  ─  SIM  ─ function_SIM ─ [SPI_Slave.v , tb_SPI_Slave.v]
                     └ pre_SIM ─ [tb_SPI_Slave_pre.v] 
                     └ post_SIM ─ [tb_SPI_Slave_post.v]
        └  SYN  ─ RTL ─ [constraints.sdc , run_genus.tcl]
                      └ Report ─ [ ... ]
                      └ Output ─ [SPI_Slave_netlist.v , SPI_Slave_sdc.sdc , delays.sdf]
        └ PNR ─ [ SPI_Slave_GDS.gds , SPI_Slave_PNR.v , SPI_Slave_delay_PNR.sdf]
                    └ timingReports ─ [SPI_Slave_preCTS_all.tarpt , SPI_Slave_postCTS_all.tarpt, SPI_Slave_postRoute_all.tarpt]

 

 

Tool은 Genus를 사용하였으며 라이브러리는 교육용 라이브러리 사용하였습니다.

스크립트 코드는 다음과 같습니다.

## run_genus.tcl

set_db init_lib_search_path ../../../LIB/lib/
set_db init_hdl_search_path ../../SIM/function_SIM/
read_libs slow_vdd1v0_basicCells.lib

read_hdl SPI_Slave.v
elaborate
read_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 > ../Report/report_timing.rpt
report_power > ../Report/report_power.rpt
report_area > ../Report/report_area.rpt
report_qor > ../Report/report_qor.rpt



#Outputs
write_hdl > ../Output/SPI_Slave_netlist.v
write_sdc > ../Output/SPI_Slave_sdc.sdc
write_sdf -timescale ns -nonegchecks -recrem split -edges check_edge  -setuphold split > ../Output/delays.sdf

 

 

 

Constraint는 지금 단계에서는 간단하게 clock 설정만 하겠습니다.

 create_clock -name i_SCLK -period 40 -waveform {0 5} [get_ports "i_SCLK"]

 

 

 

2. Pre-Layout Simulation

합성 결과로 나온 SPI_Slave_netlist.v 파일과,

delay 정보를 포함시킨 tb_SPI_Slave_pre.v 파일 및 라이브러리를 컴파일시켜 

시뮬레이션을 진행하였습니다.

Tool은 역시 Xcelium을 사용하였습니다.

 

 

 

이전에 했던 Function Simulation과 동일하게 작동하는 것을 확인할 수 있습니다.

사진에는 복잡하여 보이지 않았으나, 각 gate들에 연결된 wire들이 있고, delay는 적용되어 있습니다.

 

 

3. P&R

Tool은 Innovus를 사용하였으며, 자세한 과정은 생략하고 각 단계에서의 사진으로 대체하고,

마지막에 Script로 공유하도록 하겠습니다.

원래는 P&R 이전에 Timing 분석을 해야 하지만, Tool이 없는 관계로

각 단계에서 모두 Slack Time이 양수인지 확인하는 것으로 대체하겠습니다.

 

 

- Site Error

 

시작하자마자 문제가 생겼는데요,

Violation이 생겼습니다.

 

 

techSite 문제가 생긴 것 같은데요.

 

 

문제가 생긴 Object들이 모두 BUFX2 Cell에 있습니다.

BUFX2 Cell의 Site에 문제가 있는 것 같습니다.

 

 

교육용으로 사용하던 macro.lef 파일에 Site 이름이 잘못되어 있었는데요.

BUFX2 SITE를 CoreSiteDouble에서 CoreSite로 변경하였습니다.

 

 

- Pre-CTS

 

- Post-CTS

 

- Post-Route

 

- Result

 

이후 문제가 없이 잘 진행되었습니다.

결과적으로 Filler까지 추가한 다음 GDS 파일을 준비해놓고,

Post-Layout Simulation을 위한 netlist 파일과 sdf 파일을 준비합니다.

 

아래는 일련의 과정들을 cmd log로부터 추출하여 script로 만든 코드입니다.

 

- run_innovus.tcl

#Design Import
set init_gnd_net VSS
set init_lef_file {../../../LIB/lef/tech.lef ../../../LIB/lef/macro.lef}
set init_verilog ../../SYN/Output/SPI_Slave_netlist.v
set init_mmmc_file Default_01.view
set init_pwr_net VDD
init_design


#Floorplan
floorPlan -site CoreSite -r 0.921979067555 0.699987 10.0 10 10 10


#Power Planning
## Power-Connect Globalnets
clearGlobalNets
globalNetConnect VDD -type pgpin -pin VDD -instanceBasename * -hierarchicalInstance {}
globalNetConnect VSS -type pgpin -pin VSS -instanceBasename * -hierarchicalInstance {}

## Pin Assign
getPinAssignMode -pinEditInBatch -quiet
setPinAssignMode -pinEditInBatch true
editPin -fixOverlap 1 -unit MICRON -spreadDirection clockwise -side Left -layer 1 -spreadType center -spacing 3 -pin {i_CS {i_ID[0]} {i_ID[1]} i_MOSI i_rst_H i_SCLK}
setPinAssignMode -pinEditInBatch false

getPinAssignMode -pinEditInBatch -quiet
setPinAssignMode -pinEditInBatch true
editPin -fixOverlap 1 -unit MICRON -spreadDirection clockwise -side Right -layer 1 -spreadType center -spacing 3 -pin o_MISO
setPinAssignMode -pinEditInBatch false

## Add Ring
set sprCreateIeRingOffset 1.0
set sprCreateIeRingThreshold 1.0
set sprCreateIeRingJogDistance 1.0
set sprCreateIeRingLayers {}
set sprCreateIeRingOffset 1.0
set sprCreateIeRingThreshold 1.0
set sprCreateIeRingJogDistance 1.0
set sprCreateIeRingLayers {}
set sprCreateIeStripeWidth 10.0
set sprCreateIeStripeThreshold 1.0
set sprCreateIeStripeWidth 10.0
set sprCreateIeStripeThreshold 1.0
set sprCreateIeRingOffset 1.0
set sprCreateIeRingThreshold 1.0
set sprCreateIeRingJogDistance 1.0
set sprCreateIeRingLayers {}
set sprCreateIeStripeWidth 10.0
set sprCreateIeStripeThreshold 1.0
setAddRingMode -ring_target default -extend_over_row 0 -ignore_rows 0 -avoid_short 0 -skip_crossing_trunks none -stacked_via_top_layer Metal11 -stacked_via_bottom_layer Metal1 -via_using_exact_crossover_size 1 -orthogonal_only true -skip_via_on_pin {  standardcell } -skip_via_on_wire_shape {  noshape }
addRing -nets {VDD VSS} -type core_rings -follow core -layer {top Metal1 bottom Metal1 left Metal2 right Metal2} -width {top 1.8 bottom 1.8 left 1.8 right 1.8} -spacing {top 1.8 bottom 1.8 left 1.8 right 1.8} -offset {top 1.8 bottom 1.8 left 1.8 right 1.8} -center 0 -threshold 0 -jog_distance 0 -snap_wire_center_to_grid None


#Power Routing
setSrouteMode -viaConnectToShape { noshape }
sroute -connect { blockPin padPin padRing corePin floatingStripe } -layerChangeRange { Metal1(1) Metal11(11) } -blockPinTarget { nearestTarget } -padPinPortConnect { allPort oneGeom } -padPinTarget { nearestTarget } -corePinTarget { firstAfterRowEnd } -floatingStripeTarget { blockring padring ring stripe ringpin blockpin followpin } -allowJogging 1 -crossoverViaLayerRange { Metal1(1) Metal11(11) } -nets { VDD VSS } -allowLayerChange 1 -blockPin useLef -targetViaLayerRange { Metal1(1) Metal11(11) }


#Placement
setPlaceMode -fp false
place_design

## Violation Check
checkPlace SPI_Slave.checkPlace
setDrawView place
fit


#CTS & Routing
## Pre-CTS (ECO - Optimize Design)
setOptMode -fixCap true -fixTran true -fixFanoutLoad false
optDesign -preCTS

## Timing Report (Timing - Report timing, OutDir => timingReports Slack time must be (+)value)
redirect -quiet {set honorDomain [getAnalysisMode -honorClockDomains]} > /dev/null
timeDesign -preCTS -pathReports -drvReports -slackReports -numPaths 50 -prefix simple_spi_preCTS -outDir timingReports

## Route-Nano Route
setNanoRouteMode -quiet -routeTopRoutingLayer 11
setNanoRouteMode -quiet -routeBottomRoutingLayer 1
setNanoRouteMode -quiet -drouteEndIteration 1
setNanoRouteMode -quiet -routeWithTimingDriven false
setNanoRouteMode -quiet -routeWithSiDriven false
routeDesign -globalDetail

## Post-CTS
setOptMode -fixCap true -fixTran true -fixFanoutLoad false
optDesign -postCTS

## Post Routing(Tool - Set Mode - Specify Analysis Mode - On-chip Variation)
setAnalysisMode -cppr none -clockGatingCheck true -timeBorrowing true -useOutputPinCap true -sequentialConstProp false -timingSelfLoopsNoSkew false -enableMultipleDriveNet true -clkSrcPath true -warn true -usefulSkew true -analysisType onChipVariation -log true
setNanoRouteMode -quiet -routeWithTimingDriven false
setNanoRouteMode -quiet -routeWithSiDriven false
routeDesign -globalDetail

## Post-Route
setOptMode -fixCap true -fixTran true -fixFanoutLoad false
setDelayCalMode -engine default -siAware true
optDesign -postRoute


#Filler
getFillerMode -quiet
addFiller -cell FILL64 FILL32 FILL16 FILL8 FILL4 FILL2 FILL1 -prefix FILLER


#Verify
getMultiCpuUsage -localCpu
get_verify_drc_mode -disable_rules -quiet
get_verify_drc_mode -quiet -area
get_verify_drc_mode -quiet -layer_range
get_verify_drc_mode -check_ndr_spacing -quiet
get_verify_drc_mode -check_only -quiet
get_verify_drc_mode -check_same_via_cell -quiet
get_verify_drc_mode -exclude_pg_net -quiet
get_verify_drc_mode -ignore_trial_route -quiet
get_verify_drc_mode -max_wrong_way_halo -quiet
get_verify_drc_mode -use_min_spacing_on_block_obs -quiet
get_verify_drc_mode -limit -quiet
set_verify_drc_mode -disable_rules {} -check_ndr_spacing auto -check_only default -check_same_via_cell true -exclude_pg_net false -ignore_trial_route false -ignore_cell_blockage false -use_min_spacing_on_block_obs auto -report SPI_Slave.drc.rpt -limit 1000
verify_drc
set_verify_drc_mode -area {0 0 0 0}


#GDSII (save)
## save design
saveDesign SPI_Slave_complete

## save gds
streamOut SPI_Slave.gds -libName DesignLib -units 2000 -mode ALL

## save netlist
saveNetlist SPI_Slave_PNR.v

## save sdf
write_sdf  -ideal_clock_network SPI_Slave_PNR.sdf

 

script로 만들어두면, 문제가 생겼을 때 손으로 다시 하는 것이 아니라,

다음 명령어로 빠르게 코드로 진행할 수 있습니다.

innovus -files run_innovus.tcl

 

 

 

4. Post-Layout Simulation

P&R로부터 나온 SPI_Slave_PNR.v 파일과,

테스트벤치에 SPI_Slave_delay_PNR.sdf 파일을 연결시켜 시뮬레이션합니다.

 

 

 

Post_Sim 역시 기대하던 결과가 잘 나오는 것을 알 수 있습니다.

원래는 더 다양한 테스트 케이스 등 검증해야 할 것이 많지만

현재는 간단한 동작을 테스트 중이므로 이것으로 마무리합니다.

 

 

 

- 다음 글

(3) Arduino로 FPGA Motor 제어 : https://chonh0531.tistory.com/4

 

SPI 통신 - (3) Arduino로 FPGA Motor 제어

- 이전 글(1) RTL : https://chonh0531.tistory.com/2(2) Synthesis, P&R : https://chonh0531.tistory.com/3목차1. 과제 개요2. 소스코드3. 실습 결과  1. 과제 개요이전에 구현해 본 SPI_Slave를 테스트해 보기로 하였고,Master

chonh0531.tistory.com

 

진행년월: 24.05

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

모든 칩은 통신이 필요하기 때문에 통신 모듈이 들어가있습니다.

그중에서 좀 기본적인 통신 모듈중 하나인 SPI통신을 이해하고 구현해보려고 합니다.

 

 

2. 과제 정의 및 개요

기본적으로 SPI 통신의 표준 프레임인 Motorola의 메뉴얼을 참고하나,

기본적인 구현 자체에 목적이 있기 때문에 불필요한 기능은 빼고 다음과 같이 스펙 정의를 하도록 합니다.

 

1. SPI Mode 2 (CPOL = 1, CPHA = 0)를 채택하여 하강엣지에 데이터를 읽도록 합니다.
2. CS, SCLK, MOSI, MISO 4개의 핀을 사용합니다.
3. 32비트 데이터를 교환하며 구성은 다음과 같습니다.
    - [0,1]비트는 ID
    - [2]비트는 Read / Write 모드 선택
    - [3]비트는 메모리 선택 주소 (D0 or D1)
    - [4~15]비트는 다용도 확장을 위한 빈공간 (주소 확장 등)
    - [16~31]비트는 16비트 데이터

 

Timing Diagram과 State Diagram은 아래와 같습니다.

간단한 구현이므로 엄격하게 많은 상황을 포함하여 그리지는 않았습니다.

 

 

 

3. 소스코드

`timescale 1ns / 1ps

`define IDLE 2'b00
`define READ 2'b10
`define WRITE 2'b11

module SPI_Slave(
    // Internal input
    // We don't use internal clock now.
    input i_clk,
    input i_rst_H,
    input [1:0] i_ID,

    // SPI Interface
    input i_SCLK,
    input i_CS,
    input i_MOSI,
    output reg o_MISO
    );
    
    reg [5:0] bitcnt = 6'd0;
    reg [1:0] current_state = 2'b00;
    reg [1:0] next_state = 2'b00;
    
    reg [15:0] D0;
    reg [15:0] D1;

    reg addr;    
    reg [31:0] data_RX;
    reg id_ok;

// FSM part
    always @(negedge i_SCLK or posedge i_CS) begin
    if (i_CS) begin
		current_state <= `IDLE;
        next_state <= `IDLE;
    end
    else begin
        current_state <= next_state;
        case(current_state)
        `IDLE : begin
            if (bitcnt == 6'd3 && id_ok == 1 && data_RX[0] == 1)
                next_state <= `READ;
            else if (bitcnt == 6'd3 && id_ok == 1 && data_RX[0] == 0)
                next_state <= `WRITE;
            else
                next_state <= next_state;
        end
        `READ : begin
            if (bitcnt == 6'd0)
                next_state <= `IDLE;
            else
                next_state <= next_state;
        end
        `WRITE : begin
            if (bitcnt == 6'd0)
                next_state <= `IDLE;
            else
                next_state <= next_state;
        end
	default : next_state <= next_state; // no cases. just for synthesis
        endcase
    end
    end

    always @(posedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            id_ok <= 0;
        end
        else if (bitcnt == 6'd2 && data_RX[1:0] == i_ID) begin
            id_ok <= 1;
        end
        else
            id_ok <= id_ok;
    end


// Receive data
    always @(negedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            bitcnt <= 6'd0;
            data_RX <= 32'd0;
        end
        else begin
            bitcnt <= bitcnt + 6'd1;
            data_RX <= {data_RX[30:0], i_MOSI};
        end
    end
    
// Read address
    always @(negedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            addr <= 1'd0;
        end
        else if (current_state == `IDLE && bitcnt == 6'd4) begin
            addr <= data_RX[0];
        end
        else
            addr <= addr;
    end

 
 // Write data
    always @(posedge i_SCLK or posedge i_rst_H) begin
        if(i_rst_H) begin
            D0 <= 16'd0;
            D1 <= 16'd0;
        end
        else if(current_state == `WRITE && addr == 1'b0 && bitcnt == 6'd32) begin
            D0 <= data_RX[15:0];
        end
        else if(current_state == `WRITE && addr == 1'b1 && bitcnt == 6'd32) begin
            D1 <= data_RX[15:0];
        end
        else begin
            D0 <= D0;
            D1 <= D1;
        end
    end
        
// READ Mode: data transfer
/*  now, there is enough time to prepare the data
	because there is time for empty 12-bit transfers in preparation for expansion(addr or options, maybe).
    
    If necessary, We can also design the data to be read from the next cycle using internal clk, 
    which is about least four times faster than SCLK.
*/
    always @(posedge i_SCLK) begin
        if(current_state == `READ && addr == 1'b0) begin
            case(bitcnt)
                6'd16 : o_MISO <= D0[15];
                6'd17 : o_MISO <= D0[14];
                6'd18 : o_MISO <= D0[13];
                6'd19 : o_MISO <= D0[12];
                6'd20 : o_MISO <= D0[11];
                6'd21 : o_MISO <= D0[10];
                6'd22 : o_MISO <= D0[9];
                6'd23 : o_MISO <= D0[8];
                6'd24 : o_MISO <= D0[7];
                6'd25 : o_MISO <= D0[6];
                6'd26 : o_MISO <= D0[5];
                6'd27 : o_MISO <= D0[4];
                6'd28 : o_MISO <= D0[3];
                6'd29 : o_MISO <= D0[2];
                6'd30 : o_MISO <= D0[1];
                6'd31 : o_MISO <= D0[0];
                default : o_MISO <= 0;
            endcase
        end else if(current_state == `READ && addr == 1'b1) begin
            case(bitcnt)
                6'd16 : o_MISO <= D1[15];
                6'd17 : o_MISO <= D1[14];
                6'd18 : o_MISO <= D1[13];
                6'd19 : o_MISO <= D1[12];
                6'd20 : o_MISO <= D1[11];
                6'd21 : o_MISO <= D1[10];
                6'd22 : o_MISO <= D1[9];
                6'd23 : o_MISO <= D1[8];
                6'd24 : o_MISO <= D1[7];
                6'd25 : o_MISO <= D1[6];
                6'd26 : o_MISO <= D1[5];
                6'd27 : o_MISO <= D1[4];
                6'd28 : o_MISO <= D1[3];
                6'd29 : o_MISO <= D1[2];
                6'd30 : o_MISO <= D1[1];
                6'd31 : o_MISO <= D1[0];
                default : o_MISO <= 0;
            endcase
        end else
            o_MISO <= 1'b0;
    end

endmodule

 

주석으로 적어둔 것 처럼, 가운데 Spare 비트들은 확장을 위해 두었으며,

해당 비트 직후 Read 명령을 수행해야 하는 경우,

SCLK보다 최소 4배정도 빠른 내부 clock을 이용해 처리할 수 있습니다.

 

 

module tb_SPI_Slave();

//    reg i_clk;
    reg i_rst_H;
    reg [1:0] i_ID;
    
    reg i_SCLK;
    reg i_CS;
    reg i_MOSI;
    wire o_MISO;
    
    SPI_Slave SPI(
//        .i_clk(i_clk),
        .i_rst_H(i_rst_H),
        .i_SCLK(i_SCLK),
        .i_CS(i_CS),
        .i_MOSI(i_MOSI),
        .o_MISO(o_MISO),
        .i_ID(i_ID)
    );
    
    initial begin
//        i_clk = 1'b0;
        i_rst_H = 1'b0;
        i_SCLK = 1'b0;
        i_MOSI = 1'b0;
        i_CS = 1'b1;
        i_ID = 2'b01;
        
        // Reset sequence
        #40 i_rst_H = 1'b1;
        #40 i_rst_H = 1'b0;
        
        // Test case 1: Write mode "0101_0000_0000_0000_1100_1100_1100_1101"
        #20 i_CS = 1'b0; //if timing problem in pre_sim -> #10
        send_SPI_data(32'b0101_0000_0000_0000_1100_1100_1100_1101);
        #40 i_CS = 1'b1;
        
        // Wait for a while before the next case
        #40;
        
        // Test case 2: Read mode "0111_0000_0000_0000_0000_0000_0000_0000"
        #40 i_CS = 1'b0;
        send_SPI_data(32'b0111_0000_0000_0000_0000_0000_0000_0000);
        #40 i_CS = 1'b1;
        
        // Test case 3: Read mode, ID Error "1011_0000_0000_0000_0000_0000_0000_0000"
        #40 i_CS = 1'b0;
        send_SPI_data(32'b1011_0000_0000_0000_0000_0000_0000_0000);
        #40 i_CS = 1'b1;

        #100 $finish;
    end
    
    // Clock generation
//    always #5 begin i_clk = ~i_clk; end
    always #20 begin i_SCLK = ~i_SCLK; end
    
    // Task to send data
    task send_SPI_data(input [31:0] data);
        integer i;
        begin
            for (i = 31; i >= 0; i = i - 1) begin
                @ (posedge i_SCLK) // For timing
                    i_MOSI <= data[i];
            end
        end
    endtask
    
endmodule

 

 

4. 시뮬레이션 결과

동작을 확인해보기 위해, 또 모범적인 waveform을 마련해놓기 위해 function simulation을 합니다.

Tool은 Xcelium을 사용하였습니다.

 

 

1. Write Mode Test

 

붉은 화살표로 표시한 CS가 0인 구간에 동작을 하고,

결과적으로 D1에 원하는 데이터가 잘 들어간 것을 볼 수 있습니다.

 

 

2. Read Mode Test

 

Read 명령을 받은 후 16비트 타이밍부터 D1에 있는 데이터가

MISO를 통해 잘 출력하고 있는 것을 볼 수 있습니다.

 

 

 

 

- 다음 글

(2) Synthesis, P&R : https://chonh0531.tistory.com/3

 

SPI 통신 - (2) Synthesis, P&R

- 이전 글(1) RTL : https://chonh0531.tistory.com/2 SPI 통신 - (1) RTL목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경모든 칩은 통신이 필요하기 때문에 통신 모듈이 들어가있습니다.

chonh0531.tistory.com

 

+ Recent posts