1. 概要
三好 健文, 高前田 伸也,『Interface (インターフェース)』, 2009年09月号, CQ出版, pp.116-118に紹介されているUARTモジュールを試す。『FPGA/PLD入門記事全集』, 2014年, CQ出版にも収録されている。
負論理にできるようにしたこと、読み取り完了信号rd
のタイミングを少し変えたこと以外はテキストのままである。
2. ポケコンPC-G850VS側の準備
ここにあるターミナルソフトウェアをインストールする。今回はg850vterm.ihxをインストールした。インストールできたら、ターミナルを起動し、[BASIC]ボタンを押して設定する。
3. MAX10 FPGA側の準備
下の4つのVHDLをコンパイルして書き込む。
`トップレベルエンティティを見る/隠す`
UART.vhd
library ieee;
use ieee.std_logic_1164.all;
entity UART is
generic(
F_CLK : positive := 48_000_000; -- 評価ボードに載っているクロックをそのまま使う。
BAUD : positive := 9600;
POL_INV: std_logic := '1' -- 0: 正論理, 1: 負論理
);
port(
clk : in std_logic;
aclr_n: in std_logic;
rx: in std_logic;
tx: out std_logic;
rx_data: out std_logic_vector(7 downto 0); -- 受信したデータ
tx_data: in std_logic_vector(7 downto 0); -- 送信するデータ
rd: out std_logic; -- 受信完了信号
wr: in std_logic; -- 送信命令
tx_ready: out std_logic -- 送信可能か
);
end entity;
architecture rtl of UART is
begin
module_rx: entity work.serial_rx
generic map(
F_CLK => F_CLK,
BAUD => BAUD,
POL_INV => POL_INV
)
port map(
clk => clk,
aclr_n => aclr_n,
rd => rd,
din => rx,
dout => rx_data
);
module_tx: entity work.serial_tx
generic map(
F_CLK => F_CLK,
BAUD => BAUD,
POL_INV => POL_INV
)
port map(
clk => clk,
aclr_n => aclr_n,
wr => wr,
din => tx_data,
dout => tx,
ready => tx_ready
);
end architecture;
`シリアル通信用クロック生成器を見る/隠す`
clk_gen.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity clk_gen is
generic(
F_CLK : positive := 48_000_000;
OUT_FREQ: positive := 9600
);
port(
aclr_n : in std_logic;
clk : in std_logic;
clk_out: out std_logic
);
end entity;
architecture rtl of clk_gen is
begin
process(aclr_n, clk)
constant TOP_VAL: natural := F_CLK/(OUT_FREQ * 2) - 1;
variable count : natural range 0 to TOP_VAL := 0;
begin
if aclr_n = '0' then
count := 0;
elsif rising_edge(clk) then
if count >= TOP_VAL then
clk_out <= not clk_out;
count := 0;
else
count := count + 1;
end if;
end if;
end process;
end architecture;
`TXモジュールを見る/隠す`
serial_tx.vhd
-- 参考: Interface (インターフェース), 2009年09月号, pp.116-118, CQ出版
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity serial_tx is
generic(
F_CLK : positive := 48_000_000;
BAUD : positive := 9600;
POL_INV: std_logic := '0' -- 0: 正論理; 1: 負論理
);
port(
clk : in std_logic;
aclr_n: in std_logic;
wr : in std_logic;
din : in std_logic_vector(7 downto 0);
dout : out std_logic;
ready: out std_logic
);
end entity;
architecture rtl of serial_tx is
signal in_din: std_logic_vector(7 downto 0);
signal buf : std_logic_vector(7 downto 0);
signal tx_en: std_logic;
signal load : std_logic;
signal run : std_logic;
signal cbit : natural range 0 to 7;
signal state: natural range 0 to 2;
signal dout_temp: std_logic;
begin
module_clk_gen: entity work.clk_gen
generic map(
F_CLK => F_CLK,
OUT_FREQ => BAUD
)
port map(
aclr_n => aclr_n,
clk => clk,
clk_out => tx_en
);
ready <= '1' when run = '0' and load = '0' else '0';
dout <= dout_temp when POL_INV = '0' else not dout_temp;
process(clk, aclr_n)
begin
if aclr_n = '0' then
load <= '0';
elsif rising_edge(clk) then
if wr = '1' and run = '0' then
load <= '1';
in_din <= din;
end if;
if load = '1' and run = '1' then
load <= '0';
end if;
end if;
end process;
process(tx_en, aclr_n)
begin
if aclr_n = '0' then
dout_temp <= '1';
cbit <= 0 ;
state <= 0 ;
run <= '0';
elsif rising_edge(tx_en) then
case state is
when 0 =>
cbit <= 0;
if load then
dout_temp <= '0'; --スタートビット
state <= state + 1;
buf <= in_din;
run <= '1';
else
dout_temp <= '1';
run <= '0';
end if;
when 1 =>
if cbit <= 6 then
dout_temp <= buf(cbit); -- lsbから順番に送信する。
cbit <= cbit + 1;
elsif cbit = 7 then
dout_temp <= buf(cbit);
state <= state + 1;
end if;
when 2 =>
dout_temp <= '1'; --ストップビット
state <= 0 ;
when others =>
state <= 0;
end case;
end if;
end process;
end architecture;
`RXモジュールを見る/隠す`
serial_rx.vhd
-- 参考: Interface (インターフェース), 2009年09月号, pp.116-118, CQ出版
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity serial_rx is
generic(
F_CLK : positive := 48_000_000;
BAUD : positive := 9600;
POL_INV: std_logic := '0' -- 0: 正論理; 1: 負論理
);
port(
clk : in std_logic;
aclr_n: in std_logic;
din : in std_logic;
rd : out std_logic;
dout: out std_logic_vector(7 downto 0)
);
end entity;
architecture rtl of serial_rx is
signal buf : std_logic_vector(7 downto 0);
signal start: std_logic;
signal cbit : natural range 0 to 159;
signal rx_en: std_logic;
signal din_temp: std_logic;
begin
module_clk_gen: entity work.clk_gen
generic map(
F_CLK => F_CLK,
OUT_FREQ => BAUD * 16
)
port map(
aclr_n => aclr_n,
clk => clk,
clk_out => rx_en
);
din_temp <= din when POL_INV = '0' else not din;
process(aclr_n, rx_en)
begin
if aclr_n = '0' then
start <= '0';
cbit <= 0 ;
buf <= (others => '0');
dout <= (others => '0');
elsif rising_edge(rx_en) then
if start = '0' then
rd <= '0';
if din_temp = '0' then -- RXラインがHからLに変化して、
start <= '1';
end if;
cbit <= 0;
else
case cbit is
when 6 => -- それが本当にスタートビットであったら、
if din_temp = '1' then
cbit <= 0 ;
start <= '0';
else
cbit <= cbit + 1;
end if;
when 22|38|54|70|86|102|118|134 => -- 16クロックごとに、
buf <= din_temp & buf(7 downto 1); -- lsbから順番に読み取って、
cbit <= cbit + 1;
when 135 =>
dout <= buf; -- 全部読み取り終えたらすぐにデータを確保して、
cbit <= cbit + 1;
when 159 => -- ストップビットの末尾で、
cbit <= 0 ;
start <= '0';
rd <= '1'; -- 受信完了信号を出す。
when others =>
cbit <= cbit + 1;
end case;
end if;
end if;
end process;
end architecture;
4. 実際に動かす
4.1 全体の接続図
ポケコンから受信した1文字をそっくりそのまま送り返す。ここでは送受両ラインともこのモジュールでレベル変換したが、ポケコン(RX) <- MAX10 (TX)のラインはレベル変換しなくてもよい。
4.2 実行結果
ターミナルのエコーをオンにしているので同じ文字が2回ずつ表示される。
4.3 ロジアナによる観測結果
今、受信した'a'
をそのまま送り返したところ。通信ラインは負論理である。
5. ファイル一式
https://github.com/ti-nspire/VHDL_for_Quartus_Prime/tree/main/UART_ver_2