たいていのマイコンの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もちょっと前はまぁまぁすんなり書けていたのに、もうすっかり忘れてしまってたわ。さらに加えて、論理回路の考え方がどうも苦手なので、素人感出てるんやろなーと思う。まぁ素人ですからっ!

コメントをお書きください