chatGPTで簡単な回路をVHDLで生成する~その1
はじめに
はじめましてmiczと申します。インクジェットプリンターやレーザープリンタ、複写機のASICをごろごろ作っていた者です。巷で「chatGPTは僕ちゃんより優秀なコード吐くよ」との話が絶えないので、「所詮言語モデルなんだから大したことはできないんでしょ」「さすがにマイナーなハードウエア記述言語は無理なんでしょ」と思いつつも、「やさしくインプットしてあげれば簡単なのはいけるんじゃないの」との期待を持ちつつ遊んでみました。
2段シフトレジスタを作る
手始めに2段のシフトレジスタを作ってみます。
入力は雑にこんな感じで。
VHDL言語を使用して2段シフトレジスタの回路作成してください。入力はD、クロックはCLK、リセットはRESETNでローアクティブリセットかつ非同期リセット、出力はQとします。Qは2段目のレジスタの出力とします。
これぐらいならインターネットにごろごろ転がっているので汎用言語モデルでも出せるでしょ、見せてくれたまへchatGPT君。
ということでこうなりました。
library ieee;
use ieee.std_logic_1164.all;
entity shift_register is
port (
D: in std_logic; -- 入力信号
CLK: in std_logic; -- クロック
RESETN: in std_logic; -- リセット信号
Q: out std_logic -- 出力信号
);
end entity;
architecture rtl of shift_register is
signal Q1, Q2: std_logic; -- 内部信号
begin
process (CLK, RESETN)
begin
if RESETN = '0' then -- リセットがアクティブな時
Q1 <= '0'; -- Q1とQ2をリセットする
Q2 <= '0';
elsif rising_edge(CLK) then -- クロックの立ち上がりエッジで処理する
Q1 <= D; -- 入力信号を1段目のレジスタに格納
Q2 <= Q1; -- 1段目のレジスタの出力を2段目のレジスタに格納
end if;
end process;
Q <= Q2; -- 2段目のレジスタの出力をQに接続する
end architecture;
で、説明が以下の通り
この回路は、クロックの立ち上がりエッジで入力信号Dを1段目のレジスタに格納し、1段目のレジスタの出力を2段目のレジスタに格納します。2段目のレジスタの出力が出力信号Qに接続されます。リセット信号RESETNがアクティブになった場合、Q1とQ2がリセットされ、出力信号Qは'0'になります。
完璧ですね! まあできるとは思ったけどきれいにコメントまで付けてるし。
4個の8ビットのレジスタ群を作る
じゃあ次に「4個のレジスタを作ってライト信号と同期して該当アドレス部に書き込む。そのブロックのデータ出力は外部のリードセレクタ信号に従って該当レジスタのデータを出す」ってのはどうでしょうかね。ってことで以下チャレンジ。
もはや日本語仕様書・・
VHDL言語を使用して以下の仕様に従い回路を作成してください。分からないことがあれば私に聞いてください。
・ブロック名:REG
・入力信号:
アドレスバス:信号名はADR:バス幅2ビット
データバス:信号名はDAT:バス幅8ビット
ライト:信号名はWR_N
クロック:信号名はCLK
リセット:信号名はRESET_N
リードセレクタ:信号名はRDSEL:バス幅2ビット
・レジスタ仕様:
アドレス0x0:レジスタ名はレジスタ0
アドレス0x1:レジスタ名はレジスタ1
アドレス0x2:レジスタ名はレジスタ2
アドレス0x3:レジスタ名はレジスタ3
・出力信号
データ:信号名はDATO:バス幅8ビット
・出力信号DATOの制御
RDSELが"00"の時は、レジスタ0のデータを出力する
RDSELが"01"の時は、レジスタ1のデータを出力する
RDSELが"10"の時は、レジスタ2のデータを出力する
RDSELが"11"の時は、レジスタ3のデータを出力する
・その他仕様
信号名の後ろに_Nがあればローアクティブ
非同期リセット
立ち上がり同期クロック
レジスタはWR_Nがアクティブな時にライトする
レジスタはライトオンリー
でこうなりました。
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity REG is
port(
ADR : in std_logic_vector(1 downto 0);
DAT : in std_logic_vector(7 downto 0);
WR_N : in std_logic;
CLK : in std_logic;
RESET_N : in std_logic;
RDSEL : in std_logic_vector(1 downto 0);
DATO : out std_logic_vector(7 downto 0)
);
end entity;
architecture rtl of REG is
type reg_array is array (0 to 3) of std_logic_vector(7 downto 0);
signal reg : reg_array := (x"00", x"00", x"00", x"00");
signal prev_clk : std_logic := '0';
begin
process(CLK, RESET_N)
begin
if RESET_N = '0' then
reg <= (x"00", x"00", x"00", x"00");
elsif rising_edge(CLK) then
if WR_N = '0' then
case ADR is
when "00" =>
reg(0) <= DAT;
when "01" =>
reg(1) <= DAT;
when "10" =>
reg(2) <= DAT;
when "11" =>
reg(3) <= DAT;
when others =>
null; -- do nothing
end case;
end if;
end if;
end process;
process(RDSEL, reg)
begin
case RDSEL is
when "00" =>
DATO <= reg(0);
when "01" =>
DATO <= reg(1);
when "10" =>
DATO <= reg(2);
when "11" =>
DATO <= reg(3);
when others =>
null; -- do nothing
end case;
end process;
end architecture;
で、説明が以下の通り
この回路は、REGというエンティティで定義され、以下のようなポートを持っています。
ADR : 2ビットのアドレスバス
DAT : 8ビットのデータバス
WR_N : ライト信号
CLK : クロック信号
RESET_N : リセット信号
RDSEL : 2ビットのリードセレクタ
DATO : 8ビットの出力データ
回路の内部では、reg_arrayという型で、4つの8ビットレジスタを宣言しています。レジスタの初期値はすべてx"00"で、非同期リセットによってすべてのレジスタがリセットされます。
回路は、立ち上がりエッジで動作するプロセスで制御されます。ライト信号WR_Nがアクティブな場合、アドレスADRに対応するレジスタにデータバスのデータを書き込みます。それ以外の場合、リードセレクタRDSELに対応するレジスタのデータを出力します。
できてますね。論理合成までかけてチェックしていませんが。
signal prev_clk : std_logic := '0'; は不要かと思うのですが、まあ素晴らしいです。HDLに慣れた人なら、「直接人間が書いた方が早いんだよ」と怒られそうですが、汎用言語モデルでどこまでできるのかを試してみたかっただけなので、、
楽しくなってきたのでもう少し使ってみようと思います。お付き合いくださりありがとうございました。