진행년월: 24.08

 

- 이전 글

(1) 2차원 Filter : https://chonh0531.tistory.com/20

 

Filter - (2) FPGA에서 CNN구현

진행년월: 24.08 목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경CNN 모델 중 가장 기본적인 Lenet-5 모델을 구현해보려고 합니다.메모리 등을 고려해야 하기 때문에,성능 자

chonh0531.tistory.com

 

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

CNN 모델 중 가장 기본적인 Lenet-5 모델을 구현해보려고 합니다.

메모리 등을 고려해야 하기 때문에,

성능 자체를 끌어올리기보다는 구현 자체에 초점을 맞춥니다.

 

 

2. 과제 정의 및 개요

 

 

구현할 모델은 위와 같습니다.

 

1. CNN Python코드 작성
2. Verilog 구현용 C코드 작성
3. Verilog 코드 구현
4. 이미지 예측 정확도 평가

 

3. 소스코드

- Python CNN

import tensorflow as tf
from tensorflow.keras import layers, models
import numpy as np
import struct

# **1. LeNet-5 모델 정의**
def build_lenet5():
    model = models.Sequential([
        layers.Conv2D(6, (5, 5), activation='relu', input_shape=(28, 28, 1)),
        layers.AveragePooling2D((2, 2)),
        layers.Conv2D(16, (5, 5), activation='relu'),
        layers.AveragePooling2D((2, 2)),
        layers.Flatten(),
        layers.Dense(120, activation='relu'),
        layers.Dense(84, activation='relu'),
        layers.Dense(10, activation='softmax')
    ])
    return model

# **2. 데이터셋 로드 및 전처리**
def load_data():
    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
    x_train = x_train / 255.0  # Normalize to [0, 1]
    x_test = x_test / 255.0
    x_train = np.expand_dims(x_train, axis=-1)  # Add channel dimension
    x_test = np.expand_dims(x_test, axis=-1)
    return (x_train, y_train), (x_test, y_test)

# **3. 양자화 함수: 32비트를 8/11비트로 변환**
def quantize(value, bits=8, frac_bits=4):
    scale = 2 ** frac_bits
    quantized = np.round(value * scale)
    quantized = np.clip(quantized, -(2 ** (bits - 1)), 2 ** (bits - 1) - 1)
    return quantized.astype(np.int16)

# **4. 학습과 가중치 저장**
def train_and_save_weights(model, x_train, y_train, x_test, y_test, quant_bits=8, frac_bits=4):
    model.compile(optimizer='adam',
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))
    test_loss, test_acc = model.evaluate(x_test, y_test)
    print(f"Test Accuracy (Python): {test_acc:.4f}")

    # Save weights for FPGA implementation
    for layer in model.layers:
        if isinstance(layer, (layers.Conv2D, layers.Dense)):
            layer_weights, layer_biases = layer.get_weights()

            # 양자화된 가중치 저장
            quantized_weights = quantize(layer_weights, quant_bits, frac_bits)
            quantized_biases = quantize(layer_biases, quant_bits, frac_bits)

            # 파일 저장
            np.savetxt(f"{layer.name}_weights.txt", quantized_weights.flatten(), fmt='%d')
            np.savetxt(f"{layer.name}_biases.txt", quantized_biases, fmt='%d')

            print(f"Weights and biases for layer '{layer.name}' saved.")
    
    print("All layer weights and biases saved.")
    return test_acc

# **5. 테스트 데이터 저장 (Verilog 테스트벤치용)**
def save_test_data(x_test, y_test):
    (x_test, y_test) = (x_test[:1000], y_test[:1000])  # 제한된 샘플 수로 간단히 처리
    x_test_flat = x_test.reshape(x_test.shape[0], -1)  # Flatten 28x28 images to 784

    # Save test data
    np.savetxt('test_images.txt', x_test_flat, fmt='%d')  # Save as integer values (0-255)
    np.savetxt('test_labels.txt', y_test, fmt='%d')      # Save labels

    print("Test images and labels saved.")

# **Main Script**
if __name__ == "__main__":
    # Load data
    (x_train, y_train), (x_test, y_test) = load_data()
    
    # Build model
    model = build_lenet5()
    
    # Train model and save weights
    python_accuracy = train_and_save_weights(model, x_train, y_train, x_test, y_test)
    
    # Save test data
    save_test_data(x_test, y_test)

 

python으로 모델을 구성한 뒤, 학습을 진행합니다.

테스트 데이터는 MNIST 데이터를 사용하였습니다.

학습된 모델의 Weight와 Bias를 Vivado에서 Block 메모리 초기화 파일로 사용하기 위해

COE 파일로 저장합니다.

 

 

- Verilog CNN 이전 Convolution test

module cnn (
    input clk,
    input reset,
    input [7:0] t_rdata,
    output reg [9:0] t_addr,
    output reg [11:0] output_address, // Output memory address
    output reg signed [10:0] output_data, // Output write data
    output reg output_write_enable // Output write enable
);

// RAM Signals
reg [9:0] image_address;
reg [7:0] image_write_data;
wire [7:0] image_read_data;
reg image_write_enable;
wire signed [10:0] output_read_data;

// ROM Signals
reg [7:0] weight_address;
wire signed [7:0] weight_read_data;
reg [2:0] bias_address;
wire signed [7:0] bias_read_data;

// Loop Index Registers
reg [4:0] row, next_row, col, next_col;
reg [2:0] filter, next_filter, kernel_row, next_kernel_row, kernel_col, next_kernel_col;

// Additional Registers
reg [9:0] current_image_address, next_image_address;
(* use_dsp = "yes" *) reg signed [18:0] conv_sum, next_conv_sum;
reg [7:0] current_weight_address, next_weight_address;
reg [11:0] current_output_address, next_output_address;
reg signed [10:0] temp_output, next_temp_output;

// State Definitions
localparam s0 = 0, s1 = 1, s2 = 2, s3 = 3, s4 = 4, s5 = 5, s6 = 6;
reg [3:0] state, next_state;

// File Descriptor
integer fd;

// RAM Instantiation
i_ram image_ram (
    .clka(clk),
    .wea(image_write_enable),
    .addra(image_address),
    .dina(image_write_data),
    .douta(image_read_data)
);

a_ram output_ram (
    .clka(clk),
    .wea(output_write_enable),
    .addra(output_address),
    .dina(output_data),
    .douta(output_read_data)
);

// ROM Instantiation
w0_rom weight_rom (
    .clka(clk),
    .addra(weight_address),
    .douta(weight_read_data)
);

b0_rom bias_rom (
    .clka(clk),
    .addra(bias_address),
    .douta(bias_read_data)
);

// Finite State Machine (FSM)
always @ (reset, state, t_rdata, image_read_data, output_read_data, weight_read_data, bias_read_data, row, col, filter, kernel_row, kernel_col, current_image_address, conv_sum, current_weight_address, current_output_address, temp_output) begin
    // Default memory settings
    t_addr = 0;
    image_address = 0;
    image_write_data = 0;
    image_write_enable = 0;
    output_address = 0;
    output_data = 0;
    output_write_enable = 0;
    weight_address = 0;
    bias_address = 0;

    // Default register settings
    next_state = state;
    next_row = row;
    next_col = col;
    next_filter = filter;
    next_kernel_row = kernel_row;
    next_kernel_col = kernel_col;
    next_image_address = current_image_address;
    next_conv_sum = conv_sum;
    next_weight_address = current_weight_address;
    next_output_address = current_output_address;
    next_temp_output = temp_output;

    case (state)
        s0: begin
            next_state = s1;
            next_image_address = 0;
            t_addr = 0;
            next_row = 0;
            next_col = 0;
        end

        s1: begin
            image_write_data = t_rdata;
            image_address = current_image_address;
            image_write_enable = 1'b1;
            next_state = s1;
            next_col = col + 1;
            if (next_col == 28) begin
                next_col = 0;
                next_row = row + 1;
                if (next_row == 28) begin
                    next_row = 0;
                    next_state = s2;
                end
            end
            next_image_address = next_row * 28 + next_col;
            t_addr = next_image_address;
        end

        s2: begin
            next_filter = 0;
            next_row = 0;
            next_col = 0;
            next_conv_sum = 0;
            next_kernel_row = 0;
            next_kernel_col = 0;
            image_address = 0;
            weight_address = 0;
            next_state = s3;
        end

        s3: begin
            next_conv_sum = conv_sum + $signed({1'b0, image_read_data}) * weight_read_data;
            next_state = s3;
            next_kernel_col = kernel_col + 1;
            if (next_kernel_col == 5) begin
                next_kernel_col = 0;
                next_kernel_row = kernel_row + 1;
                if (next_kernel_row == 5) begin
                    next_kernel_row = 0;
                    next_state = s4;
                    bias_address = filter;
                end
            end
            next_image_address = (row + next_kernel_row) * 28 + col + next_kernel_col;
            next_weight_address = filter * 25 + next_kernel_row * 5 + next_kernel_col;
            image_address = next_image_address;
            weight_address = next_weight_address;
        end

        s4: begin
            next_temp_output = conv_sum[18:8];
            if (next_temp_output[10] == 1'b1) begin
                next_temp_output = next_temp_output + (|conv_sum[7:0]); // Round towards zero (RTZ)
            end
            next_temp_output = next_temp_output + bias_read_data;
            if (next_temp_output[10] == 1'b1) begin
                next_temp_output = 0;
            end
            next_output_address = filter * 576 + row * 24 + col;
            output_address = next_output_address;
            output_data = next_temp_output;
            output_write_enable = 1'b1;
            next_state = s3;
            next_col = col + 1;
            if (next_col == 24) begin
                next_col = 0;
                next_row = row + 1;
                if (next_row == 24) begin
                    next_row = 0;
                    next_filter = filter + 1;
                    if (next_filter == 6) begin
                        next_filter = 0;
                        next_state = s5;
                    end
                end
            end
            next_conv_sum = 0;
            next_image_address = next_row * 28 + next_col;
            next_weight_address = next_filter * 25;
            image_address = next_image_address;
            weight_address = next_weight_address;
        end

        s5: begin
            next_state = s5;
        end

        default: begin
            next_state = s0;
        end
    endcase

    if (reset == 1) begin
        next_state = s0;
        next_row = 0;
        next_col = 0;
        next_filter = 0;
        next_kernel_row = 0;
        next_kernel_col = 0;
        next_image_address = 0;
        next_conv_sum = 0;
        next_weight_address = 0;
        next_output_address = 0;
        next_temp_output = 0;
    end
end

// State update on clock edge
always @ (posedge clk) begin
    state <= next_state;
    row <= next_row;
    col <= next_col;
    filter <= next_filter;
    kernel_row <= next_kernel_row;
    kernel_col <= next_kernel_col;
    current_image_address <= next_image_address;
    conv_sum <= next_conv_sum;
    current_weight_address <= next_weight_address;
    current_output_address <= next_output_address;
    temp_output <= next_temp_output;
end

// Synthesis directives and file writing
// synthesis translate_off
always @ (posedge clk) begin
    fd = $fopen("test.txt", "a");
    if (state == s4 && output_write_enable == 1'b1) begin
        $fwrite(fd, "A[%0d][%0d][%0d] = %0d\n", filter, row, col, output_data);
    end
    if (reset == 1) begin
        fd = $fopen("test.txt", "w");
    end
    $fclose(fd);
end
// synthesis translate_on

endmodule

 

module tb;
    reg clk;
    reg reset;
    wire [9:0] t_addr;
    wire [7:0] t_rdata;
    wire [11:0] output_address;
    wire [10:0] output_data;
    wire output_write_enable;

    //
    // ROM instantiation
    //
    t_rom t_test (
        .clka(clk),
        .addra(t_addr),
        .douta(t_rdata)
    );

    //
    // CNN instantiation
    //
    cnn uut (
        .clk(clk),
        .reset(reset),
        .t_addr(t_addr),
        .t_rdata(t_rdata),
        .output_address(output_address),
        .output_data(output_data),
        .output_write_enable(output_write_enable)
    );

    // 100 MHz clock
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // reset
    initial begin
        reset = 0;
        #100 reset = 1;
        #100 reset = 0;
    end
endmodule

 

Verilog로 구현하기 전에 가장 중요한 Convolution 층을 먼저 구현하여 테스트 하였습니다.

 

 

- Verilog CNN

module lenet5_fpga (
    input clk,
    input reset,
    input [7:0] input_data,        // 8-bit quantized input pixel data
    output reg [3:0] output_class, // Predicted class (0-9)
    output reg done                // Processing done signal
);

    // Parameters
    parameter IMG_SIZE = 28;
    parameter NUM_CLASSES = 10;

    // State Machine States
    localparam IDLE = 0, LOAD_IMAGE = 1, 
               CONV1 = 2, POOL1 = 3, 
               CONV2 = 4, POOL2 = 5,
               FLATTEN = 6, FC1 = 7, FC2 = 8, FC3 = 9, DONE = 10;
    reg [3:0] state;

    // Layer Buffers
    reg [7:0] image [0:IMG_SIZE-1][0:IMG_SIZE-1]; // 8-bit Image Data
    reg signed [10:0] conv1_out [0:23][0:23][0:5];  // 11-bit Conv1 output
    reg signed [10:0] pool1_out [0:11][0:11][0:5];  // 11-bit Pool1 output
    reg signed [10:0] conv2_out [0:7][0:7][0:15];   // 11-bit Conv2 output
    reg signed [10:0] pool2_out [0:3][0:3][0:15];   // 11-bit Pool2 output
    reg signed [10:0] fc1_out [0:119];              // FC1 output
    reg signed [10:0] fc2_out [0:83];               // FC2 output
    reg signed [10:0] fc3_out [0:NUM_CLASSES-1];    // FC3 output

    // Flatten Buffer
    reg signed [10:0] pool2_flat [0:399]; // Flattened Pool2 Output

    // Temporary Registers
    reg signed [18:0] conv_sum; // For convolution accumulation
    reg signed [18:0] fc_sum;   // For FC accumulation
    reg signed [10:0] max_val;
    reg [3:0] max_index;
    integer i, j, k, kr, kc, c, row, col;

    // Block Memory Generators for weights and biases
    wire [7:0] conv1_weight_data;
    wire [7:0] conv2_weight_data;
    wire [7:0] fc1_weight_data;
    wire [7:0] fc2_weight_data;
    wire [7:0] fc3_weight_data;
    wire [7:0] bias_data;

    reg [15:0] conv1_weight_addr;
    reg [15:0] conv2_weight_addr;
    reg [15:0] fc1_weight_addr;
    reg [15:0] fc2_weight_addr;
    reg [15:0] fc3_weight_addr;
    reg [15:0] bias_addr;

    // Instantiate Block RAMs for weights and biases
    blk_mem_gen_conv1_weights conv1_weights_bram (
        .clka(clk),
        .wea(1'b0),
        .addra(conv1_weight_addr),
        .dina(8'b0),
        .douta(conv1_weight_data)
    );

    blk_mem_gen_conv2_weights conv2_weights_bram (
        .clka(clk),
        .wea(1'b0),
        .addra(conv2_weight_addr),
        .dina(8'b0),
        .douta(conv2_weight_data)
    );

    blk_mem_gen_fc1_weights fc1_weights_bram (
        .clka(clk),
        .wea(1'b0),
        .addra(fc1_weight_addr),
        .dina(8'b0),
        .douta(fc1_weight_data)
    );

    blk_mem_gen_fc2_weights fc2_weights_bram (
        .clka(clk),
        .wea(1'b0),
        .addra(fc2_weight_addr),
        .dina(8'b0),
        .douta(fc2_weight_data)
    );

    blk_mem_gen_fc3_weights fc3_weights_bram (
        .clka(clk),
        .wea(1'b0),
        .addra(fc3_weight_addr),
        .dina(8'b0),
        .douta(fc3_weight_data)
    );

    blk_mem_gen_bias bias_bram (
        .clka(clk),
        .wea(1'b0),
        .addra(bias_addr),
        .dina(8'b0),
        .douta(bias_data)
    );

    // FSM Logic
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            state <= IDLE;
            done <= 0;
        end else begin
            case (state)
                IDLE: begin
                    done <= 0;
                    state <= LOAD_IMAGE;
                end

                LOAD_IMAGE: begin
                    row = 0;
                    col = 0;
                    
                    // Sequentially load the image into the buffer
                    if (row < IMG_SIZE) begin
                        image[row][col] <= input_data;
                        col = col + 1;
                        if (col == IMG_SIZE) begin
                            col = 0;
                            row = row + 1;
                        end
                    end
        
                    // Move to the next state after loading the image
                    if (row == IMG_SIZE && col == 0) begin
                        state <= CONV1;
                    end
                end

                CONV1: begin
                    // Convolution Layer 1
                    for (i = 0; i < 24; i = i + 1) begin
                        for (j = 0; j < 24; j = j + 1) begin
                            for (k = 0; k < 6; k = k + 1) begin
                                conv1_weight_addr = k * 25; // Example address calculation
                                conv_sum = 0;
                                for (kr = 0; kr < 5; kr = kr + 1) begin
                                    for (kc = 0; kc < 5; kc = kc + 1) begin
                                        conv_sum = conv_sum + 
                                                   image[i+kr][j+kc] * conv1_weight_data;
                                    end
                                end
                                conv1_out[i][j][k] <= (conv_sum + bias_data) >>> 8;
                            end
                        end
                    end
                    state <= POOL1;
                end

                POOL1: begin
                    for (i = 0; i < 12; i = i + 1) begin
                        for (j = 0; j < 12; j = j + 1) begin
                            for (k = 0; k < 6; k = k + 1) begin
                                pool1_out[i][j][k] <= 
                                    (conv1_out[2*i][2*j][k] + conv1_out[2*i+1][2*j][k] +
                                     conv1_out[2*i][2*j+1][k] + conv1_out[2*i+1][2*j+1][k]) >> 2;
                            end
                        end
                    end
                    state <= CONV2;
                end

                CONV2: begin
                    for (i = 0; i < 8; i = i + 1) begin
                        for (j = 0; j < 8; j = j + 1) begin
                            for (k = 0; k < 16; k = k + 1) begin
                                conv_sum = 0;
                                for (c = 0; c < 6; c = c + 1) begin
                                    for (kr = 0; kr < 5; kr = kr + 1) begin
                                        for (kc = 0; kc < 5; kc = kc + 1) begin
                                            conv_sum = conv_sum + 
                                                       (pool1_out[i+kr][j+kc][c] * conv2_weight_data);
                                        end
                                    end
                                end
                                conv2_out[i][j][k] <= (conv_sum + bias_data) >>> 8;
                            end
                        end
                    end
                    state <= POOL2;
                end

                POOL2: begin
                    for (i = 0; i < 4; i = i + 1) begin
                        for (j = 0; j < 4; j = j + 1) begin
                            for (k = 0; k < 16; k = k + 1) begin
                                pool2_out[i][j][k] <= 
                                    (conv2_out[2*i][2*j][k] + conv2_out[2*i+1][2*j][k] +
                                     conv2_out[2*i][2*j+1][k] + conv2_out[2*i+1][2*j+1][k]) >> 2;
                            end
                        end
                    end
                    state <= FLATTEN;
                end

                FLATTEN: begin
                    for (i = 0; i < 400; i = i + 1) begin
                        pool2_flat[i] <= pool2_out[i/100][(i%100)/25][i%25];
                    end
                    state <= FC1;
                end

                FC1: begin
                    for (i = 0; i < 120; i = i + 1) begin
                        fc_sum = 0;
                        for (j = 0; j < 400; j = j + 1) begin
                            fc1_weight_addr = i * 400 + j;
                            fc_sum = fc_sum + pool2_flat[j] * fc1_weight_data;
                        end
                        fc1_out[i] <= (fc_sum + bias_data) >>> 8;
                    end
                    state <= FC2;
                end

                FC2: begin
                    for (i = 0; i < 84; i = i + 1) begin
                        fc_sum = 0;
                        for (j = 0; j < 120; j = j + 1) begin
                            fc2_weight_addr = i * 120 + j;
                            fc_sum = fc_sum + fc1_out[j] * fc2_weight_data;
                        end
                        fc2_out[i] <= (fc_sum + bias_data) >>> 8;
                    end
                    state <= FC3;
                end

                FC3: begin
                    max_val = -32768;
                    for (i = 0; i < NUM_CLASSES; i = i + 1) begin
                        fc_sum = 0;
                        for (j = 0; j < 84; j = j + 1) begin
                            fc3_weight_addr = i * 84 + j;
                            fc_sum = fc_sum + fc2_out[j] * fc3_weight_data;
                        end
                        fc3_out[i] <= (fc_sum + bias_data) >>> 8;
                        if (fc3_out[i] > max_val) begin
                            max_val = fc3_out[i];
                            max_index = i;
                        end
                    end
                    output_class <= max_index;
                    state <= DONE;
                end

                DONE: begin
                    done <= 1;
                end
            endcase
        end
    end
endmodule

 

이제 모든 층을 구현하여 코드를 구성합니다.

장비의 Memory가 충분치 않아 성능의 타협을 보고

32비트 floating point를 8비트 fixed point로 구현하였습니다.

 

 

- Verilog CNN Testbench

 

`timescale 1ns / 1ps

module tb_lenet5_fpga;

    // Signals
    reg clk;
    reg reset;
    reg [7:0] input_data;
    wire [3:0] output_class;
    wire done;

    // Parameters
    parameter CLK_PERIOD = 10;        // Clock period
    parameter NUM_IMAGES = 1000;     // Number of images to process
    parameter IMG_SIZE = 28;         // Image size (28x28)

    // File I/O
    integer input_file, output_file;
    integer i, j, k;
    reg [7:0] image_buffer [0:IMG_SIZE*IMG_SIZE-1]; // Buffer to hold one image

    // Instantiate DUT (Device Under Test)
    lenet5_fpga dut (
        .clk(clk),
        .reset(reset),
        .input_data(input_data),
        .output_class(output_class),
        .done(done)
    );

    // Clock generation
    always #(CLK_PERIOD / 2) clk = ~clk;

    // Testbench Logic
    initial begin
        // Initialize
        clk = 0;
        reset = 1;
        input_data = 0;

        // Open input image file
        input_file = $fopen("test_image.txt", "r");
        if (input_file == 0) begin
            $display("Error: Input image file not found!");
            $finish;
        end

        // Open output file
        output_file = $fopen("verilog_predietions.txt", "w");
        if (output_file == 0) begin
            $display("Error: Could not open output file!");
            $finish;
        end

        // Deassert reset after a few cycles
        #20 reset = 0;

        // Process NUM_IMAGES images
        for (k = 0; k < NUM_IMAGES; k = k + 1) begin
            $display("Processing image %d/%d...", k + 1, NUM_IMAGES);

            // Load one image into the buffer
            for (i = 0; i < IMG_SIZE * IMG_SIZE; i = i + 1) begin
                if (!$feof(input_file)) begin
                    $fscanf(input_file, "%d ", image_buffer[i]);
                end else begin
                    $display("Error: Unexpected end of input file!");
                    $finish;
                end
            end

            // Feed image buffer to DUT
            for (i = 0; i < IMG_SIZE; i = i + 1) begin
                for (j = 0; j < IMG_SIZE; j = j + 1) begin
                    input_data = image_buffer[i * IMG_SIZE + j];
                    #CLK_PERIOD;
                end
            end

            // Wait for done signal
            wait (done);

            // Write output class to file
            $fwrite(output_file, "%d\n", output_class);
        end

        $display("All images processed. Results saved to verilog_predictions.txt.");

        // Close files and finish simulation
        $fclose(input_file);
        $fclose(output_file);
        $finish;
    end
endmodule

 

이제 미리 준비한 test 데이터를 input으로 해서,

모델로 예측한 결과값들을 txt 파일로 저장합니다.

 

4. 시뮬레이션 결과

- Python 정확도

 

Python에서 확인한 정확도는 99%로,

실제 다른 경우에서는 이정도 정확도를 가지기 어렵지만

학습용 데이터를 사용하였기 때문에 가능합니다.

 

 

- FPGA 정확도

 

FPGA에서 나온 예측값들의 정확도는 96%로,

앞서 8bit fixed point로 구현하면서 타협봤었죠.

모델의 성능 차이는 3% 내외로 준수한 성능을 보입니다.

 

다른 경우에서는 Domain에 따라 어느 정도까지 타협을 볼지 정해야겠지만,

장비의 memory를 생각하며 먼저 python으로 성능을 테스트 한 뒤에

FPGA에 그 모델을 구현해서 칩에 AI를 구성할 수 있습니다.

진행년월: 24.08

 

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 결과

 

 

1. 배경

Convolution 연산은 신호 처리 부분에서 많이 사용합니다.

특히, Filtering이 주된 용도이고, HPF, LPF가 있겠죠.

 

저는 이번에 2차원 Filter를 이해하고 설계해본 뒤에

그것을 확장하여 이미지 처리 모델인 CNN을 구현해보려고 합니다.

 

 

2. 과제 정의 및 개요

 

제가 사용할 필터는 아래와 같으며, LPF(Low Pass Filter) 입니다.

 

 

위 필터는 중심을 기준으로 평균화해줍니다.

 

원본 이미지와 필터링된 이미지를 먼저 보면,

 

좌측(원본)이 조금 뚜렷하고, 우측(필터링)은 일종의 블러효과를 넣은 것처럼 흐릿해졌습니다.

 

 

A = imread('lena_gray.png');
B = imresize(A, 0.5);
dlmwrite('img_in.txt',B);

 

MATLAB을 통해 512 X 512 size 원본 이미지를 256 X 256 size로 조절한 뒤 저장하였습니다.

이제 이것이 input image가 될 것입니다.

 

 

전체 Process
1. C코드로 먼저 Fixed Point로 변환 및 구현하며, 입력, 출력 예시를 준비
2. Verilog 구현
3. Double Buffering으로 데이터 충돌 방지
4. Line Buffering으로 입력과 동시에 처리해 Cycle
5. Parameterizing, Testbench에서 DPI를 통한 C코드 활용

 

 

3. 소스코드

- 3.1 C코드 구현

#include <stdio.h>
#include <math.h>

void filter2d(unsigned char in_img[], unsigned char out_img[],
			int height, int width) {
	int		h[3][3] = {0x08, 0x10, 0x08, 0x10, 0x20, 0x10, 0x08, 0x10, 0x08};
	for(int i=0;i<height;i++) {
		for(int j=0;j<width;j++) {
			int	sum = 0;
			if(i>0 && j>0)				sum += in_img[(i-1)*width+j-1]*h[0][0];
			if(i>0) 				sum += in_img[(i-1)*width+j  ]*h[0][1];
			if(i>0 && j<width-1)			sum += in_img[(i-1)*width+j+1]*h[0][2];
			if(j>0)					sum += in_img[(i  )*width+j-1]*h[1][0];
								sum += in_img[(i  )*width+j  ]*h[1][1];
			if(j<width-1)				sum += in_img[(i  )*width+j+1]*h[1][2];
			if(i<height-1 && j>0)			sum += in_img[(i+1)*width+j-1]*h[2][0]; 
			if(i<height-1)				sum += in_img[(i+1)*width+j  ]*h[2][1]; 
			if(i<height-1 && j<width-1)		sum += in_img[(i+1)*width+j+1]*h[2][2];
								sum = (sum + (1<<6)) >> 7;
			if(sum < 0) out_img[i*width+j] = 0;
			else if(sum > 255) out_img[i*width+j] = 255;
			else out_img[i*width+j] = sum;
		}
	}
}

int main(void) {
	int			i, a;
	FILE		*inf, *outf, *memf;
	unsigned char	in_img[256*256];
	unsigned char	out_img[256*256];
	inf = fopen("img_in.txt", "r"); 
	outf = fopen("img_out.txt", "w");
	memf = fopen("img_in.dat", "w");

	for(i=0;i<256*256;i++) {
		fscanf(inf, "%d,", &a);
		in_img[i] = a;
		fprintf(memf, "%02X\n", in_img[i]);
	}

	filter2d(in_img, out_img, 256, 256);

	for(i=0;i<256*256;i++) {
		fprintf(outf, "%3d ", out_img[i]);
		if(i%256 == 255) fprintf(outf, "\n");
	}

	fclose(inf);
	fclose(outf);
	fclose(memf);
}

 

 

 

 

- 3.2 Verilog 구현

 

module filter2d (
				input			clk,
				input			n_reset,
				input			start,
				output	reg		finish,

				output			cs,
				output			we,
				output	[16:0]	addr,
				output	[7:0]	din,
				input	[7:0]	dout,

				input			h_write,
				input	[3:0]	h_idx,
				input	[7:0]	h_data
);

reg			on_proc;
reg	[3:0]	cnt;
reg	[7:0]	cnt_x;
reg	[7:0]	cnt_y;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		on_proc <= 1'b0;
		cnt <= 0;
		cnt_x <= 0;
		cnt_y <= 0;
		finish <= 1'b0;
	end else begin
		if(start == 1'b1) on_proc <= 1'b1;
		else if((cnt == 11) && (cnt_x == 255) && (cnt_y == 255)) on_proc <= 1'b0;

		if(on_proc == 1'b1) begin
			cnt <= (cnt == 11) ? 0 : cnt+1;
			if(cnt == 11) begin
				cnt_x <= (cnt_x == 255) ? 0 : cnt_x+1;
				if(cnt_x == 255) begin
					cnt_y <= (cnt_y == 255) ? 0 : cnt_y+1;
				end
			end
		end
		finish <= ((cnt == 11) && (cnt_x == 255) && (cnt_y == 255));
	end
end

wire			mem_rd = (cnt >= 0) && (cnt <= 8) && (on_proc == 1'b1);
reg		[16:0]	rd_addr;
always@(*) begin
	case(cnt)
		4'd0:	rd_addr = (cnt_y-1)*256 + cnt_x-1;
		4'd1:	rd_addr = (cnt_y-1)*256 + cnt_x;
		4'd2:	rd_addr = (cnt_y-1)*256 + cnt_x+1;
		4'd3:	rd_addr = (cnt_y  )*256 + cnt_x-1;
		4'd4:	rd_addr = (cnt_y  )*256 + cnt_x;
		4'd5:	rd_addr = (cnt_y  )*256 + cnt_x+1;
		4'd6:	rd_addr = (cnt_y+1)*256 + cnt_x-1;
		4'd7:	rd_addr = (cnt_y+1)*256 + cnt_x;
		4'd8:	rd_addr = (cnt_y+1)*256 + cnt_x+1;
		default:	rd_addr = 'bx;
	endcase 
end

reg		[7:0]	pd;
wire			pd_en = (cnt >= 1) && (cnt <= 9);
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		pd <= 0;
	end else begin
		if(pd_en == 1'b1) pd <= dout;
	end
end

reg	signed	[7:0]	h[0:8];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		h[0] <= 8'h08;
		h[1] <= 8'h10;
		h[2] <= 8'h08;
		h[3] <= 8'h10;
		h[4] <= 8'h20;
		h[5] <= 8'h10;
		h[6] <= 8'h08;
		h[7] <= 8'h10;
		h[8] <= 8'h08;
	end else begin
		if(h_write == 1'b1) begin
			h[h_idx] <= h_data;
		end
	end
end

wire signed [7:0]	coeff = h[cnt-2];
wire signed [15:0]	mul = pd * coeff;
reg	 signed [19:0]	acc;
wire signed [19:0]	acc_in = (cnt == 1) ? 0 : mul + acc;
reg					acc_en;

always@(*) begin
	acc_en = 1'b0;
	case(cnt)
		4'd 1: acc_en = 1'b1;
		4'd 2: if((cnt_y > 0) && (cnt_x >   0)) acc_en = 1'b1;
		4'd 3: if((cnt_y > 0)                 ) acc_en = 1'b1;
		4'd 4: if((cnt_y > 0) && (cnt_x < 255)) acc_en = 1'b1;
		4'd 5: if(cnt_x >   0)	acc_en = 1'b1;
		4'd 6: 					acc_en = 1'b1;
		4'd 7: if(cnt_x < 255)	acc_en = 1'b1;
		4'd 8: if((cnt_y < 255) && (cnt_x >   0)) acc_en = 1'b1;
		4'd 9: if((cnt_y < 255)                 ) acc_en = 1'b1;
		4'd10: if((cnt_y < 255) && (cnt_x < 255)) acc_en = 1'b1;
		default: acc_en = 1'b0;
	endcase
end

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		acc <= 'b0;
	end else begin
		if(acc_en == 1'b1) acc <= acc_in;
	end
end

wire	[19:0]	pd_rnd_1 = acc + (1<<6);
wire	[12:0]	pd_rnd = pd_rnd_1[19:7];
wire	[7:0]	pd_out = (pd_rnd < 0) ? 0 :
						 (pd_rnd > 255) ? 255 :
						 pd_rnd[7:0];
assign			din = pd_out;

wire			mem_wr = (cnt == 11);
wire	[16:0]	wr_addr = cnt_y * 256 + cnt_x + 256*256;

assign	cs = mem_rd | mem_wr;
assign	we = mem_wr;
assign	addr = (mem_rd == 1'b1) ? rd_addr : wr_addr;

endmodule

 

module top_filter_2d;

reg		clk, n_reset;
reg		start;
wire	finish;

initial clk = 1'b0;
always #5 clk = ~clk;

initial begin
	n_reset = 1'b1;
	$readmemh("../c/img_in.dat", i_buf.data);

	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	start = 1'b1;
	@(posedge clk);
	start = 1'b0;
end

wire	cs, we;
wire	[16:0]	addr;
wire	[7:0]	din;
wire	[7:0]	dout;

filter2d	i_filter (.clk(clk), .n_reset(n_reset), .start(start), .finish(finish),
				.cs(cs), .we(we), .addr(addr), .din(din), .dout(dout),
				.h_write(1'b0), .h_idx(4'b0), .h_data(8'b0));

mem_single #(
				.WD(8),
				.DEPTH(256*256*2)
) i_buf (
				.clk(clk),
				.cs(cs),
				.we(we),
				.addr(addr),
				.din(din),
				.dout(dout)
);

always@(posedge clk) begin
	if(finish == 1'b1) begin
		for(int i=0;i<256;i++) begin
			for(int j=0;j<256;j++) begin
				$write("%3d ", i_buf.data[i*256+j+256*256]);
			end
			$write("\n");
		end
		$finish;
	end
end

endmodule

 

module mem_single #(
		  WD = 128
		, DEPTH = 64
		, WA = $clog2(DEPTH)
) ( 
		  input					clk
		, input					cs
		, input					we
		, input		[WA-1:0]	addr
		, input		[WD-1:0]	din
		, output 	[WD-1:0]	dout
);

reg	[WD-1:0]	data[DEPTH-1:0];
reg	[WA-1:0]	addr_d;

always@(posedge clk) begin
	if(cs == 1'b1) begin
		if(we == 1'b1) data[addr] <= din;
		addr_d <= addr;
	end
end
assign dout = data[addr_d];

endmodule

 

위 mem_single 모듈은 아래에서도 계속 사용합니다.

 

 

 

- 3.3 Double Buffering

module filter2d (
				input	clk,
				input	n_reset,

				input			i_strb,
				input	[7:0]	i_data,

				output 			o_strb,
				output 	[7:0]	o_data
);

wire			start;
wire			mem_rd;
wire	[15:0]	rd_addr;
wire	[7:0]	rd_data;

filter2d_buf i_buf(
				.clk(clk),
				.n_reset(n_reset),
				.i_strb(i_strb),
				.i_data(i_data),

				.start(start),

				.mem_rd(mem_rd),
				.rd_addr(rd_addr),
				.rd_data(rd_data)
);

filter2d_op i_op(
				.clk(clk),
				.n_reset(n_reset),
				.start(start),

				.mem_rd(mem_rd),
				.rd_addr(rd_addr),
				.rd_data(rd_data),

				.o_strb(o_strb),
				.o_data(o_data)
);

endmodule

 

module filter2d_op (
				input			clk,
				input			n_reset,
				input			start,

				output				mem_rd,
				output reg	[15:0]	rd_addr,
				input		[7:0]	rd_data,

				output reg			o_strb,
				output reg	[7:0]	o_data,

				input			h_write,
				input	[3:0]	h_idx,
				input	[7:0]	h_data
);

reg			on_proc;
reg	[3:0]	cnt;
reg	[7:0]	cnt_x;
reg	[7:0]	cnt_y;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		on_proc <= 1'b0;
		cnt <= 0;
		cnt_x <= 0;
		cnt_y <= 0;
	end else begin
		if(start == 1'b1) on_proc <= 1'b1;
		else if((cnt == 11) && (cnt_x == 255) && (cnt_y == 255)) on_proc <= 1'b0;

		if(on_proc == 1'b1) begin
			cnt <= (cnt == 11) ? 0 : cnt+1;
			if(cnt == 11) begin
				cnt_x <= (cnt_x == 255) ? 0 : cnt_x+1;
				if(cnt_x == 255) begin
					cnt_y <= (cnt_y == 255) ? 0 : cnt_y+1;
				end
			end
		end
	end
end

assign mem_rd = (cnt >= 0) && (cnt <= 8) && (on_proc == 1'b1);
always@(*) begin
	case(cnt)
		4'd0:	rd_addr = (cnt_y-1)*256 + cnt_x-1;
		4'd1:	rd_addr = (cnt_y-1)*256 + cnt_x;
		4'd2:	rd_addr = (cnt_y-1)*256 + cnt_x+1;
		4'd3:	rd_addr = (cnt_y  )*256 + cnt_x-1;
		4'd4:	rd_addr = (cnt_y  )*256 + cnt_x;
		4'd5:	rd_addr = (cnt_y  )*256 + cnt_x+1;
		4'd6:	rd_addr = (cnt_y+1)*256 + cnt_x-1;
		4'd7:	rd_addr = (cnt_y+1)*256 + cnt_x;
		4'd8:	rd_addr = (cnt_y+1)*256 + cnt_x+1;
		default:	rd_addr = 'bx;
	endcase 
end

reg		[7:0]	pd;
wire			pd_en = (cnt >= 1) && (cnt <= 9);
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		pd <= 0;
	end else begin
		if(pd_en == 1'b1) pd <= rd_data;
	end
end

reg	signed	[7:0]	h[0:8];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		h[0] <= 8'h08;
		h[1] <= 8'h10;
		h[2] <= 8'h08;
		h[3] <= 8'h10;
		h[4] <= 8'h20;
		h[5] <= 8'h10;
		h[6] <= 8'h08;
		h[7] <= 8'h10;
		h[8] <= 8'h08;
	end else begin
		if(h_write == 1'b1) begin
			h[h_idx] <= h_data;
		end
	end
end

wire signed [7:0]	coeff = h[cnt-2];
wire signed [15:0]	mul = pd * coeff;
reg	 signed [19:0]	acc;
wire signed [19:0]	acc_in = (cnt == 1) ? 0 : mul + acc;
reg					acc_en;

always@(*) begin
	acc_en = 1'b0;
	case(cnt)
		4'd 1: acc_en = 1'b1;
		4'd 2: if((cnt_y > 0) && (cnt_x >   0)) acc_en = 1'b1;
		4'd 3: if((cnt_y > 0)                 ) acc_en = 1'b1;
		4'd 4: if((cnt_y > 0) && (cnt_x < 255)) acc_en = 1'b1;
		4'd 5: if(cnt_x >   0)	acc_en = 1'b1;
		4'd 6: 					acc_en = 1'b1;
		4'd 7: if(cnt_x < 255)	acc_en = 1'b1;
		4'd 8: if((cnt_y < 255) && (cnt_x >   0)) acc_en = 1'b1;
		4'd 9: if((cnt_y < 255)                 ) acc_en = 1'b1;
		4'd10: if((cnt_y < 255) && (cnt_x < 255)) acc_en = 1'b1;
		default: acc_en = 1'b0;
	endcase
end

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		acc <= 'b0;
	end else begin
		if(acc_en == 1'b1) acc <= acc_in;
	end
end

wire	[19:0]	pd_rnd_1 = acc + (1<<6);
wire	[12:0]	pd_rnd = pd_rnd_1[19:7];
wire	[7:0]	pd_out = (pd_rnd < 0) ? 0 :
						 (pd_rnd > 255) ? 255 :
						 pd_rnd[7:0];

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		o_strb <= 1'b0;
		o_data <= 'b0;
	end else begin
		o_strb <= (cnt == 11);
		if(cnt == 11) begin
			o_data <= pd_out;
		end
	end
end

endmodule

 

module filter2d_buf (
				input			clk,
				input			n_reset,
				input			i_strb,
				input	[7:0]	i_data,

				output reg		start,

				input			mem_rd,
				input	[15:0]	rd_addr,
				output	[7:0]	rd_data
);

reg	[7:0]	cnt_x;
reg	[7:0]	cnt_y;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		cnt_x <= 255;
		cnt_y <= 255;
	end else begin
		if(i_strb == 1'b1) begin
			cnt_x <= (cnt_x == 255) ? 0 : cnt_x+1;
			if(cnt_x == 255) begin
				cnt_y <= (cnt_y == 255) ? 0 : cnt_y+1;
			end
		end
	end
end

reg			mode;
wire		mode_change;
reg			mem_wr;
reg	[7:0]	wr_data;

assign mode_change = (mem_wr == 1'b1) && (cnt_x == 255) && (cnt_y == 255);
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		mode <= 1'b0;
		start <= 1'b0;
	end else begin
		if(mode_change == 1'b1) begin
			mode <= ~mode;
		end
		start <= mode_change;
	end
end

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		mem_wr <= 1'b0;
		wr_data <= 8'b0;
	end else begin
		mem_wr <= i_strb;
		wr_data <= i_data;
	end
end
wire	[15:0]	wr_addr = cnt_y*256 + cnt_x;

wire			cs0 = (mode == 1'b0) ? mem_wr : mem_rd; 
wire			we0 = (mode == 1'b0) ? mem_wr : 1'b0; 
wire	[15:0]	addr0 = (mode == 1'b0) ? wr_addr : rd_addr;
wire	[7:0]	din0 = (mode == 1'b0) ? wr_data : 'b0;
wire	[7:0]	dout0;

wire			cs1 = (mode == 1'b1) ? mem_wr : mem_rd; 
wire			we1 = (mode == 1'b1) ? mem_wr : 1'b0; 
wire	[15:0]	addr1 = (mode == 1'b1) ? wr_addr : rd_addr;
wire	[7:0]	din1 = (mode == 1'b1) ? wr_data : 'b0;
wire	[7:0]	dout1;

assign	rd_data = (mode == 1'b0) ? dout1 : dout0;


mem_single #(
				.WD(8),
				.DEPTH(256*256)
) i_buf0 (
				.clk(clk),
				.cs(cs0),
				.we(we0),
				.addr(addr0),
				.din(din0),
				.dout(dout0)
);

mem_single #(
				.WD(8),
				.DEPTH(256*256)
) i_buf1 (
				.clk(clk),
				.cs(cs1),
				.we(we1),
				.addr(addr1),
				.din(din1),
				.dout(dout1)
);

endmodule

 

module top_filter_2d;

reg		clk, n_reset;
reg		start;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[7:0]	img_data[0:65535];
reg			i_strb;
reg	[7:0]	i_data;
integer	idx, cnt;
initial begin
	cnt = 0;
	n_reset = 1'b1;
	$readmemh("../c/img_in.dat", img_data);
	i_strb = 1'b0;
	i_data = 'bx;
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	repeat(3) begin
		for(idx=0;idx<65536;idx=idx+1) begin
			i_strb = 1'b1;
			i_data = img_data[idx];
			@(posedge clk);
			repeat(16) begin
				i_strb = 1'b0;
				i_data = 'bx;
				@(posedge clk);
			end
		end
	end
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			o_strb;
wire	[7:0]	o_data;
filter2d	i_filter (
					.clk(clk), 
					.n_reset(n_reset), 
					.i_strb(i_strb), 
					.i_data(i_data),
					.o_strb(o_strb), 
					.o_data(o_data),
					.h_write(1'b0),
					.h_idx(4'b0),
					.h_data(8'b0)
);

always@(posedge clk) begin
	if(o_strb == 1'b1) begin
		$write("%3d ", o_data);
		cnt = cnt + 1;
		if(cnt[7:0] == 0) begin
			$write("\n");
		end
	end
end

endmodule

 

- 3.4 Line Buffer

module filter2d (
				input			clk,
				input			n_reset,

				input			i_strb,
				input	[7:0]	i_data,

				output reg			o_strb,
				output reg	[7:0]	o_data,

				input			h_write,
				input	[3:0]	h_idx,
				input	[7:0]	h_data
);

reg			garbage;
reg	[3:0]	cnt;
reg	[7:0]	cnt_x;
reg	[7:0]	cnt_y;
reg	[7:0]	i_data_d;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		garbage <= 1'b1;
		cnt <= 7;
		cnt_x <= 254;
		cnt_y <= 254;
		i_data_d <= 'b0;
	end else begin
		if(i_strb == 1'b1) begin
			cnt_x <= (cnt_x == 255) ? 0 : cnt_x+1;
			if(cnt_x == 255) begin
				cnt_y <= (cnt_y == 255) ? 0 : cnt_y+1;
				if(cnt_y == 255) garbage <= 1'b0;
			end
		end
		if(i_strb == 1'b1) cnt <= 0;
		else if(cnt < 7) cnt <= cnt+1;
		if(i_strb == 1'b1) i_data_d <= i_data;
	end
end

reg 	[7:0]	ibuf[2:0][2:0];
wire	[7:0]	dout;
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		for(int i=0;i<3;i++) begin
			for(int j=0;j<3;j++) begin
				ibuf[i][j] <= 'b0;
			end
		end
	end else begin
		if(cnt == 0) begin
			for(int i=0;i<3;i++) begin
				for(int j=0;j<2;j++) begin
					ibuf[i][j] <= ibuf[i][j+1];
				end
			end
			ibuf[2][2] <= i_data_d;
		end
		if(cnt == 1) ibuf[0][2] <= dout;
		if(cnt == 2) ibuf[1][2] <= dout;
	end
end

wire		mem_rd = (cnt == 0) || (cnt == 1);
wire		mem_wr = (cnt == 2);

reg		[8:0]	wr_addr;
wire	[8:0]	rd_addr0 = wr_addr;
wire	[8:0]	rd_addr1 = (wr_addr<256) ? wr_addr+256 : wr_addr-256;
wire	[8:0]	rd_addr = (cnt == 0) ? rd_addr0 : rd_addr1;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		wr_addr <= 0;
	end else begin
		if(mem_wr == 1'b1) begin
			wr_addr <= (wr_addr == 2*256-1) ? 0 : wr_addr + 1;
		end
	end
end

wire			cs = mem_rd | mem_wr;
wire			we = mem_wr;
wire	[8:0]	addr = (mem_wr == 1'b1) ? wr_addr : rd_addr;
wire	[7:0]	din = i_data_d;

mem_single #(
				.WD(8),
				.DEPTH(2*256)
) i_buf0 (
				.clk(clk),
				.cs(cs),
				.we(we),
				.addr(addr),
				.din(din),
				.dout(dout)
);

reg	signed	[7:0]	h[0:8];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		h[0] <= 8'h08;
		h[1] <= 8'h10;
		h[2] <= 8'h08;
		h[3] <= 8'h10;
		h[4] <= 8'h20;
		h[5] <= 8'h10;
		h[6] <= 8'h08;
		h[7] <= 8'h10;
		h[8] <= 8'h08;
	end else begin
		if(h_write == 1'b1) begin
			h[h_idx] <= h_data;
		end
	end
end

reg	signed	[15:0]	mul[2:0][2:0];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		for(int i=0;i<3;i++) begin
			for(int j=0;j<3;j++) begin
				mul[i][j] <= 'b0;
			end
		end
	end else begin
		if((cnt == 3) && (garbage == 1'b0)) begin
			mul[0][0] <= ((cnt_y > 0) && (cnt_x >   0)) ? ibuf[0][0] * h[0] : 'b0;
			mul[0][1] <= ((cnt_y > 0)                 ) ? ibuf[0][1] * h[1] : 'b0;
			mul[0][2] <= ((cnt_y > 0) && (cnt_x < 255)) ? ibuf[0][2] * h[2] : 'b0;
			mul[1][0] <= (cnt_x >   0) ? ibuf[1][0] * h[3] : 'b0;
			mul[1][1] <= 				 ibuf[1][1] * h[4];
			mul[1][2] <= (cnt_x < 255) ? ibuf[1][2] * h[5] : 'b0;
			mul[2][0] <= ((cnt_y < 255) && (cnt_x >   0)) ? ibuf[2][0] * h[6] : 'b0;
			mul[2][1] <= ((cnt_y < 255)                 ) ? ibuf[2][1] * h[7] : 'b0;
			mul[2][2] <= ((cnt_y < 255) && (cnt_x < 255)) ? ibuf[2][2] * h[8] : 'b0;
		end
	end
end

reg	 signed [19:0]	sum_in;
reg	 signed [19:0]	sum;
always@(*) begin
	sum_in = 0;
	for(int i=0;i<3;i++) begin
		for(int j=0;j<3;j++) begin
			sum_in = sum_in + mul[i][j];
		end
	end
end
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		sum <= 'b0;
	end else begin
		if((cnt == 4) && (garbage == 1'b0)) begin
			sum <= sum_in;
		end
	end
end

wire	[19:0]	pd_rnd_1 = sum + (1<<6);
wire	[12:0]	pd_rnd = pd_rnd_1[19:7];
wire	[7:0]	pd_out = (pd_rnd < 0) ? 0 :
						 (pd_rnd > 255) ? 255 :
						 pd_rnd[7:0];

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		o_strb <= 1'b0;
		o_data <= 'b0;
	end else begin
		o_strb <= ((cnt == 5) && (garbage == 1'b0));
		if((cnt == 5) && (garbage == 1'b0)) begin
			o_data <= pd_out;
		end
	end
end

endmodule

 

 

- 3.5 Parameter, DPI

module filter2d #(
				H = 256,
				W = 256
) (
				input			clk,
				input			n_reset,

				input			i_strb,
				input	[7:0]	i_data,

				output reg			o_strb,
				output reg	[7:0]	o_data,

				input			h_write,
				input	[3:0]	h_idx,
				input	[7:0]	h_data
);

reg			garbage;
reg	[3:0]	cnt;
reg	[$clog2(W)-1:0]	cnt_x;
reg	[$clog2(H)-1:0]	cnt_y;
reg	[7:0]	i_data_d;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		garbage <= 1'b1;
		cnt <= 7;
		cnt_x <= W-2;
		cnt_y <= H-2;
		i_data_d <= 'b0;
	end else begin
		if(i_strb == 1'b1) begin
			cnt_x <= (cnt_x == W-1) ? 0 : cnt_x+1;
			if(cnt_x == W-1) begin
				cnt_y <= (cnt_y == H-1) ? 0 : cnt_y+1;
				if(cnt_y == H-1) garbage <= 1'b0;
			end
		end
		if(i_strb == 1'b1) cnt <= 0;
		else if(cnt < 7) cnt <= cnt+1;
		if(i_strb == 1'b1) i_data_d <= i_data;
	end
end

reg 	[7:0]	ibuf[2:0][2:0];
wire	[7:0]	dout;
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		for(int i=0;i<3;i++) begin
			for(int j=0;j<3;j++) begin
				ibuf[i][j] <= 'b0;
			end
		end
	end else begin
		if(cnt == 0) begin
			for(int i=0;i<3;i++) begin
				for(int j=0;j<2;j++) begin
					ibuf[i][j] <= ibuf[i][j+1];
				end
			end
			ibuf[2][2] <= i_data_d;
		end
		if(cnt == 1) ibuf[0][2] <= dout;
		if(cnt == 2) ibuf[1][2] <= dout;
	end
end

wire		mem_rd = (cnt == 0) || (cnt == 1);
wire		mem_wr = (cnt == 2);

localparam	BUF_LEN = 2*W;
reg		[$clog2(BUF_LEN)-1:0]	wr_addr;
wire	[$clog2(BUF_LEN)-1:0]	rd_addr0 = wr_addr;
wire	[$clog2(BUF_LEN)-1:0]	rd_addr1 = (wr_addr<W) ? wr_addr+W: wr_addr-W;
wire	[$clog2(BUF_LEN)-1:0]	rd_addr = (cnt == 0) ? rd_addr0 : rd_addr1;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		wr_addr <= 0;
	end else begin
		if(mem_wr == 1'b1) begin
			wr_addr <= (wr_addr == BUF_LEN-1) ? 0 : wr_addr + 1;
		end
	end
end

wire			cs = mem_rd | mem_wr;
wire			we = mem_wr;
wire	[8:0]	addr = (mem_wr == 1'b1) ? wr_addr : rd_addr;
wire	[7:0]	din = i_data_d;

mem_single #(
				.WD(8),
				.DEPTH(BUF_LEN)
) i_buf0 (
				.clk(clk),
				.cs(cs),
				.we(we),
				.addr(addr),
				.din(din),
				.dout(dout)
);

reg	signed	[7:0]	h[0:8];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		h[0] <= 8'h08;
		h[1] <= 8'h10;
		h[2] <= 8'h08;
		h[3] <= 8'h10;
		h[4] <= 8'h20;
		h[5] <= 8'h10;
		h[6] <= 8'h08;
		h[7] <= 8'h10;
		h[8] <= 8'h08;
	end else begin
		if(h_write == 1'b1) begin
			h[h_idx] <= h_data;
		end
	end
end

reg	signed	[15:0]	mul[2:0][2:0];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		for(int i=0;i<3;i++) begin
			for(int j=0;j<3;j++) begin
				mul[i][j] <= 'b0;
			end
		end
	end else begin
		if((cnt == 3) && (garbage == 1'b0)) begin
			mul[0][0] <= ((cnt_y > 0) && (cnt_x >   0)) ? ibuf[0][0] * h[0] : 'b0;
			mul[0][1] <= ((cnt_y > 0)                 ) ? ibuf[0][1] * h[1] : 'b0;
			mul[0][2] <= ((cnt_y > 0) && (cnt_x < W-1)) ? ibuf[0][2] * h[2] : 'b0;
			mul[1][0] <= (cnt_x >   0) ? ibuf[1][0] * h[3] : 'b0;
			mul[1][1] <= 				 ibuf[1][1] * h[4];
			mul[1][2] <= (cnt_x < W-1) ? ibuf[1][2] * h[5] : 'b0;
			mul[2][0] <= ((cnt_y < H-1) && (cnt_x >   0)) ? ibuf[2][0] * h[6] : 'b0;
			mul[2][1] <= ((cnt_y < H-1)                 ) ? ibuf[2][1] * h[7] : 'b0;
			mul[2][2] <= ((cnt_y < H-1) && (cnt_x < W-1)) ? ibuf[2][2] * h[8] : 'b0;
		end
	end
end

reg	 signed [19:0]	sum_in;
reg	 signed [19:0]	sum;
always@(*) begin
	sum_in = 0;
	for(int i=0;i<3;i++) begin
		for(int j=0;j<3;j++) begin
			sum_in = sum_in + mul[i][j];
		end
	end
end
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		sum <= 'b0;
	end else begin
		if((cnt == 4) && (garbage == 1'b0)) begin
			sum <= sum_in;
		end
	end
end

wire	[19:0]	pd_rnd_1 = sum + (1<<6);
wire	[12:0]	pd_rnd = pd_rnd_1[19:7];
wire	[7:0]	pd_out = (pd_rnd < 0) ? 0 :
						 (pd_rnd > 255) ? 255 :
						 pd_rnd[7:0];

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		o_strb <= 1'b0;
		o_data <= 'b0;
	end else begin
		o_strb <= ((cnt == 5) && (garbage == 1'b0));
		if((cnt == 5) && (garbage == 1'b0)) begin
			o_data <= pd_out;
		end
	end
end

endmodule

 

module top_filter_2d;

reg		clk, n_reset;
reg		start;

initial clk = 1'b0;
always #5 clk = ~clk;

import "DPI" function void init_filter2d(input int h, input int w);
import "DPI" function byte get_input();
import "DPI" function byte get_output();

reg			i_strb;
reg	[7:0]	i_data;
initial begin
	n_reset = 1'b1;
	init_filter2d(256, 256);
	i_strb = 1'b0;
	i_data = 'bx;
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	repeat(3) begin
		repeat(256*256) begin
			i_strb = 1'b1;
			i_data = get_input();
			@(posedge clk);
			repeat(16) begin
				i_strb = 1'b0;
				i_data = 'bx;
				@(posedge clk);
			end
		end
	end
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			o_strb;
wire	[7:0]	o_data;
filter2d	i_filter (
					.clk(clk), 
					.n_reset(n_reset), 
					.i_strb(i_strb), 
					.i_data(i_data),
					.o_strb(o_strb), 
					.o_data(o_data),
					.h_write(1'b0),
					.h_idx(4'b0),
					.h_data(8'b0)
);

reg		[7:0]	out_ref;
always@(posedge clk) begin
	if(o_strb == 1'b1) begin
		out_ref = get_output();
		if(o_data != out_ref) begin
			$display("Error!! o_data = %3d, out_ref = %3d", o_data, out_ref);
			#10;
			$finish;
		end
	end
end

endmodule

 

#include <stdio.h>
#include <stdlib.h>

unsigned char	*in_img;
unsigned char	*out_img;
int				height, width;

void filter2d(void) {
	int		h[3][3] = {0x08, 0x10, 0x08, 0x10, 0x20, 0x10, 0x08, 0x10, 0x08};
	for(int i=0;i<height;i++) {
		for(int j=0;j<width;j++) {
			int	sum = 0;
			if(i>0 && j>0)				sum += in_img[(i-1)*width+j-1]*h[0][0];
			if(i>0) 					sum += in_img[(i-1)*width+j  ]*h[0][1];
			if(i>0 && j<width-1)		sum += in_img[(i-1)*width+j+1]*h[0][2];
			if(j>0)						sum += in_img[(i  )*width+j-1]*h[1][0];
										sum += in_img[(i  )*width+j  ]*h[1][1];
			if(j<width-1)				sum += in_img[(i  )*width+j+1]*h[1][2];
			if(i<height-1 && j>0)		sum += in_img[(i+1)*width+j-1]*h[2][0]; 
			if(i<height-1)				sum += in_img[(i+1)*width+j  ]*h[2][1]; 
			if(i<height-1 && j<width-1)	sum += in_img[(i+1)*width+j+1]*h[2][2];
			sum = (sum + (1<<6)) >> 7;
			if(sum < 0) out_img[i*width+j] = 0;
			else if(sum > 255) out_img[i*width+j] = 255;
			else out_img[i*width+j] = sum;
		}
	}
}

void init_filter2d(int h, int w) {
	int			i, a;
	FILE		*inf;
	inf = fopen("../c/img_in.txt", "r");

	height = h;
	width = w;
	in_img = malloc(height*width*sizeof(unsigned char));
	out_img = malloc(height*width*sizeof(unsigned char));
	for(i=0;i<height*width;i++) {
		fscanf(inf, "%d,", &a);
		in_img[i] = a;
	}

	filter2d();

	fclose(inf);
}

unsigned char get_input(void) {
	static	int	i;
	unsigned char res = in_img[i];
	i++;
	if(i==height*width) i = 0;	
	return res;
}

unsigned char get_output(void) {
	static	int	i;
	unsigned char res = out_img[i];
	i++;
	if(i==height*width) i = 0;	
	return res;
}

 

 

4. 결과

 

다양한 방법으로 Filter를 구현해 보았습니다.

우선은 convolution 연산을 verilog로 구현할 수 있게 되었고,

확장해서 C로 작성한 코드를 이용할 수도 있었습니다.

 

Filter 자체도 사용할 곳이 많겠지만

다음에는 본래 목적인 이미지 AI 모델을 구현해보겠습니다.

우선은 직접 구현의 방식으로 계획 중에 있고,

잘 안된다면 C코드를 불러오는 DPI 방식까지 고려해두겠습니다.

 

 

- 다음 글

(2) FPGA에서 CNN 구현 : https://chonh0531.tistory.com/21

 

Filter - (2) FPGA에서 CNN구현

진행년월: 24.08 목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경CNN 모델 중 가장 기본적인 Lenet-5 모델을 구현해보려고 합니다.메모리 등을 고려해야 하기 때문에,성능 자

chonh0531.tistory.com

 

진행년월: 24.09

 

 

 

IDEC 강의 "SOC 설계 및 구현" 에서 실습하였습니다.

내용이 너무 방대하고, 자료가 많고 용량이 크기 때문에

적절하게 정리할 수 있는 방법을 찾고 올리도록 하겠습니다.

 

 

 

DE2-115 Board
- Intel FPGA Cyclone IV

Quartus II
- Verilog codes (H/W)
 
Keil uVision
- Assembly codes (S/W)


Process
1. Quartus II에서 사용할 Peripheral을 AHB-Lite에 적절히 연결
2. API들을 추가
3. Quartus 에서 compile, program 후 동작 확인

 

 

진행년월: 24.09

 

IDEC 강의 "SOC 설계 및 구현" 에서 실습하였습니다.

내용이 너무 방대하고, 자료가 많고 용량이 크기 때문에

적절하게 정리할 수 있는 방법을 찾고 올리도록 하겠습니다.

 

 

 

DE2-115 Board
- Intel FPGA Cyclone IV

Quartus II
- Verilog codes (H/W)
 
Keil uVision
- Assembly codes (S/W)


Process
1. Quartus II에서 사용할 Peripheral을 AHB-Lite에 적절히 연결
2. 메모리맵을 참고하여 Keil에서 ASSEMBLY 코드 작성 후 빌드
3. 완성된 code.hex를 FPGA/AHB_BRAM/에 복사
4. Quartus 에서 compile, program 후 동작 확인

 

 

진행년월: 24.09

 

IDEC 강의 "SOC 설계 및 구현" 에서 실습하였습니다.

내용이 너무 방대하고, 자료가 많고 용량이 크기 때문에

적절하게 정리할 수 있는 방법을 찾고 올리도록 하겠습니다.

 

 

 

DE2-115 Board
- Intel FPGA Cyclone IV

Quartus II
- Verilog codes (H/W)
 
Keil uVision
- Assembly codes (S/W)


Process
1. Quartus II에서 사용할 Peripheral을 AHB-Lite에 적절히 연결
2. 메모리맵을 참고하여 Keil에서 ASSEMBLY 코드 작성 후 빌드
3. 완성된 code.hex를 FPGA/AHB_BRAM/에 복사
4. Quartus 에서 compile, program 후 동작 확인

 

 

 

 

● 진행기간

 - 2022.06.08 ~ 22.08.22

 

● 요약

 - 공공데이터를 활용, 군집분석을 통한 드론공항 위치 선정

 - AI가 최적의 하늘길을 계산

 - 해당 모델이 탑재된 Web 관제 시스템 구축

 

● 역할

 - 프로젝트 기획, 군집분석, 강화학습 알고리즘 보상체계 설계, 최단경로 알고리즘, 웹 구현 (총 3명 중 기여도 50%)

 

● 성과

 - 국토교통부 빅데이터 활용 공모전 우수상 수상

 

 

 

https://github.com/MiddleJo/SkyRoad_for_Drone

 

GitHub - MiddleJo/SkyRoad_for_Drone: Optimal drone aeronautical route calculation for making emergency delivery system using dro

Optimal drone aeronautical route calculation for making emergency delivery system using drones. - MiddleJo/SkyRoad_for_Drone

github.com

 

 

 

제 깃허브 (위 링크) 에 상세한 설명이 있습니다.

 

 

● 진행기간

 - 2022.04.15 ~ 22.05.20

 

● 요약

 - 콘크리트 혼화제 회사의 데이터로부터 수요 예측

 - 대시보드 구축 및 원자재 발주 자동화

 

● 역할

 - 데이터 전처리, 변수 개발, 시계열 모델 알고리즘, Tableau 구조설계 (총 6명중 기여도 60%)

 

● 성과

 - 수요량 예측 개선을 통한 발주 오차율 개선 ( 30% → 10% 내외 )

 - 재고관리부터 발주까지의 자동화로 일평균 2시간가량 절약

 - Tableau를 활용한 대시보드로 효율적인 고객관리 및 회의 준비 시간 대폭 감소

 

 

https://github.com/MiddleJo/Automatic_stock_management

 

GitHub - MiddleJo/Automatic_stock_management: Automatic stock management system for constructing smart factory.

Automatic stock management system for constructing smart factory. - MiddleJo/Automatic_stock_management

github.com

 

 

제 깃허브 (위 링크) 에 상세한 설명이 있습니다.

진행년월: 24.06

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

규모가 큰 설계를 하게 된다면, 메모리에 접근은 반드시 필요하게 됩니다.

Cache는 고속으로 일을 처리해 응답시간을 줄여주고,

DRAM 등에 접근하는 횟수를 줄여주어 메모리 부하를 줄여줍니다.

 

이전에 Testbench들은 몇 가지 의도된 케이스를 직접 만들어 실험하였고,

모든 케이스가 검증되었다고 볼 수는 없습니다.

Cache 메모리 컨트롤러를 설계하며,

다양한 Test case들에 대해 Coverage를 올리는 Verification에 대해 학습해 봅니다.

 

 

2. 과제 정의 및 개요

 

위 그림처럼 원래는 Cache가 DRAM에 접근하지만,

이번 설계에서는 조금 더 간단하게 하기 위해

Bus Interface가 있다고 가정하고 설계해 보겠습니다.

 

CPU Interface
1. cpu_addr은 32-bit address
2. cpu_din/cpu_dout은 32-bit in/out data
3. cpu_cs가 0일 때 전송
4. cpu_we는 Read/Write, 0일 때 Read
5. cpu_nwait는 전송이 끝나는 flag

Dram Interface
1. dram_addr은 32-bit address
2. dram_din/dram_dout은 32-bit in/out data
3. dram_cs가 0일때 전송
4. dram_we는 Read/Write, 0일 때 Read
5. dram_nwait는 전송이 끝나는 flag
 
Cache Spec.
1. 한 line에 4 word
2. 1024 lines, block address는 10-bit
3. address는 32-bit : tag는 18-bit
즉, 18(tag)+10(block addr)+2(word addr)+2(intra word addr) = 32 bit

Cache Interface
1. cache_din/cache_dout은 data(32*4) + tag(18) = 146-bit
2. valid, dirty bit는 따로 관리

 

 

 

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

 

3. 소스코드

module cache (
				input	clk,
				input	n_reset,

				input				cpu_cs,
				input				cpu_we,
				input		[31:0]	cpu_addr,
				input		[31:0]	cpu_din,
				output reg	[31:0]	cpu_dout,
				output				cpu_nwait,

				output			dram_cs,
				output			dram_we,
				output	[31:0]	dram_addr,
				output	[31:0]	dram_din,
				input	[31:0]	dram_dout,
				input			dram_nwait
);

parameter	IDLE   = 4'b0000;
parameter	READ   = 4'b0001;
parameter	R_WMEM = 4'b0010;
parameter	R_RMEM = 4'b0011;
parameter	R_REND = 4'b0100;
parameter	R_OUT  = 4'b0101;
parameter	WRITE  = 4'b1001;
parameter	W_WMEM = 4'b1010;
parameter	W_RMEM = 4'b1011;
parameter	W_REND = 4'b1100;

reg	[3:0]	state, next;
wire		hit, dirty, valid;
reg	[1:0]	cnt;

always@(*) begin
	next = state;
	case(state)
		IDLE: begin
				if(cpu_cs == 1'b1) begin
					if(cpu_we == 1'b1) next = WRITE;
					else next = READ;
				end
			end
		READ: begin
				if(hit == 1'b1) begin
					if(cpu_cs == 1'b1) begin
						if(cpu_we == 1'b1) next = WRITE;
						else next = READ;
					end else begin
						next = IDLE;
					end
				end else begin
					if(dirty == 1'b1) next = R_WMEM;
					else next = R_RMEM;
				end
			end
		R_WMEM: begin
				if((dram_nwait == 1'b1) && (cnt == 3)) next = R_RMEM;
			end
		R_RMEM: begin
				if((dram_nwait == 1'b1) && (cnt == 3)) next = R_REND;
			end
		R_REND: begin
				if(dram_nwait == 1'b1) next = R_OUT;
			end
		R_OUT: begin
				if(cpu_cs == 1'b1) begin
					if(cpu_we == 1'b1) next = WRITE;
					else next = READ;
				end else begin
					next = IDLE;
				end
			end
		WRITE: begin
				if(hit == 1'b1) begin
					next = IDLE;
				end else begin
					if(dirty == 1'b1) next = W_WMEM;
					else next = W_RMEM;
				end
			end
		W_WMEM: begin
				if((dram_nwait == 1'b1) && (cnt == 3)) next = W_RMEM;
			end
		W_RMEM: begin
				if((dram_nwait == 1'b1) && (cnt == 3)) next = W_REND;
			end
		W_REND: begin
				if(dram_nwait == 1'b1) next = IDLE;
			end
	endcase
end

always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		state <= IDLE;
		cnt <= 0;
	end else begin
		state <= next;
		if((state == R_WMEM) || (state == R_RMEM) ||
				(state == W_WMEM) || (state == W_RMEM)) begin
			if(dram_nwait == 1'b1) begin
				cnt <= cnt + 1;
			end
		end
	end
end

reg		[31:0]	cpu_addr_d;
reg		[31:0]	cpu_din_d;
wire	[145:0]	cache_dout;
reg		[145:0]	cache_line;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cpu_addr_d <= 'b0;
		cpu_din_d <= 'b0;
	end else begin
		if((cpu_cs == 1'b1) && (cpu_nwait == 1'b1)) begin
			cpu_addr_d <= cpu_addr;
			if(cpu_we == 1'b1) cpu_din_d <= cpu_din;
		end
	end
end
assign cpu_nwait = (state == IDLE) ||
					((state == READ) && (hit == 1'b1)) ||
					(state == R_OUT);
always@(*) begin
	if(state == READ) begin
		case(cpu_addr_d[3:2])
			2'b00: cpu_dout = cache_dout[31:0];
			2'b01: cpu_dout = cache_dout[63:32];
			2'b10: cpu_dout = cache_dout[95:64];
			2'b11: cpu_dout = cache_dout[127:96];
		endcase
	end else begin
		case(cpu_addr_d[3:2])
			2'b00: cpu_dout = cache_line[31:0];
			2'b01: cpu_dout = cache_line[63:32];
			2'b10: cpu_dout = cache_line[95:64];
			2'b11: cpu_dout = cache_line[127:96];
		endcase
	end
end

wire	cache_read = ((state == IDLE) && (cpu_cs == 1'b1))
					|| ((state == READ) && (cpu_cs == 1'b1) && (hit == 1'b1))
					|| ((state == R_OUT) && (cpu_cs == 1'b1));
wire	cache_write = ((state == WRITE) && (hit == 1'b1))
					|| ((state == R_REND) && (dram_nwait == 1'b1))
					|| ((state == W_REND) && (dram_nwait == 1'b1));
wire	cache_cs = cache_read || cache_write;
wire	cache_we = cache_write;
wire	[9:0]	cache_addr = (state == IDLE) || (state == READ) ||
														(state == R_OUT) ?
							cpu_addr[13:4] : cpu_addr_d[13:4];
// tag 18, 4 words
reg		[145:0]	cache_din;

always@(*) begin
	if(state == WRITE) begin
		cache_din[145:0] = cache_dout[145:0];
		if(cpu_addr_d[3:2] == 2'b00) cache_din[31:0] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b01) cache_din[63:32] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b10) cache_din[95:64] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b11) cache_din[127:96] = cpu_din_d;
	end else if(state == R_REND) begin
		cache_din[145:128] = cpu_addr_d[31:14];
		cache_din[127:96] = dram_dout;
		cache_din[95:0] = cache_line[95:0];
	end else if(state == W_REND) begin
		cache_din[145:128] = cpu_addr_d[31:14];
		cache_din[127:96] = dram_dout;
		cache_din[95:0] = cache_line[95:0];
		if(cpu_addr_d[3:2] == 2'b00) cache_din[31:0] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b01) cache_din[63:32] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b10) cache_din[95:64] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b11) cache_din[127:96] = cpu_din_d;
	end else begin
		cache_din = cache_line;
	end
end

mem_single #(
				.WD(146),
				.DEPTH(1024)
) i_cache_mem (
				.clk(clk),
				.cs(cache_cs),
				.we(cache_we),
				.addr(cache_addr),
				.din(cache_din),
				.dout(cache_dout)
);

reg		[1023:0]	valids;
reg		[1023:0]	dirtys;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cache_line <= 'b0;
		valids <= 1024'b0;
		dirtys <= 1024'b0;
	end else begin
		if(state == READ) begin
			cache_line <= cache_dout;
		end else if(state == WRITE) begin
			cache_line <= cache_dout;
		end else if((state == R_RMEM) && (dram_nwait == 1'b1)) begin
			if(cnt == 2'b01) cache_line[31:0] <= dram_dout;
			if(cnt == 2'b10) cache_line[63:32] <= dram_dout;
			if(cnt == 2'b11) cache_line[95:64] <= dram_dout;
		end else if((state == R_REND) && (dram_nwait == 1'b1)) begin
			cache_line[127:96] <= dram_dout;
		end else if((state == W_RMEM) && (dram_nwait == 1'b1)) begin
			if(cnt == 2'b01) cache_line[31:0] <= dram_dout;
			if(cnt == 2'b10) cache_line[63:32] <= dram_dout;
			if(cnt == 2'b11) cache_line[95:64] <= dram_dout;
		end else if((state == W_REND) && (dram_nwait == 1'b1)) begin
			cache_line[127:96] <= dram_dout;
		end
		if((state == WRITE) && (hit == 1'b1)) begin
			dirtys[cpu_addr_d[13:4]] <= 1'b1;
		end else if((state == R_REND) && (dram_nwait == 1'b1)) begin
			dirtys[cpu_addr_d[13:4]] <= 1'b0;
		end else if((state == W_REND) && (dram_nwait == 1'b1)) begin
			dirtys[cpu_addr_d[13:4]] <= 1'b1;
		end
		if((state == R_REND) && (dram_nwait == 1'b1)) begin
			valids[cpu_addr_d[13:4]] <= 1'b1;
		end else if((state == W_REND) && (dram_nwait == 1'b1)) begin
			valids[cpu_addr_d[13:4]] <= 1'b1;
		end
	end
end

assign	valid = valids[cpu_addr_d[13:4]];
assign	dirty = dirtys[cpu_addr_d[13:4]];
wire	[17:0]	tag = cache_dout[145:128];
assign	hit = (tag == cpu_addr_d[31:14]) &&
			  (valid == 1'b1);


wire	dram_read = (state == R_RMEM) || (state == W_RMEM);
wire	dram_write = (state == R_WMEM) || (state == W_WMEM);
assign	dram_cs = dram_read || dram_write;
assign	dram_we = dram_write;
assign	dram_addr = (state == R_RMEM) || (state == W_RMEM) ?
						{cpu_addr_d[31:4], cnt, 2'b00} :
						{cache_line[145:128], cpu_addr_d[13:4],cnt, 2'b00};
assign	dram_din = (cnt == 2'b00) ? cache_line[31:0] :
				   (cnt == 2'b01) ? cache_line[63:32] :
				   (cnt == 2'b10) ? cache_line[95:64] :
									cache_line[127:96];

endmodule

 

 

module mem_single #(
		  WD = 128
		, DEPTH = 64
		, WA = $clog2(DEPTH)
) ( 
		  input					clk
		, input					cs
		, input					we
		, input		[WA-1:0]	addr
		, input		[WD-1:0]	din
		, output 	[WD-1:0]	dout
);

reg	[WD-1:0]	data[DEPTH-1:0];
reg	[WA-1:0]	addr_d;

always@(posedge clk) begin
	if(cs == 1'b1) begin
		if(we == 1'b1) data[addr] <= din;
		addr_d <= addr;
	end
end
assign dout = data[addr_d];

endmodule

 

편의를 위해 system-verilog가 사용되었습니다.

verilog-HDL로 컴파일하기 위해서는

Parameter 자리에 WA 부분을 localparam으로 옮기고,

이에 따라 input 정의에 WA가 아닌 직접 기술해줘야 합니다.

 

 

- top0 (Test case 직접 구상)

module top_cache;

reg		clk, n_reset;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[31:0]	dram_data[0:64*1024*1024-1];

initial begin
	$vcdplusfile("cache.vpd");
	$vcdpluson(0,top_cache);
end

reg				cpu_cs;
reg				cpu_we;
reg		[31:0]	cpu_addr;
reg		[31:0]	cpu_din;
wire	[31:0]	cpu_dout;
wire			cpu_nwait;
initial begin
	n_reset = 1'b1;
	for(int i=0;i<64*1024*1024;i++) dram_data[i] = $random;
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	cpu_cs = 1'b0;

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);

	// first miss
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A37B9C;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// hit
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A37B98;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// miss on the same cache line
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A3BB98;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// write hit
	cpu_cs = 1'b1;
	cpu_we = 1'b1;
	cpu_addr = 32'h00A3BB90;
	cpu_din = 32'hFFFFFFFF;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// miss & write back because of dirty
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A37B94;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// miss on the same cache line
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A3BB90;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			dram_cs;
wire			dram_we;
wire	[31:0]	dram_addr;
wire	[31:0]	dram_din;
wire	[31:0]	dram_dout;
wire			dram_nwait;

cache i_cache(
				.clk(clk),
				.n_reset(n_reset),

				.cpu_cs(cpu_cs),
				.cpu_we(cpu_we),
				.cpu_addr(cpu_addr),
				.cpu_din(cpu_din),
				.cpu_dout(cpu_dout),
				.cpu_nwait(cpu_nwait),

				.dram_cs(dram_cs),
				.dram_we(dram_we),
				.dram_addr(dram_addr),
				.dram_din(dram_din),
				.dram_dout(dram_dout),
				.dram_nwait(dram_nwait)
);

reg		[1:0]	cnt;
reg				dram_we_d;
reg		[31:0]	dram_addr_d;
reg		[31:0]	dram_din_d;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cnt <= 0;
	end else begin
		if((dram_cs == 1'b1) || (cnt > 0)) begin
			cnt <= cnt + 1;
		end
		if((dram_cs == 1'b1) && (dram_nwait == 1'b1)) begin
			dram_we_d <= dram_we;
			dram_addr_d <= dram_addr;
			dram_din_d <= dram_din;
		end
		if((dram_we_d == 1'b1) && (cnt == 3)) begin
			dram_data[dram_addr_d[31:2]] <= dram_din_d;
		end
	end
end

assign dram_dout = (dram_nwait==1'b1) ? dram_data[dram_addr_d[31:2]] : 'bx;
assign dram_nwait = (cnt == 0);

endmodule

 

기존의 방식처럼 4가지 경우의 수를 직접 기술한 테스트벤치입니다.

편하게 확장하기 위해 system verilog로 작성하였습니다.

 

 

- top1 Random generation

module top_cache;
parameter	DRAM_SIZE = 64*1024*1024;

reg		clk, n_reset;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[31:0]	dram_data[0:DRAM_SIZE-1];
reg	[31:0]	dram_data_ref[0:DRAM_SIZE-1];

initial begin
	$shm_open("./waveform");
	$shm_probe(top_cache,"AS");
end

reg				cpu_cs;
reg				cpu_we;
reg		[31:0]	cpu_addr;
reg		[31:0]	cpu_din;
wire	[31:0]	cpu_dout;
wire			cpu_nwait;
initial begin
	n_reset = 1'b1;
	for(int i=0;i<DRAM_SIZE;i++) begin
		dram_data[i] = $random;
		dram_data_ref[i] = dram_data[i];
	end
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	cpu_cs = 1'b0;

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	#6;

	repeat(10000) begin                   // -----> 10000 cases
		cpu_cs = $random % 2;             // -----> access or not
		if(cpu_cs == 1'b1) begin
			cpu_we = $random % 2;         // -----> read or write
			cpu_addr = {$random & (DRAM_SIZE-1), 2'b00}; // ----> random addr
			if(cpu_we == 1'b1) cpu_din = $random;        // ----> random data on write
			else cpu_din = 'bx;
		end else begin
			cpu_we = 1'bx;
			cpu_addr = 'bx;
			cpu_din = 'bx;
		end
		while(1) begin
			@(posedge clk);
			#6;
			if(cpu_nwait == 1'b1) begin
				break;
			end else begin
				cpu_cs = 1'bx;
				cpu_we = 1'bx;
				cpu_addr = 'bx;
				cpu_din = 'bx;
			end
		end
	end

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			dram_cs;
wire			dram_we;
wire	[31:0]	dram_addr;
wire	[31:0]	dram_din;
wire	[31:0]	dram_dout;
reg				dram_nwait;

cache i_cache(
				.clk(clk),
				.n_reset(n_reset),

				.cpu_cs(cpu_cs),
				.cpu_we(cpu_we),
				.cpu_addr(cpu_addr),
				.cpu_din(cpu_din),
				.cpu_dout(cpu_dout),
				.cpu_nwait(cpu_nwait),

				.dram_cs(dram_cs),
				.dram_we(dram_we),
				.dram_addr(dram_addr),
				.dram_din(dram_din),
				.dram_dout(dram_dout),
				.dram_nwait(dram_nwait)
);

reg		[1:0]	cnt;
reg				dram_we_d;
reg		[31:0]	dram_addr_d;
reg		[31:0]	dram_din_d;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cnt <= 0;
	end else begin
		if((dram_cs == 1'b1) || (cnt > 0)) begin
			cnt <= cnt + 1;
		end
		if((dram_cs == 1'b1) && (dram_nwait == 1'b1)) begin
			dram_we_d <= dram_we;
			dram_addr_d <= dram_addr;
			dram_din_d <= dram_din;
		end
		if((dram_we_d == 1'b1) && (cnt == 3)) begin
			dram_data[dram_addr_d[31:2]] <= dram_din_d;
		end
	end
end

assign dram_dout = (dram_nwait==1'b1) ? dram_data[dram_addr_d[31:2]] : 'bx;
assign dram_nwait = (cnt == 0);

reg		[31:0]	cpu_addr_d;
reg		[31:0]	cpu_din_d;
reg		[1:0]	cpu_prev_op;
always@(posedge clk) begin
	if(cpu_nwait == 1'b1) begin
		cpu_prev_op <= {cpu_cs, cpu_we};
		if(cpu_cs == 1'b1) begin
			cpu_addr_d <= cpu_addr;
			cpu_din_d <= cpu_din;
		end
		if(cpu_prev_op == 2'b10) begin
			if(dram_data_ref[cpu_addr_d[31:2]] == cpu_dout) begin
			end else begin
				$display("Error!! addr = %X, dram_data = %X, cpu_dout = %X",
					cpu_addr_d, dram_data_ref[cpu_addr_d[31:2]], cpu_dout);
				#10; $finish;
			end
		end else if(cpu_prev_op == 2'b11) begin
			dram_data_ref[cpu_addr_d[31:2]] <= cpu_din_d;
		end
	end
end


endmodule

 

직접 기술할 수 없을 수많은 케이스를 확인하기 위해

Random generation을 사용해 10000가지 경우의 수로 검증합니다.

 

 

- top2 (Constrained Random generation)

module top_cache;
parameter	DRAM_SIZE = 64*1024*1024;

reg		clk, n_reset;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[31:0]	dram_data[0:DRAM_SIZE-1];
reg	[31:0]	dram_data_ref[0:DRAM_SIZE-1];

initial begin
	$shm_open("./waveform");
	$shm_probe(top_cache,"AS");
end

reg				cpu_cs;
reg				cpu_we;
reg		[31:0]	cpu_addr;
reg		[31:0]	cpu_din;
wire	[31:0]	cpu_dout;
wire			cpu_nwait;
reg		[31:0]	p_addr;
initial begin
	n_reset = 1'b1;
	for(int i=0;i<DRAM_SIZE;i++) begin
		dram_data[i] = $random;
		dram_data_ref[i] = dram_data[i];
	end
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	cpu_cs = 1'b0;

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	#6;

	p_addr = {$random & (DRAM_SIZE-1), 2'b00};
	repeat(10000) begin
		cpu_cs = $random % 2;
		if(cpu_cs == 1'b1) begin
			cpu_we = $random % 2;
			if($random%4 > 0) begin         // -----> in 75% probability
				if($random%2 == 0) cpu_addr = p_addr + $random%4 * 4;     // --    increase
				else cpu_addr = p_addr - $random%4 * 4;                   //  |--> or decrease
				if(cpu_addr >= DRAM_SIZE*4) cpu_addr = (DRAM_SIZE-1) * 4; // --    previous addr
			end else begin                  // -----> in 25% probability
				cpu_addr = {$random & (DRAM_SIZE-1), 2'b00};  // ----> new random addr
			end
			p_addr = cpu_addr;                                // ----> save generated addr
			if(cpu_we == 1'b1) cpu_din = $random;
			else cpu_din = 'bx;
		end else begin
			cpu_we = 1'bx;
			cpu_addr = 'bx;
			cpu_din = 'bx;
		end
		while(1) begin
			@(posedge clk);
			#6;
			if(cpu_nwait == 1'b1) begin
				break;
			end else begin
				cpu_cs = 1'bx;
				cpu_we = 1'bx;
				cpu_addr = 'bx;
				cpu_din = 'bx;
			end
		end
	end

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			dram_cs;
wire			dram_we;
wire	[31:0]	dram_addr;
wire	[31:0]	dram_din;
wire	[31:0]	dram_dout;
reg				dram_nwait;

cache i_cache(
				.clk(clk),
				.n_reset(n_reset),

				.cpu_cs(cpu_cs),
				.cpu_we(cpu_we),
				.cpu_addr(cpu_addr),
				.cpu_din(cpu_din),
				.cpu_dout(cpu_dout),
				.cpu_nwait(cpu_nwait),

				.dram_cs(dram_cs),
				.dram_we(dram_we),
				.dram_addr(dram_addr),
				.dram_din(dram_din),
				.dram_dout(dram_dout),
				.dram_nwait(dram_nwait)
);

reg		[1:0]	cnt;
reg				dram_we_d;
reg		[31:0]	dram_addr_d;
reg		[31:0]	dram_din_d;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cnt <= 0;
	end else begin
		if((dram_cs == 1'b1) || (cnt > 0)) begin
			cnt <= cnt + 1;
		end
		if((dram_cs == 1'b1) && (dram_nwait == 1'b1)) begin
			dram_we_d <= dram_we;
			dram_addr_d <= dram_addr;
			dram_din_d <= dram_din;
		end
		if((dram_we_d == 1'b1) && (cnt == 3)) begin
			dram_data[dram_addr_d[31:2]] <= dram_din_d;
		end
	end
end

assign dram_dout = (dram_nwait==1'b1) ? dram_data[dram_addr_d[31:2]] : 'bx;
assign dram_nwait = (cnt == 0);

reg		[31:0]	cpu_addr_d;
reg		[31:0]	cpu_din_d;
reg		[1:0]	cpu_prev_op;
always@(posedge clk) begin
	if(cpu_nwait == 1'b1) begin
		cpu_prev_op <= {cpu_cs, cpu_we};
		if(cpu_cs == 1'b1) begin
			cpu_addr_d <= cpu_addr;
			cpu_din_d <= cpu_din;
		end
		if(cpu_prev_op == 2'b10) begin
			if(dram_data_ref[cpu_addr_d[31:2]] == cpu_dout) begin
			end else begin
				$display("Error!! addr = %X, dram_data = %X, cpu_dout = %X",
					cpu_addr_d, dram_data_ref[cpu_addr_d[31:2]], cpu_dout);
				#10; $finish;
			end
		end else if(cpu_prev_op == 2'b11) begin
			dram_data_ref[cpu_addr_d[31:2]] <= cpu_din_d;
		end
	end
end


endmodule

 

무작위로 할 경우, 원하는 상황이 나올지 안 나올지 확인하기 힘듭니다.

제약조건을 걸어 75% 확률로 hit를 만들고,

이전 주소에서 word단위로 이동되도록 합니다.

이 과정에서 DRAM의 유효범위를 넘지 않게 합니다.

 

 

-top3 (task 적용)

module top_cache;
parameter	DRAM_SIZE = 64*1024*1024;

reg		clk, n_reset;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[31:0]	dram_data[0:DRAM_SIZE-1];
reg	[31:0]	dram_data_ref[0:DRAM_SIZE-1];

initial begin
	$shm_open("./waveform");
	$shm_probe(top_cache,"AS");
end

reg				cpu_cs;
reg				cpu_we;
reg		[31:0]	cpu_addr;
reg		[31:0]	cpu_din;
wire	[31:0]	cpu_dout;
wire			cpu_nwait;

task mem_drive (
	input			cs,
	input			we,
	input	[31:0]	addr,
	input	[31:0]	din
);
begin
	cpu_cs = cs;
	if(cs == 1'b1) begin
		cpu_we = we;
		cpu_addr = addr;
		if(we == 1'b1) cpu_din = din;
		else cpu_din = 'bx;
	end else begin
		cpu_we = 1'bx;
		cpu_addr = 'bx;
		cpu_din = 'bx;
	end
	while(1) begin
		@(posedge clk);
		#6;
		if(cpu_nwait == 1'b1) begin
			break;
		end else begin
			cpu_cs = 1'bx;
			cpu_we = 1'bx;
			cpu_addr = 'bx;
			cpu_din = 'bx;
		end
	end
end
endtask

reg				cs;
reg				we;
reg		[31:0]	addr;
reg		[31:0]	din;
reg		[31:0]	p_addr;

initial begin
	n_reset = 1'b1;
	for(int i=0;i<DRAM_SIZE;i++) begin
		dram_data[i] = $random;
		dram_data_ref[i] = dram_data[i];
	end
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	cpu_cs = 1'b0;

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	#6;

	// first miss
	mem_drive(1'b1, 1'b0, 32'h00A37B9C, 'b0);
	// hit
	mem_drive(1'b1, 1'b0, 32'h00A37B98, 'b0);
	// miss on the same cache line
	mem_drive(1'b1, 1'b0, 32'h00A3BB98, 'b0);
	// write hit
	mem_drive(1'b1, 1'b1, 32'h00A3BB90, 32'hFFFFFFFF);
	// miss & write back because of dirty
	mem_drive(1'b1, 1'b0, 32'h00A37B94, 'b0);
	// miss on the same cache line
	mem_drive(1'b1, 1'b0, 32'h00A3BB90, 'b0);

	p_addr = {$random & (DRAM_SIZE-1), 2'b00};
	repeat(10000) begin
		cs = $random % 2;
		we = $random % 2;
		if($random%4 > 0) begin
			if($random%2 == 0) addr = p_addr + $random%4 * 4;
			else addr = p_addr - $random%4 * 4;
			if(addr >= DRAM_SIZE*4) addr = (DRAM_SIZE-1) * 4;
		end else begin
			addr = {$random & (DRAM_SIZE-1), 2'b00};
		end
		din = $random;
		p_addr = addr;

		mem_drive(cs, we, addr, din);
	end

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			dram_cs;
wire			dram_we;
wire	[31:0]	dram_addr;
wire	[31:0]	dram_din;
wire	[31:0]	dram_dout;
reg				dram_nwait;

cache i_cache(
				.clk(clk),
				.n_reset(n_reset),

				.cpu_cs(cpu_cs),
				.cpu_we(cpu_we),
				.cpu_addr(cpu_addr),
				.cpu_din(cpu_din),
				.cpu_dout(cpu_dout),
				.cpu_nwait(cpu_nwait),

				.dram_cs(dram_cs),
				.dram_we(dram_we),
				.dram_addr(dram_addr),
				.dram_din(dram_din),
				.dram_dout(dram_dout),
				.dram_nwait(dram_nwait)
);

reg		[1:0]	cnt;
reg				dram_we_d;
reg		[31:0]	dram_addr_d;
reg		[31:0]	dram_din_d;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cnt <= 0;
	end else begin
		if((dram_cs == 1'b1) || (cnt > 0)) begin
			cnt <= cnt + 1;
		end
		if((dram_cs == 1'b1) && (dram_nwait == 1'b1)) begin
			dram_we_d <= dram_we;
			dram_addr_d <= dram_addr;
			dram_din_d <= dram_din;
		end
		if((dram_we_d == 1'b1) && (cnt == 3)) begin
			dram_data[dram_addr_d[31:2]] <= dram_din_d;
		end
	end
end

assign dram_dout = (dram_nwait==1'b1) ? dram_data[dram_addr_d[31:2]] : 'bx;
assign dram_nwait = (cnt == 0);

reg		[31:0]	cpu_addr_d;
reg		[31:0]	cpu_din_d;
reg		[1:0]	cpu_prev_op;
always@(posedge clk) begin
	if(cpu_nwait == 1'b1) begin
		cpu_prev_op <= {cpu_cs, cpu_we};
		if(cpu_cs == 1'b1) begin
			cpu_addr_d <= cpu_addr;
			cpu_din_d <= cpu_din;
		end
		if(cpu_prev_op == 2'b10) begin
			if(dram_data_ref[cpu_addr_d[31:2]] == cpu_dout) begin
			end else begin
				$display("Error!! addr = %X, dram_data = %X, cpu_dout = %X",
					cpu_addr_d, dram_data_ref[cpu_addr_d[31:2]], cpu_dout);
				#10; $finish;
			end
		end else if(cpu_prev_op == 2'b11) begin
			dram_data_ref[cpu_addr_d[31:2]] <= cpu_din_d;
		end
	end
end


endmodule

 

처음에 의도한 4개의 case를 보고, random 한 케이스를 봅니다.

4개의 case는 주소만 다를 뿐 같은 작업이므로,

task 함수를 만들어 활용합니다.

 

 

4. 시뮬레이션 결과

동작을 확인해 보기 위해 function simulation을 합니다.

Tool은 Xcelium과 VCS를 사용하였습니다.

 

VCS 툴에서는 Coverage를 제공합니다.

Coverage는, Statement, line, block coverage를 계산해

모든 경우의 수 중 얼마나 검증해 봤는가 확인해 주는 기능입니다.

 

다음과 같이 사용할 수 있으며, 이후 결과만 보여드리도록 하겠습니다.

 

- 명령어

vcs -full64 -sverilog -debug_access+all top0.v cache.v mem_sim.v -cm line+cond+fsm+tgl+branch -cmhier cov.conf

./simv -cm line+cond+fsm+tgl+branch
urg -full64 -dir ./simv.vdb -format text
vi urgReport/modinfo.txt

 

 

- cov.conf

+tree top_cache.i_cache

 

 

 

 

4-1. top0 (Testcase 직접구상)

 

1. 처음으로 읽는 부분은 miss이고, dram에서 데이터를 읽어옵니다.

이때, 근처의 데이터 포함 4개의 데이터가 읽어져 cache에 저장됩니다.

 

2. 그다음 비슷한 주소를 읽으면, cache에 저장된 데이터가 즉시 불러와집니다.

 

1. 그 다음 miss에 의해 다시 dram에서 데이터를 읽어옵니다.

 

2. 그 다음 cache에 저장된 주소중 하나에 쓰기를 진행하면(hit), 즉시 cache의 데이터가 수정됩니다.

 

 

1. 이후 miss 읽기를 진행하면, dirty bit가 1이기 때문에,

먼저 기존 주소의 dram을 update 하기 위해 쓰기를 진행합니다.

 

2. 그다음 원래 요청주소에 대해 dram에서 데이터를 읽어옵니다.

 

 

- Coverage_top0

 

직접 만든 케이스들은, 모든 경우의 수를 테스트하지 못합니다.

그래서 coverage score 역시 매우 낮습니다.

 

 

 

4-2. top1 (random generation)

 

random generation에 의해 1만 가지 case가 동작하고 있습니다.

 

- Coverage_top1

 

top0와 달리 점수가 월등하게 오른 것을 볼 수 있습니다.

 

 

 

4-3. top2 (Constrained random generation)

이 경우 역시 top1과 마찬가지로 waveform은 의미가 없으므로, 바로 점수를 보도록 하겠습니다.

 

 

- Coverage_top2

 

조금이지만 score가 더 올라간 것을 확인할 수 있습니다.

 

 

 

 

위처럼 직접 찾아 확인해 볼 수 있고, 다시 방법을 찾아 검증할 수 있습니다.

현재는 Cover 되지 않은 부분 중에 문제가 되는 경우는 없습니다.

따라서 여기까지만 테스트하도록 합니다.

 

 

 

※참고: 직접 확인하는 방법 말고, functional coverage를 사용하는 방법도 있습니다.

program automatic test;
    covergroup fcov @(port_event);   // coverage group 생성
        coverpoint sa;
        coverpoint da;
    endgroup: fcov

    bit[3:0] sa, da;
    event port_event;
    real coverage = 0.0;
    fcov port_fc = new();    // Instantiate & coverage object 생성 

    initial while (coverage < 99.5) begin
        ...
        sa = pkt_ref.sa;
        da = pkt_ref.da;
        ->port_event;      // port_fc coverage group의 data가 sampling 됨
        // port_fc.sample(); // alternative form of updating of bins

        coverage = $get_coverage(); // overall coverage  , coverage result query
        // coverage = port_fc.get_inst_coverage(); // instance coverage
    end
endprogram: test

출처: https://wikidocs.net/172887

 

다음 검증에는 위와 같은 방법도 고려해보려고 합니다.

 

 

● 진행기간

 - 2022.03.07 ~ 22.04.01

 

● 요약

 - 유통회사의 고객 구매데이터 2700만 건을 분석

 - 고객 유형별 솔루션과 개인별 솔루션을 합쳐 유의미한 추천 시스템 구축

 

● 역할

 - 메인 이슈 탐색, 군집분석, 변수개발, 머신러닝 알고리즘, 추천시스템 개발 및 솔루션 도출 (5명 중 기여도 60%)

 

● 성과

 - 방대한 데이터에서 필요한 부분을 빠르게 추출하고 분류하는 방법 학습

 - 많은 고객의 개별 행동들을 분석하기 위해 데이터들을 가공하는 방법 학습

 - 테이블 생성 및 연결 관리의 중요성을 학습

 - 도메인지식의 중요성 인식

 - ETL의 기본과 고객데이터 분석에 대한 중요한 경험이 되었음

 

 

https://github.com/MiddleJo/Marketing_solutions_for_target_customer

 

GitHub - MiddleJo/Marketing_solutions_for_target_customer: Marketing solutions for target customers decreasing in purchases.

Marketing solutions for target customers decreasing in purchases. - MiddleJo/Marketing_solutions_for_target_customer

github.com

 

 

제 깃허브 (위 링크) 에 상세한 설명이 있습니다.

PWM.zip
10.04MB

목차

1. 배경

2. 보드 구성 및 소스코드

3. 실습결과

 

 

1. 배경

 

칩 설계를 공부하다 보니, 모터 제어가 필요한 경우가 있었습니다.

모터 제어 등에서 진동수 혹은 주기를 다룰 때는 PWM이 필요합니다.

 

 

 

이번에는 PWM과 Buzzer를 이용해 노래를 연주해보려고 합니다.

 

 

 

 

 

 

2. 보드 구성 및 소스코드

이번에도 역시 다른 분석 없이 PWM의 이론을 테스트할 것이기 때문에,

Board Select 방식으로 진행하겠습니다.

 

 

 

- 보드 세팅

 

 

 

- TIMER 세팅

 

 

Prescaler는 설정하기 나름입니다.

결과적으로 원하는 주파수만 계산할 수 있으면 됩니다.

 

 

 

- PV

/* USER CODE BEGIN PV */
#define c 261.63
#define c_sharp 277.18
#define d 293.66
#define d_sharp 311.13
#define e 329.63
#define f 349.23
#define f_sharp 369.99
#define g 392.00
#define g_sharp 415.30
#define a 440.00
#define a_sharp 466.16
#define b 493.88

#define C 523.25
#define C_SHARP 554.37
#define D 587.33
#define D_SHARP 622.25
#define E 659.25
#define F 698.46
#define F_SHARP 739.99
#define G 783.99
#define G_SHARP 830.61
#define A 880.00
#define A_SHARP 932.33
#define B 987.77
#define REST 0

float melody[] = {E       , D_SHARP, E      , D_SHARP, E      , b      , D      , C      ,
		a      , REST   , c      , e      , a      , b      , REST   ,
		e      , g_sharp, b      , C      , REST   , e      ,
		E       , D_SHARP, E      , D_SHARP, E      , b      , D      , C      ,
		a      , REST   , c      , e      , a      , b      , REST   , e      , C      , b      , a     };
int beats[] = 	 {200     , 200    , 200    , 200    , 200    , 200    , 200    , 200    ,
		400    , 200    , 200    , 200    , 200    , 400    , 200    ,
		200    , 200    , 200    , 400    , 200    , 200    ,
		200     , 200    , 200    , 200    , 200    , 200    , 200    , 200    ,
		400    , 200    , 200    , 200    , 200    , 400    , 200    , 200    , 200    , 200    , 800   };
int melody_size = sizeof(melody) / sizeof(melody[0]);

/* USER CODE END PV */

 

저는 "엘리제를 위하여" 곡의 일부를 연주하기 위해 melody를 구성하였습니다.

각 음들의 주파수 값들은 변수로 정의되어 있고,

beats는 그 음을 얼마나 유지할 것인지를 담고 있습니다.

 

 

 

- Main

 

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ETH_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    for (int i = 0; i < melody_size; i++) {
      float frequency = melody[i];
      uint16_t value = (uint16_t)((1000000 / frequency) - 1); // ARR 계산
      TIM3->ARR = value;
      TIM3->CCR3 = value / 2; // 듀티 사이클 50%

      HAL_Delay(beats[i]);
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

첫 음의 주파수를 가져와 ARR과 CCR을 계산하고,

부저는 해당 음을 내게 됩니다.

beats에 담긴 길이동안 음이 지속되고,

loop에 의해 노래가 연주됩니다.

 

 

3. 실습결과

 

 

기계음이라 어색하긴 하지만, 연주가 잘 됨을 확인할 수 있습니다.

 

RTC.zip
10.76MB

목차

1. 배경

2. 보드 구성 및 소스코드

3. 실습결과

 

 

1. 배경

 

다양한 작업을 하다 보면 타이머가 필요한 경우가 있습니다.

RTC는 다양한 시간 단위로 컨트롤할 수 있고, 알람 인터럽트를 제공합니다.

 

간단하게 10초마다 알람을 울리는 알람시계를 만들어보도록 하겠습니다.

 

2. 보드 구성 및 소스코드

기본적으로, Board Select 선택 시 불필요한 기능이 포함되어

코드가 길어지고 보기 불편합니다.

 

이번에는, Oscillator 설정 등의 설정이 복잡하여

Board select로 선택하고 진행하도록 하겠습니다.

 

 

 

- 보드 세팅

 

 

- RCC 세팅

 

이번에도 LSE를 사용하도록 하겠습니다.

 

 

 

- NVIC 세팅

 

RTC Interrupt를 사용하기 위해 세팅해 줍니다.

 

 

- RTC 설정

 

이후에 코드에서도 바꿀 수 있지만, 여기서 보기 좋게 바꿀 수 있습니다.

Seconds 메뉴에 10 입력하여 10초마다 울리게 만들도록 하겠습니다.

 

 

 

 

- PV

/* USER CODE BEGIN PV */
//char ampm[2][3] = {"AM","PM"};
char *ampm[] = {"AM","PM"}; // 1d array
char temp[100];
char s1[100];

//#define RTC_HOURFORMAT12_AM = ((uint8_t)0x00)
//#define RTC_HOURFORMAT12_PM = ((uint8_t)0x40)

void get_time(void)
{
	RTC_DateTypeDef sDate;
	RTC_TimeTypeDef sTime;

	HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
	HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);

	sprintf(temp, "\r\n20%02d-%02d-%02d %s %02d:%02d:%02d", sDate.Year, sDate.Month,
	sDate.Date, ampm[sTime.TimeFormat >> 6], sTime.Hours, sTime.Minutes, sTime.Seconds);
	sprintf(s1,"%d",sTime.TimeFormat);
	sprintf(s1,"%d",sTime.Hours);
	sprintf(s1,"%d",5);
}
/* USER CODE END PV */

 

PV함수와 함께, get time 함수를 정의합니다.

시간에 대해 문자열을 만들어줍니다.

중간에 define 함수는 stm32f4xx_hal_rtc.h에서 바꿔줘도 됩니다.

 

 

 

 

- Main

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ETH_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();
  MX_RTC_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  get_time();
	  HAL_UART_Transmit(&huart3, (uint8_t *)&temp, strlen(temp), 1000);
	  HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

앞서 정의한 get_time 함수로 시간 정보를 받고,

UART를 통해 PC에 보여줍니다.

 

 

- HAL_RTC_Alarm Callback

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
	printf("\r\nAlarm Callback Occurred!! \r\n");

	HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7);

//	// load time
//	RTC_TimeTypeDef sTime;
//	HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN);
//
//	// reset alarm
//	RTC_AlarmTypeDef sAlarm = {0};
//	sAlarm.AlarmTime.Hours = sTime.Hours;
//	sAlarm.AlarmTime.Minutes = sTime.Minutes;
//	sAlarm.AlarmTime.Seconds = (sTime.Seconds + 10) % 60;
//	if ((sTime.Seconds + 10) >= 60) {
//		sAlarm.AlarmTime.Minutes = (sTime.Minutes + 1) % 60;
//		if ((sTime.Minutes + 1) >= 60) {
//			sAlarm.AlarmTime.Hours = (sTime.Hours + 1) % 24;
//		}
//	}
//	sAlarm.AlarmTime.SubSeconds = 0;
//	sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
//	sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
//	sAlarm.AlarmMask = RTC_ALARMMASK_HOURS | RTC_ALARMMASK_MINUTES | RTC_ALARMMASK_DATEWEEKDAY;
//	sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
//	sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
//	sAlarm.AlarmDateWeekDay = 1;
//	sAlarm.Alarm = RTC_ALARM_A;
//
//	if (HAL_RTC_SetAlarm_IT(hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
//	{
//		Error_Handler();
//	}
}

 

주석은 다른 실험을 위해 작성하였습니다. 작동에는 지장이 없습니다.

 

 

3. 실습결과

 

 

 

10초가 되었을 때 알람이 잘 울리는 것을 알 수 있습니다.

실습 당시 날짜를 않고 실행하였는데, 보드 설정에서 설정하실 수 있습니다.

 

ADC.zip
10.47MB

목차

1. 배경

2. 보드 구성 및 소스코드

3. 실습결과

 

 

1. 배경

 

ADC는 아날로그 신호를 디지털로 변환해 주는 장치입니다.

현실에서는 다 아날로그 신호로 되어 있죠.

우리가 사용할 디지털 신호로 바꾸는 방법은 굉장히 중요합니다.

 

저는 가지고 있는 LCM1602 IIC 쉴드 장치를 가지고 실습해보려고 합니다.

출처: https://www.devicemart.co.kr/goods/view?no=1279486

 

사진에 보이는 것처럼 버튼들이 있는데,

버튼들의 Digital 변환값을 확인할 것입니다.

 

 

 

위의 이론상의 계산값과 실습을 통한 결과값이 일치하는지 확인해보려고 합니다.

 

 

2. 보드 구성 및 소스코드

기본적으로, Board Select 선택 시 불필요한 기능이 포함되어

코드가 길어지고 보기 불편합니다.

따라서 MCU 방법으로 직접 세팅하겠습니다.

기본 상태에서 코딩이 필요한 부분만을 언급할 예정이며,

자세한 것은 첨부된 파일을 참고하시면 됩니다.

 

- 보드 세팅

 

- ADC 세팅

 

Polling 모드가 아닌 Interrupt 모드가 빠르기 때문에

저는 Interrupt 방식을 채택할 것입니다.

 

 

- Main

 

 

- ADC Callback

/* USER CODE BEGIN 4 */

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
  if(hadc -> Instance == ADC1)
  {
	  adc_value = HAL_ADC_GetValue(&hadc1);
	  memset(uart_buf, 0, sizeof(uart_buf));
	  sprintf(uart_buf,"ADC Value : %d \r\n", adc_value);
//	  sprintf(uart_buf,"ADC_Value\n%d", adc_value);
	  HAL_UART_Transmit(&huart3, (uint8_t *)uart_buf, sizeof(uart_buf), 1000);
//	  HAL_ADC_Start_IT(&hadc1);
	  HAL_ADC_Start_DMA(&hadc1, (uint32_t *)uart_buf, 1);
  }
}

/* USER CODE END 4 */

 

인터럽트 함수를 찾는 법은 이전 글 참고 부탁드립니다.

memset 함수는 메모리를 원하는 사이즈로 조절하는 함수입니다.

sprintf 함수는 문자열을 생성해 버퍼에 저장하는 함수입니다.

HAL_ADC_Start_DMA 함수는 DMA 전송이 완료되면 인터럽트를 활성화하는 함수입니다.

 

 

- PV

 

 

3. 실습결과

 

좌측부터 Up, Down, Left, Right, Select 버튼을 눌렀을 때의 값입니다.

 

이론상의 값과 비슷한 것을 확인할 수 있습니다.

 

UART.zip
8.55MB

목차

1. 배경

2. 보드 구성 및 소스코드

3. 실습결과

 

 

1. 배경

 

모든 칩에는 통신 모듈이 들어가 있습니다.

그래야 정보를 교환할 수 있으니까요.

UART는 PC와 소통할 때 가장 많이 사용하고,

직관적이고 간단합니다.

다른 작업에도 PC로 보기 위해 알아둘 필요가 있습니다.

 

 

제가 사용하는 STM32 F429 보드는 위와 같은 방식으로 PC와 통신합니다.

F103 파트에 데이터를 전달하고, USB로 바꿔주는 방식인데요.

 

 

 

회로도를 확인해 봐도, STLK_RX와 STLK_TX로 연결되어 있음을 확인할 수 있습니다.

따라서, 저는 많은 포트 중 UART 3번 포트를 활용하려고 합니다.

 

 

2. 보드 구성 및 소스코드

기본적으로, Board Select 선택 시 불필요한 기능이 포함되어

코드가 길어지고 보기 불편합니다.

따라서 MCU 방법으로 직접 세팅하겠습니다.

기본 상태에서 코딩이 필요한 부분만을 언급할 예정이며,

자세한 것은 첨부된 파일을 참고하시면 됩니다.

 

- 보드 세팅

 

 

- RCC 세팅

 

Nucleo 보드에서는 내부 클럭보다 외부 클럭이 더 정확하다고 하고,

ST-LINK MCO는 8 MHz를 사용합니다.

따라서, 위 사진과 같이 LSE를 사용해 줄 필요가 있습니다.

 

 

- NVIC 세팅

 

 

UART Interrupt를 사용하기 위해 세팅해 줍니다.

저희는 테스트를 위해 이 인터럽트가 가장 우선순위가 되도록 설정하겠습니다.

 

 

- UART 설정

 

이후에 코드에서도 바꿀 수 있지만, 여기서 통신 방식에 대해 설정하고 갈 수 있습니다.

 

 

- (참고) printf 사용을 위한 세팅

printf를 사용하기 위해서는 Syscalls.c에서 extern 처리되어 있는

위 _write 함수 혹은 __io_putchar 함수를 수정하여 main에 가져와 사용할 수 있습니다.

제가 마지막으로 보여드릴 예시에는 printf 함수가 필요하지 않습니다.

 

 

- UART_RxCPltCallback

 

stm32 f4 xx_hal_uart.c 에서 위와 같은 함수를 찾을 수 있습니다.

이것을 main으로 가져와 사용할 것입니다.

 

 

 

- Main

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();
  /* USER CODE BEGIN 2 */

  HAL_UART_Receive_IT(&huart3, (uint8_t *)&rx_data, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
//	  // printf Test
//	  printf("Hello, STM32World %d\n", i++);
//	  HAL_Delay(1000);
//
//	  // Create string test
//	  memset(data, 0, sizeof(data));
//	  sprintf(data, "%d. Hello STM32F249 World !!! \n", i++);
//	  HAL_UART_Transmit(&huart3, (uint8_t *)data, strlen(data), 500);
//	  HAL_Delay(1000);

// 	  // Echo Test
// 	  if (HAL_UART_Receive(&huart3, (uint8_t *)&data_echo, sizeof(data), 500))
//	  {
//	    HAL_UART_Transmit(&huart3, (uint8_t *)&data_echo, sizeof(data), 500);
//	  }

//	  // Polling Test
//	  if (HAL_UART_Receive(&huart3, (uint8_t *)&data, 1, 1000) == HAL_OK)
//	  {
//	  	HAL_UART_Transmit(&huart3, (uint8_t *)&data, 1, 1000);
//	  }
	  //
	  switch (led_mode)
	  {
	  	  case 1 : GPIOB -> ODR = 0x0001; break;
	  	  case 2 : GPIOB -> ODR = 0x0080; break;
	  	  case 3 : GPIOB -> ODR = 0x4000; break;
	  	  case 0 : GPIOB -> ODR = 0x0000; break;
	  }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

주석처리된 것에서 다른 예시를 확인할 수 있습니다.

while문 앞에 HAL_UART_Receive_IT 함수를 한번 초기화했습니다.

 

인터럽트로부터 변한 led_mode 변수에 따라

PB0, PB7, PB14에 HIGH 신호를 주게 됩니다.

 

ODR에 직접 값을 입력하면, 굉장히 빠르지만

어떤 경우에는 빠른 것이 좋지 않을 수 있습니다.

현재는 상관없으므로, ODR 방법을 사용하였습니다.

 

Reference Manual을 보면, ODR의 어떤 비트가 어떤 제어를 하는지 알 수 있고,

저는 상황에 맞게 16진수로 명령을 넣어주었습니다.

 

 

- HAL_UART_RxCpltCallback

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart -> Instance == USART3)
  {
	  HAL_UART_Receive_IT(&huart3, (uint8_t *)&rx_data, 1);
	  HAL_UART_Transmit(&huart3, (uint8_t *)&rx_data, 1, 500);
	  switch (rx_data)
	  {
	  case '1': led_mode = 1; break;
	  case '2': led_mode = 2; break;
	  case '3': led_mode = 3; break;
	  default: led_mode = 0;
	  }

  }

}

 

인터럽트는 UART통신을 통해 받은 문자가 1, 2, 3일 경우

led_mode 변수를 변화시키고, 이것이 main 함수의 작동을 돕습니다.

 

 

- PV

 

 

 

3. 실습결과

 

 

1을 입력하면 초록불(PB0)이,

2를 입력하면 파란불(PB7)이,

3을 입력하면 빨간불(PB14)이 켜집니다.

PC와 잘 통신하고 있음을 알 수 있습니다.

 

EXTI.zip
7.04MB

목차

1. 배경

2. 보드 구성 및 소스코드

3. 실습결과

 

 

1. 배경

 

 

EXTI는 외부 인터럽트라는 뜻입니다.

인터럽트는 CPU의 효율이 떨어지지 않고

특정 이벤트들을 처리할 수 있도록 도와줍니다.

또한, 다른 방식보다 처리 속도가 굉장히 빠르기 때문에

사용을 위해 알아둘 필요가 있습니다.

 

 

2. 보드 구성 및 소스코드

기본적으로, Board Select 선택 시 불필요한 기능이 포함되어

코드가 길어지고 보기 불편합니다.

따라서 MCU 방법으로 직접 세팅하겠습니다.

기본 상태에서 코딩이 필요한 부분만을 언급할 예정이며,

자세한 것은 첨부된 파일을 참고하시면 됩니다.

 

- 보드 세팅

 

- NVIC 세팅

 

 

 

 

NUCLEO-F429ZI 매뉴얼을 참고하여

몇 가지 실험할 포트만 활성화하였습니다.

또한, 인터럽트 사용을 위해 NVIC 설정을 해주었습니다.

 

- 인터럽트 신호 선택

 

PC13으로 연결된 USER 버튼으로 인터럽트를 일으키겠습니다.

HAL_GPIO_EXTI_IRQHandler 함수를 블록 씌우고 F3을 누르면,

 

 

다음과 같은 함수를 확인할 수 있고,

아래에 있는 HAL_GPIO_EXTI_Callback 함수는 weak 처리되어 있습니다.

이제 이 함수를 main.c로 가져와 커스텀합니다.

 

- EXTI_Callback

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if (GPIO_Pin == GPIO_PIN_13)
		{
			EXTI_flag = 1;
		}
}
/* USER CODE END 4 */

 

위와 같이 버튼이 눌렸을 때 flag를 1로 주어 main 함수에서 활용할 수 있도록 합니다.

 

 

- Main

/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  switch (currentState)
	      {
	        case STATE_SEG_DISPLAY:
	          if (EXTI_flag == 1)
	          {
	            currentState = STATE_EXTI_PROCESS;
	            EXTI_flag = 0;
	            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
//	            last_tick = HAL_GetTick();
	          }
	          else
	          {
//	            if (HAL_GetTick() - last_tick >= 1000)
//	            {
	              GPIOD->BSRR = seg[segment_index];
	              segment_index = (segment_index + 1) % 10;
	              HAL_Delay(1000);
//	              last_tick = HAL_GetTick();
//	            }
	          }
	          break;

	        case STATE_EXTI_PROCESS:
	          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
	          HAL_Delay(1000);
	          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
	          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
	          HAL_Delay(1000);
	          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
	          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
	          HAL_Delay(1000);
	          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);

	          currentState = STATE_SEG_DISPLAY;
//	          last_tick = HAL_GetTick();  // 상태가 복귀될 때 시간 재설정
	          break;

	  }
	  /* USER CODE END WHILE */

 

대기상태로 기다리다가, EXTI_flag 신호가 들어오면 EXTI Process로 전이합니다.

PB0, PB7, PB14 (LED들)을 차례대로 키고 끈 뒤, 다시 작업으로 복귀합니다.

 

flag가 0인 경우에는, FND의 숫자가 계속 증가하도록 하는데,

아래와 같이 변수로 정의해 둔 것을 사용합니다.

자세한 7-segment 설명은 생략합니다.

 

tick의 경우는 인터럽트 이후 시간을 기다렸다가 갈 건지 등을 조절하기 위해 만들었습니다만,

굳이 사용하지 않겠습니다.

 

- PV

/* USER CODE BEGIN PV */

typedef enum {
  STATE_SEG_DISPLAY,
  STATE_EXTI_PROCESS
} StateType;

StateType currentState = STATE_SEG_DISPLAY;
volatile uint8_t EXTI_flag = 0;
volatile uint8_t segment_index = 0;
// volatile uint32_t last_tick = 0;

uint32_t seg[10] = {
        0x003f00c0,  // 0
        0x000600f9,  // 1
        0x005b00a4,  // 2
        0x004f00b0,  // 3
        0x00660099,  // 4
        0x006d0092,  // 5
        0x007d0082,  // 6
        0x002700d8,  // 7
        0x007f0080,  // 8
        0x006f0090   // 9
};

/* USER CODE END PV */

 

 

 

3. 실습결과

 

 

시간에 따라 FND가 증가하다가,

버튼을 누르면 인터럽트에 의해 LED가 차례대로 켜지고,

다시 증가합니다.

 

Test_GPIO.zip
9.17MB

목차

1. 배경

2. 보드 구성 및 소스코드

3. 실습결과

 

 

1. 배경

GPIO는 사용자가 다양한 목적으로 자유롭게 사용할 수 있도록 마련해둔 Pin입니다.

LED를 다룬다거나, 다른 Bread board에 실험을 하는 등

다양한 곳에서 사용하는 가장 기초이기 때문에 먼저 학습하겠습니다.

2. 보드 구성 및 소스코드

기본적으로, Board Select 선택 시 불필요한 기능이 포함되어

코드가 길어지고 보기 불편합니다.

따라서 MCU 방법으로 직접 세팅하겠습니다.

기본 상태에서 코딩이 필요한 부분만을 언급할 예정이며,

자세한 것은 첨부된 파일을 참고하시면 됩니다.

 

 

 

NUCLEO-F429ZI 메뉴얼을 참고하여

몇가지 실험할 포트만 활성화 하였습니다.

이 때, Closed 되어 있는 포트만 골라야 합니다.

아니라면 따로 납땜하여 달아야 사용이 가능한 경우도 있습니다.

 

 /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
	     if (HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_14) == GPIO_PIN_SET)
	     {
	        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
	     }
	     else
	     {
	        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
	     }
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

 

포트 F14 에 HIGH 신호가 들어오면, 포트 B01를 LOW 신호로 바꾸고

그 외에는 HIGH 신호로 유지합니다.

 

 

3. 실습결과

 

 

 

스위치를 눌러 PF14에 HIGH 신호를 전달하면,

PB1에 연결된 전구의 불이 꺼짐을 확인할 수 있습니다.

 

- 이전 글

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

 

UART 통신 - (1) RTL

목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경지난 프로젝트에서 컴퓨터와 신호를 주고받기 위해 UART 통신을 사용하였는데요,UART 통신도 이해하고 Verilog로 구현해보고

chonh0531.tistory.com

 

목차

1. 과제 개요

2. 소스코드

3. 실습 결과

 

 

1. 과제 개요

이전에 구현해 본 UART_RX를 테스트해 보기로 합니다.

UART 신호 생성은 ARM Cortex-M4 Core가 있는 NUCLEO-F429ZI 보드를 이용하여

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

 

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

 

 

 

2. 소스코드

- Top 모듈 Uart_lcd

`timescale 1ns / 1ps

module uart_lcd(
input clk,
input rst,
input rx,
output [7:0]data,
output lcd_e,
output lcd_rs
    );
 wire rx_clk;
     
 wire [7:0]rx_register;
 wire [4:0]clk_cnt;
 wire [3:0]rx_cnt;    
 wire [7:0]lcd_register;
 wire [6:0]lcd_cnt;
 
uart_clock #(.max(163)) C0 (.clk_in(clk),.rst(rst),.clk_out(rx_clk));    
    
uart_rx R0 (.clk(rx_clk),.rst(rst),.rx(rx),.rx_register(rx_register),.clk_cnt(clk_cnt),.rx_cnt(rx_cnt));

lcd L0 (.clk(clk),.rx_clk(rx_clk),.rst(rst),.rx_register(rx_register),.rx_cnt(rx_cnt),.clk_cnt(clk_cnt),.lcd_e(lcd_e),.lcd_rs(lcd_rs),.data(data),.lcd_cnt(lcd_cnt),.lcd_register(lcd_register));

ila_0 ila_0 (.clk(clk),.probe0(lcd_cnt),.probe1(rx),.probe2(data),.probe3(lcd_register),.probe4(rx_cnt));
    
endmodule

 

 

- UART_RX

module uart_rx(
input clk,
input rx,
input rst,
output reg [7:0]rx_register,
output reg rx_end,
output reg [4:0]clk_cnt,
output reg [3:0]rx_cnt
    );  

localparam RX_IDLE = 2'b00;
localparam RX_START = 2'b01;
localparam RX_STOP = 2'b10;

reg [1:0]current_state ;
reg [1:0]next_state;
    
reg [15:0]rx_check;
    
always @(posedge clk or posedge rst) begin
    if (rst)
        clk_cnt <= 0;   
    else if (clk_cnt == 16 && rx_cnt != 11)
        clk_cnt <= 1;
    else if (clk_cnt == 16 && rx_cnt == 11)
        clk_cnt <= 0;    
    else 
        case (next_state)
        RX_IDLE : clk_cnt <= 0;
        RX_START : clk_cnt <= clk_cnt+1;
        RX_STOP : clk_cnt <= clk_cnt+1;
        default : clk_cnt <= clk_cnt;
        endcase                
    end

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_cnt <= 0;
    else if (rx_cnt == 11 && clk_cnt == 16)
        rx_cnt <= 0;
    else if (current_state == RX_IDLE && rx == 0)
        rx_cnt <= 1;
    else if (clk_cnt == 16)
        rx_cnt <= rx_cnt+1;
    else
        rx_cnt <= rx_cnt;
    end                            
    
always @(posedge clk or posedge rst) begin
    if (rst)
        current_state <= RX_IDLE;
    else
        current_state <= next_state;
    end   

always @(negedge clk or posedge rst) begin
    if (rst)
        next_state <= RX_IDLE;
    else
        case(current_state)
        RX_IDLE : if (rx == 0)
                    next_state <= RX_START;
               else       
                    next_state <= next_state;                    
        RX_START : if (rx_cnt == 11)
                    next_state <= RX_STOP;
                else 
                    next_state <= next_state;                              
        RX_STOP : if (rx_cnt == 0)
                    next_state <= RX_IDLE;      
               else
                    next_state <= next_state;
        default : next_state <= next_state;                           
        endcase
    end    
    
 reg [4:0]rx_score;
    
always @(posedge clk or posedge rst) begin
    if (rst)
        rx_check <= 16'b1111_1111_1111_1111;          
    else 
        rx_check <= {rx_check[15:0], rx};                   
    end

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_score <= 0;
    else if (rx_cnt == 0)
        rx_score <= 0;    
    else if (clk_cnt ==16)
        rx_score <= 0;
    else if (rx_cnt == 11)
        rx_score <= 0;        
    else if (rx == 1)
        rx_score <= rx_score+1;       
    else 
        rx_score <= rx_score;
    end             

reg rx_sampling;

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_sampling <= 1;
    else if (rx_cnt == 0)
        rx_sampling <= 1;    
    else if (clk_cnt == 16 && rx_score > 8 && rx_cnt != 11)        
        rx_sampling <= 1;
    else if (clk_cnt == 16 && rx_score < 8 && rx_cnt != 11)        
        rx_sampling <= 0;    
    else 
        rx_sampling <= rx_sampling;
    end

reg [9:0]rx_received;    
    
always @(posedge clk or posedge rst) begin
    if (rst)
        rx_received <= 10'b00_0000_0000;
    else if (clk_cnt == 1 && rx_cnt >1) 
        rx_received <= {rx_received[8:0], rx_sampling};
    else 
        rx_received <= rx_received;
    end    

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_register <= 8'b0000_0000;
    else if (rx_cnt == 11 && clk_cnt == 2)
        rx_register <= rx_received[8:1];
    else 
        rx_register <= rx_register;
    end                                              

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_end <= 1;
    else 
        case (next_state)
        RX_IDLE : rx_end <= 1;
        RX_START : rx_end <= 0;
        RX_STOP : rx_end <= 0;
        default : rx_end <= rx_end;
        endcase
    end    
       
endmodule

 

이전 포스팅에서 언급하였듯이,

코드를 최적화하지 않고 직관적으로 코딩한 후 그대로 두었습니다.

 

- UART_Clock

`timescale 1ns / 1ps

module uart_clock(
input clk_in,
input rst,
output reg clk_out
    );
    
reg [10:0]clk_cnt;

parameter max = 1;
    
always @(posedge clk_in or posedge rst) begin
    if (rst) begin
        clk_cnt <= 0;
        end
    else if (clk_cnt == max) begin        
        clk_cnt <= 0;
        end
    else 
        clk_cnt <= clk_cnt + 1;
    end

always @(posedge clk_in or posedge rst) begin
    if (rst) begin
        clk_out <= 0;
        end
    else if (clk_cnt == max) begin        
        clk_out <= !clk_out;
        end
    else 
        clk_out <= clk_out;
    end    
            
endmodule

 

 

- LCD

`timescale 1ns / 1ps

module lcd(
input clk,
input rx_clk,
input rst,
input [7:0]rx_register,
input [3:0]rx_cnt,
input [4:0]clk_cnt,
output reg lcd_e,
output reg lcd_rs,
output reg [7:0]data,
output reg [6:0]lcd_cnt,
output reg [7:0]lcd_register
    );
    
 reg [7:0] dspdata [0:37];
 integer i = 0;
 integer j = 0;
 
    always @(posedge rx_clk or posedge rst) begin
        if (rst)
            lcd_register <= 8'h80;
        else if (rx_cnt == 11 && clk_cnt == 16) begin
            lcd_register[0] <= rx_register[7];
            lcd_register[1] <= rx_register[6];
            lcd_register[2] <= rx_register[5];
            lcd_register[3] <= rx_register[4];
            lcd_register[4] <= rx_register[3];
            lcd_register[5] <= rx_register[2];
            lcd_register[6] <= rx_register[1];
            lcd_register[7] <= rx_register[0];
            end
        else
            lcd_register <= lcd_register;
        end            
    
    always @(posedge rx_clk or posedge rst) begin
        if (rst)
            lcd_cnt <= 4;
        else if (lcd_cnt == 20 && rx_cnt == 11 && clk_cnt == 16)
            lcd_cnt <= 22;
        else if (lcd_cnt == 37 && rx_cnt == 11 && clk_cnt == 16)
            lcd_cnt <= 5;       
        else if (rx_cnt== 11 && clk_cnt == 16)
            lcd_cnt <= lcd_cnt+1;             
        else
            lcd_cnt <= lcd_cnt;
        end                    
    
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            dspdata[0] <= 8'b00111000;    // function set 8bit, 2line, 5x7 dot
            dspdata[1] <= 8'b00001100;    // display on/off , display on, cursor off, cursor blink off 
            dspdata[2] <= 8'b00000110;    // entry mode set increment cursor position, no display shift
            dspdata[3] <= 8'b00000001;    // clear display
            dspdata[4] <= 8'h80;    // set cg ram address 1000 0000   1번라인 첫번째부터
            dspdata[5] <= 8'b00110000;    // 0
            dspdata[6] <= 8'b00110000;    // 0
            dspdata[7] <= 8'b00110000;    // 0
            dspdata[8] <= 8'b00110000;    // 0
            dspdata[9] <= 8'b00110000;    // 0
            dspdata[10] <= 8'b00110000;    // 0
            dspdata[11] <= 8'b00110000;    // 0
            dspdata[12] <= 8'b00110000;    // 0
            dspdata[13] <= 8'b00110000;    // 0
            dspdata[14] <= 8'b00110000;    // 0
            dspdata[15] <= 8'b00110000;    // 0
            dspdata[16] <= 8'b00110000;    // 0
            dspdata[17] <= 8'b00110000;    // 0
            dspdata[18] <= 8'b00110000;    // 0
            dspdata[19] <= 8'b00110000;    // 0
            dspdata[20] <= 8'b00110000;    // 0           
            dspdata[21] <= 8'hC0;   // set cg ram address 1100 0000   2번라인 첫번째부터
            dspdata[22] <= 8'b00110000;    // 0
            dspdata[23] <= 8'b00110000;    // 0
            dspdata[24] <= 8'b00110000;    // 0
            dspdata[25] <= 8'b00110000;    // 0
            dspdata[26] <= 8'b00110000;    // 0
            dspdata[27] <= 8'b00110000;    // 0
            dspdata[28] <= 8'b00110000;    // 0
            dspdata[29] <= 8'b00110000;    // 0
            dspdata[30] <= 8'b00110000;    // 0
            dspdata[31] <= 8'b00110000;    // 0
            dspdata[32] <= 8'b00110000;    // 0
            dspdata[33] <= 8'b00110000;    // 0
            dspdata[34] <= 8'b00110000;    // 0
            dspdata[35] <= 8'b00110000;    // 0
            dspdata[36] <= 8'b00110000;    // 0
            dspdata[37] <= 8'b00110000;    // 0
            end
        else if (rx_cnt == 0)
            dspdata[lcd_cnt] <= lcd_register;    
        else begin
            dspdata[0] <= dspdata[0];
            dspdata[1] <= dspdata[1];
            dspdata[2] <= dspdata[2];
            dspdata[3] <= dspdata[3];            
            dspdata[4] <= dspdata[4];
            dspdata[5] <= dspdata[5];
            dspdata[6] <= dspdata[6];
            dspdata[7] <= dspdata[7];
            dspdata[8] <= dspdata[8];
            dspdata[9] <= dspdata[9];
            dspdata[10] <= dspdata[10];
            dspdata[11] <= dspdata[11];
            dspdata[12] <= dspdata[12];
            dspdata[13] <= dspdata[13];
            dspdata[14] <= dspdata[14];
            dspdata[15] <= dspdata[15];
            dspdata[16] <= dspdata[16];
            dspdata[17] <= dspdata[17];
            dspdata[18] <= dspdata[18];
            dspdata[19] <= dspdata[19];
            dspdata[20] <= dspdata[20];            
            dspdata[21] <= dspdata[21];
            dspdata[22] <= dspdata[22];
            dspdata[23] <= dspdata[23];
            dspdata[24] <= dspdata[24];
            dspdata[25] <= dspdata[25];
            dspdata[26] <= dspdata[26];
            dspdata[27] <= dspdata[27];
            dspdata[28] <= dspdata[28];
            dspdata[29] <= dspdata[29];
            dspdata[30] <= dspdata[30];
            dspdata[31] <= dspdata[31];
            dspdata[32] <= dspdata[32];
            dspdata[33] <= dspdata[33];
            dspdata[34] <= dspdata[34];
            dspdata[35] <= dspdata[35];
            dspdata[36] <= dspdata[36];
            dspdata[37] <= dspdata[37];
            end
    end                                
    
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            i <= 0;            
            lcd_e <= 0;            
            data <= 8'b0;
            end
        else begin
            if(i <= 1000000) begin
                i <= i + 1;
                lcd_e <= 1;
                data <= dspdata[j];
                end 
            else if ((i > 1000000) && (i < 2000000)) begin
                i <= i + 1;
                lcd_e <= 0;
                end 
            else if (i == 2000000) begin              
                i <= 1'b0;
                end 
                                      
            else begin
                i <= i;
                lcd_e <= lcd_e;
                data <= data;
                end
        end
   end     
   
   always @(posedge clk or posedge rst) begin
       if (rst)
        j <= 0;    
    else if (j == 38)   
        j <= 4;
    else if (i == 2000000)
        j <= j+1;    
    else 
        j <= j;    
    end
    
    always @(posedge clk or posedge rst) begin
        if (rst)
            lcd_rs <= 0;            
        else if (j <= 4)
            lcd_rs <= 0;
        else if (j > 4 && j < 21)
            lcd_rs <= 1;
        else if (j == 21)
            lcd_rs <= 0;
        else if (j > 21 && j < 38)
            lcd_rs <= 1;
        else 
            lcd_rs <= lcd_rs;
        end                               
endmodule

 

 

- Nucleo Board 제어용 코드

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
  uint8_t rx_data;
  int led_mode = 0;

/* USER CODE END PV */


int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();
  /* USER CODE BEGIN 2 */

  HAL_UART_Receive_IT(&huart3, (uint8_t *)&rx_data, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  switch (led_mode)
	  {
	  	  case 1 : GPIOB -> ODR = 0x0001; break;
	  	  case 2 : GPIOB -> ODR = 0x0080; break;
	  	  case 3 : GPIOB -> ODR = 0x4000; break;
	  	  case 0 : GPIOB -> ODR = 0x0000; break;
	  }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart -> Instance == USART3)
  {
	  HAL_UART_Receive_IT(&huart3, (uint8_t *)&rx_data, 1);
	  HAL_UART_Transmit(&huart3, (uint8_t *)&rx_data, 1, 500);
	  switch (rx_data)
	  {
	  case '1': led_mode = 1; break;
	  case '2': led_mode = 2; break;
	  case '3': led_mode = 3; break;
	  default: led_mode = 0;
	  }

  }

}
/* USER CODE END 4 */

 

CUBE IDE를 다루는 법은 본 글의 목적에 맞지 않으므로

자세히 설명하지 않고, 필요한 부분만 적어두었습니다.

led 부분의 경우 uart 통신이 잘 이루어지는지 확인하기 위해 넣었습니다.

 

3. 실습결과

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

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

Nucleo 보드는 CUBE IDE에서 제어하였습니다.

 

 

 

 

키보드를 통해 입력한 문자들이

Nucleo 보드를 거쳐 LCD에 잘 출력되는 것을 볼 수 있습니다.

 

이번에는 합성 및 P&R 단계를 생략하고 테스트를 먼저 진행하였습니다.

이후 시간이 된다면 해당 부분도 포스팅하도록 하겠습니다.

 

진행년월: 24.05

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

지난 프로젝트에서 컴퓨터와 신호를 주고받기 위해 UART 통신을 사용하였는데요,

UART 통신도 이해하고 Verilog로 구현해보고자 합니다.

Receiver 부분과 Transmitter 부분이 있는데,

전부 구현은 하지만, 테스트 단계에서

SPI 때처럼 Receiver 부분을 중점적으로 테스트하려고 합니다.

 

 

2. 과제 정의 및 개요

 

1. TX, RX 2개의 핀을 사용합니다.
2. 10비트 데이터를 교환하며 구성은 다음과 같습니다.
    - [0] 비트는 start bit (0)
    - [1~8] 비트는 8비트 data
    - [9] 비트는 end bit (1)
3. 16배로 오버샘플링하며, 중앙 샘플링 방법을 채택하도록 합니다.
    - 간단한 예시이므로 노이즈가 심하지 않을 것임을 알고 있어 채택하였습니다.
    - 7, 8, 9번째 신호 중 2개 이상 1이 나온다면, 1로 인정합니다.

 

 

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

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

 

3. 소스코드

- Top module

`timescale 1ns / 1ps

module uart(
    input i_clk,            // i_clk = 50Mhz
    input i_rx,
    input i_rst,
    output o_tx
);

wire w_tx_clk;
wire w_rx_clk;
wire [7:0] w_rx_register;
wire w_rx_end;
wire [4:0] w_clk_cnt;
wire [3:0] w_rx_cnt;
wire [3:0] w_send_cnt;
wire [1:0] w_current_state;
wire [1:0] w_next_state;

uart_clock #(.max(2604)) uart_clk_gen_tx (
    .clk_in(i_clk),
    .rst(i_rst),
    .clk_out(w_tx_clk)
); // Baud rate = 9600, max = 50,000,000 / 9,600 = 5,208

uart_clock #(.max(163)) uart_clk_gen_rx (
    .i_clk(i_clk),
    .i_rst(i_rst),
    .o_clk(w_rx_clk)
); // Sampling rate = 16*, max = 50,000,000 / 9,600 * 16 = 325

uart_rx uart_receiver (
    .i_clk(w_rx_clk),
    .i_rst(i_rst),
    .i_rx(i_rx),
    .o_rx_register(w_rx_register),
    .o_rx_end(w_rx_end),
    .o_clk_cnt(w_clk_cnt),
    .o_rx_cnt(w_rx_cnt)
);

uart_tx uart_transmitter (
    .i_clk(w_tx_clk),
    .i_rst(i_rst),
    .o_tx(o_tx),
    .i_rx_register(w_rx_register),
    .i_rx_end(w_rx_end),
    .o_send_cnt(w_send_cnt),
    .o_current_state(w_current_state),
    .o_next_state(w_next_state)
);

endmodule

 

 

주석에 적어둔 것 처럼, 보드레이트를 맞추기 위한 작업이 포함되어 있습니다.

 

- Uart_clock

`timescale 1ns / 1ps

module uart_clock(
    input i_clk,
    input i_rst,
    output reg o_clk
);
    
reg [10:0] r_clk_cnt;

parameter max = 1;
    
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_clk_cnt <= 0;
    end else if (r_clk_cnt == max) begin        
        r_clk_cnt <= 0;
    end else 
        r_clk_cnt <= r_clk_cnt + 1;
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_clk_out <= 0;
    end else if (r_clk_cnt == max) begin        
        o_clk <= !o_clk;
    end else 
        o_clk <= o_clk;
end    

endmodule

 

- Uart_TX

`timescale 1ns / 1ps

module uart_tx(
    input i_clk,
    input i_rst,
    input [7:0] i_rx_register,
    input i_rx_end,
    output reg o_tx,
    output reg [3:0] o_send_cnt,
    output reg [1:0] o_current_state,
    output reg [1:0] o_next_state
);
    
localparam TX_IDLE = 2'b00;
localparam TX_START = 2'b01;
localparam TX_DATA = 2'b10;
localparam TX_STOP = 2'b11;

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst)
        o_current_state <= TX_IDLE;
    else
        o_current_state <= o_next_state;
end
 
always @(negedge i_clk or posedge i_rst) begin
    if (i_rst)
        o_next_state <= TX_IDLE;
    else
        case(o_current_state)
        TX_IDLE : if (i_rx_end == 1)
                    o_next_state <= TX_START;
                  else
                    o_next_state <= o_next_state;
        TX_START : o_next_state <= TX_DATA;
        TX_DATA : if (o_send_cnt == 8)
                    o_next_state <= TX_STOP;
               else
                    o_next_state <= o_next_state;
        TX_STOP : o_next_state <= TX_IDLE;
        endcase
end                                       
        
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst)
        o_send_cnt <= 0;
    else if (o_send_cnt == 10)
        o_send_cnt <= 0;
    else 
        case(o_next_state)
            TX_IDLE : o_send_cnt <= 0;
            TX_START : o_send_cnt <= 0;
            TX_DATA : o_send_cnt <= o_send_cnt+1;
            TX_STOP : o_send_cnt <= o_send_cnt+1;
        endcase 
end       

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst)
        o_tx <= 1;
    else
        case (o_next_state)
        TX_IDLE : o_tx <= 1;
        TX_START : o_tx <= 0;
        TX_DATA : o_tx <= i_rx_register[o_send_cnt];
        TX_STOP : o_tx <= 1;
        endcase
end    

endmodule

 

TX 부분은 들어온 RX 신호를 그대로 다시 전송할 수 있도록 간단하게 구현하였습니다.

 

- UART_RX

module uart_rx(
    input i_clk,
    input i_rx,
    input i_rst,
    output reg [7:0] o_rx_register,
    output reg o_rx_end,
    output reg [4:0] o_clk_cnt,
    output reg [3:0] o_rx_cnt
);

localparam RX_IDLE  = 2'b00;
localparam RX_START = 2'b01;
localparam RX_STOP  = 2'b10;

reg [1:0] r_current_state;
reg [1:0] r_next_state;
reg [15:0] r_rx_check;
reg [4:0] r_rx_score;
reg r_rx_sampling;
reg [9:0] r_rx_received;

// Clock counter and bit counter logic
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_clk_cnt <= 0;
    end else if (o_clk_cnt == 16 && o_rx_cnt != 11) begin
        o_clk_cnt <= 1;
    end else if (o_clk_cnt == 16 && o_rx_cnt == 11) begin
        o_clk_cnt <= 0;
    end else begin
        case (r_next_state)
            RX_IDLE : o_clk_cnt <= 0;
            RX_START : o_clk_cnt <= o_clk_cnt + 1;
            RX_STOP : o_clk_cnt <= o_clk_cnt + 1;
            default : o_clk_cnt <= o_clk_cnt;
        endcase
    end
end

// Bit counter logic
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_rx_cnt <= 0;
    end else if (o_rx_cnt == 11 && o_clk_cnt == 16) begin
        o_rx_cnt <= 0;
    end else if (r_current_state == RX_IDLE && i_rx == 0) begin
        o_rx_cnt <= 1;
    end else if (o_clk_cnt == 16) begin
        o_rx_cnt <= o_rx_cnt + 1;
    end else begin
        o_rx_cnt <= o_rx_cnt;
    end
end

// FSM for RX control
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_current_state <= RX_IDLE;
    end else begin
        r_current_state <= r_next_state;
    end
end

always @(negedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_next_state <= RX_IDLE;
    end else begin
        case (r_current_state)
            RX_IDLE : if (i_rx == 0)
                         r_next_state <= RX_START;
                     else
                         r_next_state <= r_next_state;
            RX_START : if (o_rx_cnt == 11)
                         r_next_state <= RX_STOP;
                     else
                         r_next_state <= r_next_state;
            RX_STOP : if (o_rx_cnt == 0)
                         r_next_state <= RX_IDLE;
                     else
                         r_next_state <= r_next_state;
            default : r_next_state <= r_next_state;
        endcase
    end
end

// Central sampling logic as per the original code
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_rx_check <= 16'b1111_1111_1111_1111;
    end else begin
        r_rx_check <= {r_rx_check[14:0], i_rx};
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_rx_score <= 0;
    end else if (o_rx_cnt == 0) begin
        r_rx_score <= 0;
    end else if (o_clk_cnt == 16) begin
        r_rx_score <= 0;
    end else if (o_rx_cnt == 11) begin
        r_rx_score <= 0;
    end else if (i_rx == 1) begin
        r_rx_score <= r_rx_score + 1;
    end else begin
        r_rx_score <= r_rx_score;
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_rx_sampling <= 1;
    end else if (o_rx_cnt == 0) begin
        r_rx_sampling <= 1;
    end else if (o_clk_cnt == 16 && r_rx_score > 8 && o_rx_cnt != 11) begin
        r_rx_sampling <= 1;
    end else if (o_clk_cnt == 16 && r_rx_score < 8 && o_rx_cnt != 11) begin
        r_rx_sampling <= 0;
    end else begin
        r_rx_sampling <= r_rx_sampling;
    end
end

// Register received data
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_rx_received <= 10'b0;
    end else if (o_clk_cnt == 1 && o_rx_cnt > 1) begin
        r_rx_received <= {r_rx_received[8:0], r_rx_sampling};
    end
end

// Update RX register
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_rx_register <= 8'b0;
    end else if (o_rx_cnt == 11 && o_clk_cnt == 2) begin
        o_rx_register <= r_rx_received[8:1];
    end
    	else
        rx_register <= rx_register;
end

// RX end signal
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_rx_end <= 1;
    end else begin
        case (r_next_state)
            RX_IDLE : o_rx_end <= 1;
            RX_START : o_rx_end <= 0;
            RX_STOP : o_rx_end <= 0;
            default : o_rx_end <= o_rx_end;
        endcase
    end
end

endmodule

 

여기서 o_rx_end 신호는 의미상으로만 존재할뿐, 사용하지 않습니다.

Data Update Timing 등은 설계하기 나름이며, 최대한 간단한 형태로 설계하였습니다.

 

- tb_rx

`timescale 1ns / 1ps

module tb_rx;

    reg clk;
    reg rx;
    reg rst;
    wire [7:0] rx_register;
    wire rx_end;
    
    reg [7:0] tx_data; // 보낼 데이터
    integer i;

    // UART RX 모듈 인스턴스화
    uart_rx uut (
        .i_clk(clk),
        .i_rx(rx),
        .i_rst(rst),
        .o_rx_register(rx_register),
        .o_rx_end(rx_end)
    );

    // Clock generation (100 MHz)
    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10ns 주기 클럭, 100 MHz
    end

    // 테스트 시나리오
    initial begin
        // 초기화
        rst = 1;  // 리셋
        rx = 1;   // IDLE 상태 (RX 라인은 기본적으로 High 상태)
        tx_data = 8'b0101_0111; // 전송할 데이터: 8'b0101_0111
        #100 rst = 0;  // 리셋 해제

        // 데이터 전송 (UART 프레임 생성)
        // 시작 비트 (Start Bit)
        rx = 0;
        #160; // 16 클럭 사이클 (샘플링 속도)

        // 데이터 비트 (Data Bits)
        for (i = 0; i < 8; i = i + 1) begin
            rx = tx_data[i];
            #160; // 각 데이터 비트는 16 클럭 사이클 동안 유지
        end

        // 정지 비트 (Stop Bit)
        rx = 1;
        #160; // Stop Bit

        // 대기 후 결과 확인
        #200;
        
        // 결과 출력
        if (rx_register == tx_data) begin
            $display("Test Passed! Received Data: %b", rx_register);
        end else begin
            $display("Test Failed! Received Data: %b, Expected Data: %b", rx_register, tx_data);
        end

        // 시뮬레이션 종료
        #100;
        $finish;
    end

endmodule

 

앞서 언급하였듯이 RX 위주로 확인할 것이기 때문에,

RX의 테스트벤치만을 간단하게 작성하였습니다.

 

 

4. 시뮬레이션 결과

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

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

 

사실, 앞서 언급한 바와 같이 rx_end 신호는 의미상으로만 존재합니다.

제 구현에서는 rx_end 신호가 필요하지 않습니다.

 

만약, 필요하다면 처음 rx_end 신호가 Low로 내려가는 순간은

다른 신호와 마찬가지로 샘플링된 결과에 의해 작동해야 합니다.

처음에 그렸던 Timing Diagram과 비슷한 waveform을 만들고자 의도한 바입니다.

 

rx_register에 저장되는 Timing 등은 설계하기 나름이지만,

여유를 두기 위해 통신이 끝날 때로 두었습니다.

 

 

 

- 다음 글

(2) Arm보드로 FPGA LCD제어: https://chonh0531.tistory.com/6

 

UART 통신 - (2) Arm보드로 FPGA LCD 제어

- 이전 글(1) RTL : https://chonh0531.tistory.com/5목차1. 과제 개요2. 소스코드3. 실습 결과  1. 과제 개요이전에 구현해 본 UART_RX를 테스트해 보기로 합니다.UART 신호 생성은 ARM Cortex-M4 Core가 있는 NUCLEO-F42

chonh0531.tistory.com

 

 

- 이전 글

(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