たいていのマイコンのSPI機能で送れるビット数は8bit固定です。可変のものでも5~9bitです。このためSWDには使えない。よって、FPGAで作っちゃう。構成を考えると、マイコンまで組み込んだほうがはんだ付けが楽なので、MAX10FPGAでNIOSIIを組み込むところまでやってみる予定。が、いきなりQuartusPrimeでやるのはいろいろイラっとするので、GHDLとGtkWaveでやる。GHDLのインストールやらGtkWaveのインストールやらは別のサイトに説明があるに違いない。当方、WSLでやってるので、Synapticでインストールしちゃいました。(Win11はLinuxのGUIソフトも何も気を使うことなく動くのでいいぞー。)
ざっくりこんな感じで行きます。しょーじき、得意じゃないので、後でいろいろ不都合が出て変えるかもしれません。
ClockGenerator
INITの立下りエッジで、設定情報を取り込んで、RUNの立下りエッジで動作を開始する。動作中はBUSYをHで出力。NCLKDIVはCLKINを分周して出す設定。NSENDはクロックを吐き出す回数(これで、SPIのビット数を設定する)。
TrgGen
INITからINITとRUNを生成してClockGeneratorに入れる。ブロックで書いてあるけど部品化はしない。
Writer
INITでWDATAを取り込んで、CLKの立下りエッジで書き出しと内部データの左シフトをする。
Reader
INITでRDATAを初期化して、CLKの立ち上がりで内部データの左シフトとINPの取り込みをする。
で、さっそく、
(Google Code Prettifyがイマイチすぎるけどしょうがない)
clkgen.vhdl
LIBRARY ieee; USE ieee.std_logic_1164.ALL; USE ieee.std_logic_unsigned.ALL; ENTITY ClockGenerator IS PORT ( NSEND : IN STD_LOGIC_VECTOR(2 DOWNTO 0); NCLKDIV : IN STD_LOGIC_VECTOR(2 DOWNTO 0); CLKIN : IN STD_LOGIC; CLKOUT : OUT STD_LOGIC; CLKSYS : IN STD_LOGIC; BUSY : OUT STD_LOGIC; RUN : IN STD_LOGIC; INIT : IN STD_LOGIC ); END ClockGenerator; ARCHITECTURE ppl_type OF ClockGenerator IS TYPE t_state IS (STATE_IDLE, STATE_INIT, STATE_RUN, STATE_DONE); SIGNAL state : t_state := STATE_IDLE; SIGNAL next_state : t_state := STATE_IDLE; SIGNAL state_num : INTEGER RANGE 0 TO 7 := 0; SIGNAL reqinit : STD_LOGIC := '0'; SIGNAL reqrun : STD_LOGIC := '0'; SIGNAL reqdone : STD_LOGIC := '0'; SIGNAL reqidle : STD_LOGIC := '0'; SIGNAL in_nsend : INTEGER RANGE 0 TO 8; SIGNAL in_nclkdiv : INTEGER RANGE 0 TO 8; SIGNAL clkcntr : INTEGER RANGE 0 TO 8; SIGNAL clkdivcntr : INTEGER RANGE 0 TO 8; SIGNAL out_clk : STD_LOGIC := '1'; SIGNAL internal_oclk : STD_LOGIC := '1'; SIGNAL internal_clkdivcntr : INTEGER RANGE 0 TO 8; SIGNAL CLKIN_p : STD_LOGIC := '0'; SIGNAL out_clk_p : STD_LOGIC := '0'; BEGIN PROCESS (CLKSYS, INIT, state) BEGIN IF INIT'event AND INIT = '0' THEN IF state = STATE_IDLE OR state = STATE_RUN OR state = STATE_DONE THEN reqinit <= '1'; END IF; END IF; IF state = STATE_INIT THEN reqinit <= '0'; END IF; END PROCESS; PROCESS (CLKSYS, RUN, state) BEGIN IF RUN'event AND RUN = '0' THEN IF state = STATE_INIT THEN reqrun <= '1'; END IF; END IF; IF state = STATE_RUN THEN reqrun <= '0'; END IF; END PROCESS; PROCESS (CLKSYS, clkcntr, state) BEGIN IF clkcntr = in_nsend THEN IF state = STATE_RUN THEN reqdone <= '1'; END IF; END IF; IF state = STATE_DONE THEN reqdone <= '0'; END IF; END PROCESS; PROCESS (internal_oclk, state) BEGIN IF internal_oclk'event AND internal_oclk = '0' THEN IF state = STATE_DONE THEN reqidle <= '1'; END IF; END IF; IF state = STATE_IDLE THEN reqidle <= '0'; END IF; END PROCESS; PROCESS (state, reqinit, reqrun, reqdone, reqidle) BEGIN CASE state IS WHEN STATE_IDLE => IF reqinit = '1' THEN next_state <= STATE_INIT; ELSE next_state <= STATE_IDLE; END IF; WHEN STATE_INIT => IF reqrun = '1' THEN next_state <= STATE_RUN; ELSE next_state <= STATE_INIT; END IF; WHEN STATE_RUN => IF reqdone = '1' THEN next_state <= STATE_DONE; ELSE next_state <= STATE_RUN; END IF; WHEN STATE_DONE => IF reqidle = '1' THEN next_state <= STATE_IDLE; ELSE next_state <= STATE_DONE; END IF; END CASE; END PROCESS; PROCESS (CLKSYS) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN state <= next_state; END IF; END PROCESS; BUSY <= '1' WHEN state = STATE_INIT ELSE '1' WHEN state = STATE_RUN ELSE '1' WHEN state = STATE_DONE ELSE '0'; state_num <= 1 WHEN state = STATE_INIT ELSE 2 WHEN state = STATE_RUN ELSE 3 WHEN state = STATE_DONE ELSE 0; PROCESS (CLKSYS, state) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN IF state = STATE_INIT THEN in_nsend <= CONV_INTEGER(NSEND) + 1; in_nclkdiv <= CONV_INTEGER(NCLKDIV); END IF; END IF; END PROCESS; PROCESS (CLKSYS, CLKIN, state) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN IF state = STATE_INIT THEN clkdivcntr <= 0; out_clk <= '1'; END IF; IF state = STATE_IDLE THEN out_clk <= '1'; END IF; IF CLKIN_p = '0' AND CLKIN = '1' THEN IF state = STATE_RUN THEN IF clkdivcntr < in_nclkdiv THEN clkdivcntr <= clkdivcntr + 1; END IF; IF clkdivcntr = in_nclkdiv THEN clkdivcntr <= 0; out_clk <= out_clk XOR '1'; END IF; END IF; END IF; END IF; END PROCESS; PROCESS (CLKSYS, CLKIN, state) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN IF state = STATE_INIT THEN internal_clkdivcntr <= 0; internal_oclk <= '1'; END IF; IF state = STATE_IDLE THEN internal_oclk <= '1'; END IF; IF CLKIN_p = '0' AND CLKIN = '1' THEN IF state = STATE_RUN OR state = STATE_DONE THEN IF internal_clkdivcntr < in_nclkdiv THEN internal_clkdivcntr <= internal_clkdivcntr + 1; END IF; IF internal_clkdivcntr = in_nclkdiv THEN internal_clkdivcntr <= 0; internal_oclk <= internal_oclk XOR '1'; END IF; END IF; END IF; END IF; END PROCESS; PROCESS (CLKSYS, CLKIN, state) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN CLKIN_p <= CLKIN; END IF; END PROCESS; PROCESS (CLKSYS, out_clk, state) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN out_clk_p <= out_clk; IF state = STATE_INIT THEN clkcntr <= 0; END IF; IF out_clk_p = '0' AND out_clk = '1' THEN IF state = STATE_RUN THEN IF clkcntr < in_nsend THEN clkcntr <= clkcntr + 1; END IF; END IF; END IF; END IF; END PROCESS; CLKOUT <= out_clk; END;
writer.vhdl
LIBRARY ieee; USE ieee.std_logic_1164.ALL; USE ieee.std_logic_unsigned.ALL; ENTITY Writer IS PORT ( CLKSYS : IN STD_LOGIC; CLK : IN STD_LOGIC; INIT : IN STD_LOGIC; WDATA : IN STD_LOGIC_VECTOR(7 DOWNTO 0); OUTP : OUT STD_LOGIC ); END Writer; ARCHITECTURE ppl_type OF Writer IS SIGNAL in_wdata : STD_LOGIC_VECTOR(7 DOWNTO 0) := "00000000"; SIGNAL out_outp : STD_LOGIC := '1'; SIGNAL CLK_p : STD_LOGIC := '0'; SIGNAL INIT_p : STD_LOGIC := '0'; BEGIN PROCESS (CLKSYS, INIT, CLK) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN CLK_p <= CLK; INIT_p <= INIT; END IF; END PROCESS; PROCESS (CLKSYS, INIT, CLK) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN IF INIT_p = '1' AND INIT = '0' THEN in_wdata <= WDATA; END IF; IF CLK_p = '1' AND CLK = '0' THEN out_outp <= in_wdata(7); in_wdata <= in_wdata(6 DOWNTO 0) & '0'; END IF; END IF; END PROCESS; OUTP <= out_outp; END;
reader.vhdl
LIBRARY ieee; USE ieee.std_logic_1164.ALL; USE ieee.std_logic_unsigned.ALL; ENTITY Reader IS PORT ( CLKSYS : IN STD_LOGIC; CLK : IN STD_LOGIC; INIT : IN STD_LOGIC; RDATA : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); INP : IN STD_LOGIC ); END Reader; ARCHITECTURE ppl_type OF Reader IS SIGNAL out_rdata : STD_LOGIC_VECTOR(7 DOWNTO 0) := "00000000"; SIGNAL in_inp : STD_LOGIC := '1'; SIGNAL CLK_p : STD_LOGIC := '0'; SIGNAL INIT_p : STD_LOGIC := '0'; BEGIN PROCESS (CLKSYS, INIT, CLK) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN CLK_p <= CLK; INIT_p <= INIT; END IF; END PROCESS; PROCESS (CLKSYS, INIT, CLK) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN IF INIT_p = '1' AND INIT = '0' THEN out_rdata <= "00000000"; END IF; IF CLK_p = '0' AND CLK = '1' THEN out_rdata <= out_rdata(6 DOWNTO 0) & in_inp; END IF; END IF; END PROCESS; in_inp <= INP; RDATA <= out_rdata; END;
myspi.vhdl
LIBRARY ieee; USE ieee.std_logic_1164.ALL; USE ieee.std_logic_unsigned.ALL; ENTITY MySPI IS PORT ( -- run : out std_logic; NSEND : IN STD_LOGIC_VECTOR(2 DOWNTO 0); NCLKDIV : IN STD_LOGIC_VECTOR(2 DOWNTO 0); CLKIN : IN STD_LOGIC; CLKOUT : OUT STD_LOGIC;--SCK CLKSYS : IN STD_LOGIC; BUSY : OUT STD_LOGIC; INIT : IN STD_LOGIC; WDATA : IN STD_LOGIC_VECTOR(7 DOWNTO 0); RDATA : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); OUTP : OUT STD_LOGIC;--MOSI INP : IN STD_LOGIC; --MISO EN : IN STD_LOGIC ); END MySPI; ARCHITECTURE ppl_type OF MySPI IS COMPONENT ClockGenerator IS PORT ( NSEND : IN STD_LOGIC_VECTOR(2 DOWNTO 0); NCLKDIV : IN STD_LOGIC_VECTOR(2 DOWNTO 0); CLKIN : IN STD_LOGIC; CLKOUT : OUT STD_LOGIC; CLKSYS : IN STD_LOGIC; BUSY : OUT STD_LOGIC; RUN : IN STD_LOGIC; INIT : IN STD_LOGIC ); END COMPONENT; COMPONENT Writer IS PORT ( CLKSYS : IN STD_LOGIC; CLK : IN STD_LOGIC; INIT : IN STD_LOGIC; WDATA : IN STD_LOGIC_VECTOR(7 DOWNTO 0); OUTP : OUT STD_LOGIC ); END COMPONENT; COMPONENT Reader IS PORT ( CLKSYS : IN STD_LOGIC; CLK : IN STD_LOGIC; INIT : IN STD_LOGIC; RDATA : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); INP : IN STD_LOGIC ); END COMPONENT; SIGNAL out_clkout : STD_LOGIC; SIGNAL out_outp : STD_LOGIC; SIGNAL out_busy : STD_LOGIC; SIGNAL internal_init : STD_LOGIC := '1'; SIGNAL internal_run : STD_LOGIC := '1'; TYPE t_state IS (STATE_IDLE, STATE_TRIGGERED); SIGNAL state : t_state := STATE_IDLE; SIGNAL next_state : t_state := STATE_IDLE; SIGNAL reqinit : STD_LOGIC := '0'; BEGIN -- run<=internal_run; ClockGenerator_inst : COMPONENT ClockGenerator PORT MAP( NSEND => NSEND, NCLKDIV => NCLKDIV, CLKIN => CLKIN, CLKOUT => out_clkout, CLKSYS => CLKSYS, BUSY => out_busy, RUN => internal_run, INIT => internal_init ); Writer_inst : COMPONENT Writer PORT MAP( CLKSYS => CLKSYS, CLK => out_clkout, INIT => internal_init, WDATA => WDATA, OUTP => out_outp ); Reader_inst : COMPONENT Reader PORT MAP( CLKSYS => CLKSYS, CLK => out_clkout, INIT => internal_init, RDATA => RDATA, INP => INP ); CLKOUT <= out_clkout WHEN EN = '1' ELSE 'Z'; internal_init <= INIT; OUTP <= out_outp WHEN (out_busy = '1' AND EN = '1') ELSE '1' WHEN (out_busy = '0' AND EN = '1') ELSE 'Z'; --OUTP<='1' WHEN out_busy='0' ELSE out_outp; BUSY <= out_busy; PROCESS (CLKSYS, INIT) BEGIN IF INIT'event AND INIT = '0' THEN IF state = STATE_IDLE THEN reqinit <= '1'; END IF; END IF; IF state = STATE_TRIGGERED THEN reqinit <= '0'; END IF; END PROCESS; PROCESS (state, reqinit) BEGIN CASE state IS WHEN STATE_IDLE => IF reqinit = '1' THEN next_state <= STATE_TRIGGERED; END IF; WHEN STATE_TRIGGERED => next_state <= STATE_IDLE; END CASE; END PROCESS; PROCESS (CLKSYS) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN state <= next_state; END IF; END PROCESS; PROCESS (CLKSYS) BEGIN IF CLKSYS'event AND CLKSYS = '1' THEN IF state = STATE_TRIGGERED THEN internal_run <= '0'; ELSE internal_run <= '1'; END IF; END IF; END PROCESS; END;
そして、テストベンチ。MOSIをMISOとつなげている。
myspi_tb.vhdl
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity MySPI_TB is end MySPI_TB; architecture SIM of MySPI_TB is component MySPI is port ( NSEND : in std_logic_vector(2 downto 0); NCLKDIV : in std_logic_vector(2 downto 0); CLKIN : in std_logic; CLKOUT : out std_logic; CLKSYS : in std_logic; BUSY : out std_logic; INIT : in std_logic; WDATA : in std_logic_vector(7 downto 0); RDATA : out std_logic_vector(7 downto 0); OUTP : out std_logic; INP : in std_logic; EN : in std_logic ); end component; signal nsend : std_logic_vector(2 downto 0):="111"; signal nclkdiv : std_logic_vector(2 downto 0):="101"; signal clkbase : std_logic; signal clkout : std_logic; signal clksys : std_logic; signal busy : std_logic; signal init : std_logic:='1'; signal wdata : std_logic_vector(7 downto 0):="11010101"; signal rdata : std_logic_vector(7 downto 0); signal outp : std_logic; signal inp : std_logic; signal en : std_logic:='1'; constant STEPSYS:time:=125 ns; constant STEPBASE:time:=500 ns; begin MySPI_inst : component MySPI port map( NSEND=>nsend, NCLKDIV=>nclkdiv, CLKIN=>clkbase, CLKOUT=>clkout, CLKSYS=>clksys, BUSY=>busy, INIT=>init, WDATA=>wdata, RDATA=>rdata, OUTP=>outp, INP=>inp, EN=>en ); inp<=outp; process begin clksys<='1'; wait for STEPSYS/2; clksys<='0'; wait for STEPSYS/2; end process; process begin clkbase<='1'; wait for STEPBASE/2; clkbase<='0'; wait for STEPBASE/2; end process; process begin wait for 1 us; init<='0'; wait for STEPSYS; init<='1'; wait for 60 us; wdata<="01101010"; nclkdiv<="001"; wait for 1 us; init<='0'; wait for STEPSYS; init<='1'; wait for 20 us; wdata<="00110101"; nclkdiv<="000"; wait for 1 us; init<='0'; wait for STEPSYS; init<='1'; wait for 10 us; wdata<="01010101"; nsend<="000"; nclkdiv<="000"; wait for 1 us; init<='0'; wait for STEPSYS; init<='1'; wait for 10 us; wdata<="01110100"; nsend<="111"; nclkdiv<="000"; wait for 1 us; init<='0'; wait for STEPSYS; init<='1'; wait for 10 us; en<='0'; wait; end process; end;
VHDLでも相変わらずのコメント皆無。wikipediaによると、
米国国防総省は、業者の納品する機器で含むASICの動作の文書記述のためにVHDLを開発した。すなわち、分厚く複雑になりがちな紙のマニュアルの代替を目指したのが始まりである。
ということなので、VHDLそのものが仕様を示しており、それにコメントを書くのはナンセンスなので、できるだけ書かないようにしよう(、、、曲解)。
ちょっとだけラクするために、Makefile
VHDLC=/usr/bin/ghdl SRC=clkgen.vhdl writer.vhdl reader.vhdl myspi.vhdl TBSRC=myspi_tb.vhdl VCD=myspi.vcd SIMRTL=MySPI_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)
ちなみに、テストベンチに終わりを指示していないので放っておくとずっとやるみたい。適当なところでCtrl+Cでとめちゃう。
そして、GtkWaveで開くとこうなる。
それらしくできているんじゃないかと思う。初めて使ったGHDL。早くていい。ロジックの検討ならメーカーの開発環境は使わずにこっちでやったほうがラク。今までなんで使ってなかったのか。
次回はQuartusPrimeでもやってみます、、、道のりは長い。道のりが長いネタをやっているときに、事件が起こると、休みがなくなって放置してしまう。そして忘れる、、、その繰り返し。やり切ってないネタをいつかやり遂げたいが、過去を振り返るのは大きなストレスを感じる。
VHDLもVerilogHDLもちょっと前はまぁまぁすんなり書けていたのに、もうすっかり忘れてしまってたわ。さらに加えて、論理回路の考え方がどうも苦手なので、素人感出てるんやろなーと思う。まぁ素人ですからっ!
コメントをお書きください