· 

ChatGPTで「SPIハードをFPGAで」を再構築(1)

ChatGPTは流行っているので、それに乗っかってみる。GoogleIDかMicrosoftIDを持っていればとりあえず使うことはできるらしい。とはいえ、会社内とかからAIとやり取りするのはたぶん今後も許されるわけない。便利なんだろうけど、発展の方向がわからん技術。だけど流行っている。で、やってみる。以前、ビット数可変のSPIハードをFPGAで自作したけど、ロジック回路はどうにも苦手なので、変な実装をしたかもしれない。ここはAIの知識と経験に頼るとどうなるかを検証してみる。

以下AIとのやり取りを掲載(日本語でもわかってくれるみたいだけど、つたない英語でAIが困るかどうかも同時に検証しています(•౪•))。

SundayEngineer : please create an example of spi ip using vhdl.

CharGPT : Sure, here's an example of a simple Serial Peripheral Interface (SPI) IP implemented in VHDL. This SPI IP provides a basic interface for transferring data between a master and a slave device over a serial communication link.

library ieee;
use ieee.std_logic_1164.all;

entity spi_ip is
    port (
        -- Master interface
        sclk_m : in std_logic;
        mosi_m : in std_logic;
        miso_m : out std_logic;
        ss_m   : in std_logic;

        -- Slave interface
        sclk_s : in std_logic;
        mosi_s : in std_logic;
        miso_s : out std_logic;
        ss_s   : in std_logic
    );
end spi_ip;

architecture rtl of spi_ip is
    -- Define internal signals
    signal mosi : std_logic;
    signal miso : std_logic;
    signal shift_reg : std_logic_vector(7 downto 0);
    signal shift_count : integer range 0 to 7 := 0;
begin
    -- Connect master MOSI and slave MISO signals
    mosi <= mosi_m when ss_m = '0' else 'Z';
    miso_s <= miso when ss_s = '0' else 'Z';

    process (sclk_m, sclk_s)
    begin
        if rising_edge(sclk_m) then
            -- Master mode
            if ss_m = '0' then
                shift_reg <= shift_reg(6 downto 0) & mosi_m;
                miso_m <= shift_reg(7);
            end if;

            -- Slave mode
            if ss_s = '0' then
                shift_reg <= shift_reg(6 downto 0) & mosi_s;
                miso <= shift_reg(7);
            end if;

            shift_count <= shift_count + 1;
            if shift_count = 7 then
                shift_count <= 0;
            end if;
        elsif rising_edge(sclk_s) then
            -- Slave mode
            if ss_s = '0' then
                shift_reg <= shift_reg(6 downto 0) & mosi_s;
                miso <= shift_reg(7);
            end if;

            shift_count <= shift_count + 1;
            if shift_count = 7 then
                shift_count <= 0;
            end if;
        end if;
    end process;
end rtl;

In this implementation, the spi_ip entity has two interfaces: a master interface and a slave interface. The master interface includes signals for the serial clock (sclk_m), master output slave input (mosi_m), master input slave output (miso_m), and slave select (ss_m). The slave interface includes similar signals for the slave device.

 

Inside the rtl architecture, the mosi and miso signals are defined as internal signals. The shift_reg signal is an 8-bit shift register used to store the incoming data. The shift_count signal keeps track of the number of bits shifted in or out.

 

The process block is used to handle the SPI data transfer. On the rising edge of the serial clock signal for the master, the incoming data is shifted into the shift register if the slave select signal is active. The outgoing data is shifted out of the shift register on the miso_m signal. The same thing happens for the slave interface when the rising edge of the serial clock signal for the slave is detected.

 

Note that this implementation only supports 8-bit data transfer and does not include any error detection or correction mechanisms. It is intended as a basic example to demonstrate the implementation of

日曜技術者はビット数可変という仕様を提示するのを忘れていた(仕事であっちゃいかんやつ)ので仕様を追加する。

SundayEngineer : please add spi sending bit customization.

ChatGPT : Sure! Here's an updated version of the VHDL code that allows the master to customize the number of bits to send during SPI data transfer.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi_ip is
    generic (
        DATA_WIDTH : integer := 8
    );
    port (
        -- Master interface
        sclk_m    : in  std_logic;
        mosi_m    : in  std_logic_vector(DATA_WIDTH - 1 downto 0);
        miso_m    : out std_logic_vector(DATA_WIDTH - 1 downto 0);
        ss_m      : in  std_logic;

        -- Slave interface
        sclk_s    : in  std_logic;
        mosi_s    : in  std_logic_vector(DATA_WIDTH - 1 downto 0);
        miso_s    : out std_logic_vector(DATA_WIDTH - 1 downto 0);
        ss_s      : in  std_logic
    );
end spi_ip;

architecture rtl of spi_ip is
    -- Define internal signals
    signal mosi      : std_logic_vector(DATA_WIDTH - 1 downto 0);
    signal miso      : std_logic_vector(DATA_WIDTH - 1 downto 0);
    signal shift_reg : std_logic_vector(DATA_WIDTH - 1 downto 0);
    signal shift_count : integer range 0 to DATA_WIDTH - 1 := 0;
begin
    -- Connect master MOSI and slave MISO signals
    mosi <= mosi_m when ss_m = '0' else (others => 'Z');
    miso_s <= miso when ss_s = '0' else (others => 'Z');

    process (sclk_m, sclk_s)
    begin
        if rising_edge(sclk_m) then
            -- Master mode
            if ss_m = '0' then
                shift_reg <= shift_reg(DATA_WIDTH - 2 downto 0) & mosi_m(DATA_WIDTH - 1);
                miso_m <= shift_reg(DATA_WIDTH - 1);

                shift_count <= shift_count + 1;
                if shift_count = DATA_WIDTH - 1 then
                    shift_count <= 0;
                end if;
            end if;

            -- Slave mode
            if ss_s = '0' then
                shift_reg <= shift_reg(DATA_WIDTH - 2 downto 0) & mosi_s(DATA_WIDTH - 1);
                miso <= shift_reg(DATA_WIDTH - 1);

                shift_count <= shift_count + 1;
                if shift_count = DATA_WIDTH - 1 then
                    shift_count <= 0;
                end if;
            end if;
        elsif rising_edge(sclk_s) then
            -- Slave mode
            if ss_s = '0' then
                shift_reg <= shift_reg(DATA_WIDTH - 2 downto 0) & mosi_s(DATA_WIDTH - 1);
                miso <= shift_reg(DATA_WIDTH - 1);

                shift_count <= shift_count + 1;
                if shift_count = DATA_WIDTH - 1 then
                    shift_count <= 0;
                end if;
            end if;
        end if;
    end process;
end rtl;

In this updated implementation, a generic parameter DATA_WIDTH is added to specify the number of bits to send during SPI data transfer. The mosi and miso signals are defined as vectors of size DATA_WIDTH. The shift_reg signal is also updated to match the size of mosi and miso.

 

In the process block, the shift register is updated to shift in the incoming data of size `DATA

日曜技術者はビット数可変という仕様に対して、固定値ではなくランタイムで可変とするという追加情報を提示し忘れていたので、土下座して追加を依頼する。

SundayEngineer : please change "DATA_WIDTH" from a constant to runtime changable value.

ChatGPT : Sure, here's an updated version of the VHDL code that allows the DATA_WIDTH to be a runtime configurable value.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi_ip is
    generic (
        MAX_DATA_WIDTH : integer := 32
    );
    port (
        -- Master interface
        sclk_m    : in  std_logic;
        mosi_m    : in  std_logic_vector(MAX_DATA_WIDTH - 1 downto 0);
        miso_m    : out std_logic_vector(MAX_DATA_WIDTH - 1 downto 0);
        ss_m      : in  std_logic;

        -- Slave interface
        sclk_s    : in  std_logic;
        mosi_s    : in  std_logic_vector(MAX_DATA_WIDTH - 1 downto 0);
        miso_s    : out std_logic_vector(MAX_DATA_WIDTH - 1 downto 0);
        ss_s      : in  std_logic;

        -- Configuration interface
        data_width_cfg : in  integer range 1 to MAX_DATA_WIDTH
    );
end spi_ip;

architecture rtl of spi_ip is
    -- Define internal signals
    signal mosi      : std_logic_vector(MAX_DATA_WIDTH - 1 downto 0);
    signal miso      : std_logic_vector(MAX_DATA_WIDTH - 1 downto 0);
    signal shift_reg : std_logic_vector(MAX_DATA_WIDTH - 1 downto 0);
    signal shift_count : integer range 0 to MAX_DATA_WIDTH - 1 := 0;
    signal data_width : integer range 1 to MAX_DATA_WIDTH := 8;
begin
    -- Connect master MOSI and slave MISO signals
    mosi <= mosi_m when ss_m = '0' else (others => 'Z');
    miso_s <= miso when ss_s = '0' else (others => 'Z');

    -- Update data width based on configuration input
    process (data_width_cfg)
    begin
        if data_width_cfg >= 1 and data_width_cfg <= MAX_DATA_WIDTH then
            data_width <= data_width_cfg;
        end if;
    end process;

    process (sclk_m, sclk_s)
    begin
        if rising_edge(sclk_m) then
            -- Master mode
            if ss_m = '0' then
                shift_reg <= shift_reg(data_width - 2 downto 0) & mosi_m(data_width - 1);
                miso_m <= shift_reg(data_width - 1);

                shift_count <= shift_count + 1;
                if shift_count = data_width - 1 then
                    shift_count <= 0;
                end if;
            end if;

            -- Slave mode
            if ss_s = '0' then
                shift_reg <= shift_reg(data_width - 2 downto 0) & mosi_s(data_width - 1);
                miso <= shift_reg(data_width - 1);

                shift_count <= shift_count + 1;
                if shift_count = data_width - 1 then
                    shift_count <= 0;
                end if;
            end if;
        elsif rising_edge(sclk_s) then
            -- Slave mode
            if ss_s = '0' then
                shift_reg <= shift_reg(data_width - 2 downto 0) & mosi_s(data_width - 1);
                miso <= shift_reg(data_width - 1);

                shift_count <= shift_count + 1;
                if shift_count = data_width - 1 then
                    shift_count <= 0;
                end if;
            end if;
        end if;
    end

普通なら仕様の後出しはぼろくそ言われるけど、文句を言わずに素直にやってくれるのがいい(リアルワールドでは文句を言ってもらったほうが心理的にはやりやすいんですが)。

では、まずはシミュレーションしよう。テストベンチを作成して、、、あ、テストベンチも作ってくれませんかね?

SundayEngineer : please generate a test bench vhdl code to test the spi implementation above.

CharGPT : Sure, here's a test bench VHDL code to test the SPI IP implementation:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi_ip_tb is
end spi_ip_tb;

architecture tb of spi_ip_tb is
    constant CLK_PERIOD : time := 10 ns;
    signal sclk_m, sclk_s, mosi_m, miso_m, mosi_s, miso_s, ss_m, ss_s : std_logic;
    signal data_width_cfg : integer range 1 to 32 := 8;
begin
    dut : entity work.spi_ip
        generic map (
            MAX_DATA_WIDTH => 32
        )
        port map (
            sclk_m => sclk_m,
            mosi_m => mosi_m,
            miso_m => miso_m,
            ss_m => ss_m,
            sclk_s => sclk_s,
            mosi_s => mosi_s,
            miso_s => miso_s,
            ss_s => ss_s,
            data_width_cfg => data_width_cfg
        );

    process
    begin
        -- Initialize
        sclk_m <= '0';
        mosi_m <= (others => '0');
        miso_m <= (others => '0');
        ss_m <= '1';

        sclk_s <= '0';
        mosi_s <= (others => '0');
        miso_s <= (others => '0');
        ss_s <= '1';

        -- Test 1: Send 8-bit data from master to slave
        report "Test 1: Send 8-bit data from master to slave" severity note;
        ss_m <= '0';
        mosi_m <= x"5A";
        wait for CLK_PERIOD * 16;
        ss_m <= '1';
        wait for CLK_PERIOD * 2;

        -- Test 2: Send 16-bit data from slave to master
        report "Test 2: Send 16-bit data from slave to master" severity note;
        ss_s <= '0';
        mosi_s <= x"1234";
        wait for CLK_PERIOD * 16;
        ss_s <= '1';
        wait for CLK_PERIOD * 2;

        -- Test 3: Send 32-bit data from master to slave
        report "Test 3: Send 32-bit data from master to slave" severity note;
        ss_m <= '0';
        mosi_m <= x"12345678";
        wait for CLK_PERIOD * 32;
        ss_m <= '1';
        wait for CLK_PERIOD * 2;

        -- Test 4: Send 24-bit data from slave to master with custom data width
        report "Test 4: Send 24-bit data from slave to master with custom data width" severity note;
        data_width_cfg <= 24;
        ss_s <= '0';
        mosi_s <= x"ABCDEF";
        wait for CLK_PERIOD * 24;
        ss_s <= '1';
        wait for CLK_PERIOD * 2;

        -- Test 5: Send 10-bit data from master to slave with custom data width
        report "Test 5: Send 10-bit data from master to slave with custom data width" severity note;
        data_width_cfg <= 10;
        ss_m <= '0';
        mosi_m <= x"3FF";
        wait for CLK_PERIOD * 10;
        ss_m <= '1';
        wait for CLK_PERIOD * 2;

        wait;
    end process;
end tb;

This test bench will test the SPI IP implementation by sending and receiving different data patterns with varying data

リアルワールドなら人的リソースを確保していないって断られるけど淡々とやってくれる。

では、SPIハードをFPGAで(1)と同じようにやってみる。 

フォルダはこんな内容で。

VHDLC=/usr/bin/ghdl
SRC=chatgpt_spi.vhdl
TBSRC=chatgpt_spi_tb.vhdl
VCD=chatgpt_spi.vcd
SIMRTL=spi_ip_tb

all :
        @make comp
        @make tb
        @make tb_e
        @make vcd

comp :
        $(VHDLC) -a --ieee=synopsys $(SRC)

tb :
        $(VHDLC) -a --ieee=synopsys $(TBSRC)

tb_e :
        $(VHDLC) -e --ieee=synopsys $(SIMRTL)

vcd :
        $(VHDLC) -r --ieee=synopsys $(SIMRTL) --vcd=$(VCD)

Makefileはこうする。そして躊躇なく

make all

エラーです。AIが間違うはずないじゃないかーって絶叫したくなる。きっと人間(日曜技術者)が間違ったに違いない。エラー情報をみると、processとendが対になってないとか書いてある。これはコピペミスに違いない、、、って見直しても、ChatGPT通り。仕様追加する前のコードを見ると、ChatGPTさんは

end process;

end rtl;

で終わらせているのに最終のコードだけ

end

で終わっている。、、、何か気の迷いがあったのか。そっと追加してやってみる。

エラーです。AIが間違うはずないじゃないかーって絶叫したくなる。

で、コードをよく見ると、mosi, misoがstd_logic_vectorになっている。明らかにstd_logicじゃないとおかしい。人間なら絶対にこんな間違いはしない。(ここを修正しても、結局あちこちにエラーが出たので、一旦立ち止まる。)AIが間違うはずないので、何か仕様の伝え方を変えないといけないと思う。

 

あちこちにChatGPTでコードを書いてうまくいったって記事があるけど、実はどうもそれほど優秀じゃないと思う。ちなみにpythonを使ってyokogawaオシロで保存した波形データを読み出したいってお願いしたら、メンテナンスされていないからかありもしないライブラリを紹介してきて、ことごとくpipで見つけられないってことを何度も繰り返すってこともあった。間違いを指摘すると謝ってくるんだけど、いい加減正解出せよって逆にイラっとくる。

 

やっぱり人間は偉大だ。

こんなクズのおじさんと一緒に働いているみなさんに感謝!