ShrikeLiteの例題を読み解く

電子工作

 これからShrikeLiteのアプリを開発するにあたり、CPU-FPGA間のインタフェースとしてSPIを使いたいと思っています。何故、spiなのかというと、CPUからFPGAをコンフィグレーションするためにSPIを使っており、配線済みなので新たに配線する必要がないからです。このSPIはFPGAのコンフィグレーションが終われば用済みになるので、使わないと勿体ないのです。SPIはI2Cに比べるとアドレスと言う概念がなく、一度に32ビットなどの大きなデータをやり取りできる点も魅力です。

 ShrikeLiteのGithubにはspi_loopback_ledという例題が掲載されています。例題の仕様やピンアサインについて英文ですがキッチリ書かれています。要約すると、SPIのスレーブの例として、CPUからSPIを通して0xABを書き込むとLEDが点灯し、0xFFを書き込むと消灯し、書き込み後に前回のデータを読み出すという機能です。Verilogの勉強を兼ねて、この例題を読み解いて理解することにしました。2つのVerilogファイルがありますが、トップレベルの記述であるtop.vを見て行きます。

module top ( 
	(* iopad_external_pin, clkbuf_inhibit *) input clk, 	// System Clock (50MHz) 
	(* iopad_external_pin *) output clk_en, 
	(* iopad_external_pin *) input rst_n, 			   	// System Reset (Active Low) 
	
	// Physical SPI Pins (Connect these to FPGA I/O) 
	(* iopad_external_pin *) input spi_ss_n, 
	(* iopad_external_pin *) input spi_sck, 
	(* iopad_external_pin *) input spi_mosi, 
	(* iopad_external_pin *) output spi_miso, 
	(* iopad_external_pin *) output spi_miso_en,
	
	// Physical LED Pins 
	(* iopad_external_pin *) output reg led, 
	(* iopad_external_pin *) output led_en 
);

 topという名前のモジュールを定義しており、5本の入力と5本の出力を持っています。(* *)で囲まれた部分は属性を定義しており、トップレベルの定義らしく、iopad_external_pinという属性を与えています。少し前に試したblink_ledという例題では、( top ) module blink(・・・と定義されていました。この例では、( top *)との記述がありませんが、topという名前だからトップレベルモジュールと認識されるのでしょうか? clkbuf_inhibitという属性が何を意味するのか不明ですが、まぁ良いとしましょう。clk_enやspi_miso_en,led_enという外部信号は不要なのではないかと思っていましたが、ピン割り当ての際に、どのGPIOにもIN, OUT, ENという信号が存在することに気付きました。どうやら、このFPGAの出力信号にはEN(enable)信号が必要なのでしょう。

    assign led_en = 1'b1;
    assign clk_en = 1'b1;

    wire [7:0] rx_data_wire;
    wire       rx_valid_pulse;
    reg  [7:0] tx_data_reg;

 led_enとclk_enという信号に1を割り当てているので、常にイネーブルという意味になります。rx_data_wireというSPIモジュールで受信した8ビットの信号線を定義しています。rx_valid_pulseという受信データ有効を示す信号線です。tx_data_regはSPIからCPUに送信するデータを保持するレジスタです。

    // The Echo Logic    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            tx_data_reg <= 8'h00;
        end else if (rx_valid_pulse) begin
            tx_data_reg <= rx_data_wire;
    	end
    end

 rst_nが0ならtx_data_regに0x00をセットし、rst_nが1でかつrx_valid_pulseが1ならrx_data_wireの信号をセットします。

    // LED Logic
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            led <= 1'b0;
    	end else if (rx_valid_pulse) begin
            if (rx_data_wire == 8'hAB)
                led <= 1'b1;
            else if (rx_data_wire == 8'hFF)
                led <= 1'b0;
        end
    end

 rst_nが0ならledに0をセットし、rst_nが1でかつrx_valid_pulseが1の時、rx_data_wireが0xABならledに1をセットし、rx_data_wireが0xFFならledに0をセットします。

    // SPI Target
    spi_target #(
        .CPOL(1'b0),   // Standard Mode 0 (Idle Low)
        .CPHA(1'b0),   // Standard Mode 0 (Sample Rising)
        .WIDTH(8),
        .LSB(1'b0)     // MSB First (Standard)
    ) u_spi_target (
        // System Common
        .i_clk(clk),
        .i_rst_n(rst_n),
        .i_enable(1'b1),        // Enable the module permanently

        // SPI Physical Interface
        .i_ss_n(spi_ss_n),
        .i_sck(spi_sck),
        .i_mosi(spi_mosi),
        .o_miso(spi_miso),
        .o_miso_oe(spi_miso_en),

        // RX Interface (Data FROM MCU)
        .o_rx_data(rx_data_wire),
        .o_rx_data_valid(rx_valid_pulse),

        // TX Interface (Data TO MCU)
        .i_tx_data(tx_data_reg), 
        .o_tx_data_hold()        // Not needed for simple echo
    );

endmodule

 spi_targetモジュールのインスタンスを宣言して、clk, rst_nなどの信号をインスタンスに割り当てています。spi_target #()はパラメータを示しています。このモジュールをインスタンス化するにあたり、CPOL, CPHA, WIDTH, LSBというパラメータを具体的に指定しています。

コメント