FPGAでルックアップテーブルをもとに三角関数を出力する
※にひにひのメモです.
0. はじめに
FPGAにおいて,三角関数の計算はマイコンみたいに三角関数の関数を呼べばいいという単純なものではありません.実際に逐一計算させようとすると[1]のようにそれだけでかなりの論理素子を使ってしまいます.今回は三角関数のルックアップテーブル用のcsvを用いて三角関数を使っていきたいと思います.
- 注意点
- 読み込むsin.csvについて
- simulationにおいて($readmemh)
- FPGAに書き込み
- まとめ
1. 注意点
- FPGAに実際に書き込むときとシミュレーションを行うときで,使える関数やルックアップテーブルのファイル形式が異なる場合があります.
- 今回は一気にファイルを読み込む形にします.一行ずつ読み込む方法もあります.[2]
今回"にひにひ"が行った環境は以下の通りです
- Windows11 Education
- Quartus Prime Lite Edition 20.1.1
- 使用FPGA:10M08SAE144C8GES
- PulseView(ロジックアナライザ用ソフトウェア)
- 安物ロジックアナライザ24MHz8ch(詳細不明,これと同じものかと思われる)
2. 読み込むsin.csvについて
ルックアップテーブル用のcsvを作成するコードをpythonで書きました.今回はこれで生成したsin.csvを読み込ませます.
import math
# パラメータ設定
num_points = 256 # データ点数
amplitude = 127 # 振幅(8bit符号なしで 0~255 に収まるようにする)
offset = 128 # オフセット(中心を0→128にシフト)
filename = "sin.csv" # 出力ファイル名
with open(filename, "w") as f:
for i in range(num_points):
angle = 2 * math.pi * i / num_points
sin_val = int(amplitude * math.sin(angle) + offset)
hex_str = f"{sin_val:02X}" # 2桁の16進表記(大文字)
f.write(hex_str + "\n")
print(f"{filename} を生成しました.")
生成したcsvは以下の図のような正弦波の値となっています.
3. simulationにおいて($readmemh)
sin.csvからデータを配列データsin_memに読み込みます.
$readmemh("sin.csv", sin_mem);
0~99までのデータを読み込みたい場合,
$readmemh("sin.csv", sin_mem, 0, 99);
とすれば良いです.
実際にsinのデータを読み込んで出力するモジュールを記述しました.
module readcsv (
input wire clk,
output reg [7:0] sin_out
);
// パラメータ
parameter DATA_LEN = 256;
reg [7:0] sin_mem [0:DATA_LEN-1];
integer i;
// 出力用カウンタ
reg [7:0] addr;
initial begin
// CSVファイル読み込み
$readmemh("C:/XXXXX/sin.csv", sin_mem);//sin.csvの絶対パス
addr = 0;
end
always @(posedge clk ) begin
sin_out <= sin_mem[addr];
if (addr < DATA_LEN - 1)
addr <= addr + 1;
end
endmodule
このコードを雑に説明すると,initial begin-endでcsvファイルを読み込み,sim_memに格納します.always begin-endでsin_memの要素をひとつづつaddrをインクリメントしてsin_outに格納しています.
ブロック図は以下の通りです.特別なことは何もしていないです.
シミュレーションの実行結果は以下の通りです.
理想の信号波形が以下のような感じなので,だいたいあってそうですね.
4. FPGAに書き込み
今回安物のロジアナで測定する関係上,分周器を挟むように修正しました.(以下のブロック図参照,分周器モジュールのコードは付録2に載せています)
そして,実際にMAX10 FPGA 10M08SAE144C8Gに書き込みました.
測定風景↓
シミュレーション(上図),FPGAにロジックアナライザをつないで測定(下図)
5. まとめ
csvを読み込んでFPGAのシミュレーションを行うことができた.
参考文献・サイト
- [1]CORDICをFPGAに実装する @takeru0x5569(takeRu) (閲覧日2025/04/17)https://qiita.com/takeru0x5569/items/2d51212f27788f8b3c8f
- [2]Verilogで外部テキストファイル読み込み @takeru0x5569
(takeRu) (閲覧日2025/04/17)(https://qiita.com/takeru0x5569/items/b054cdcf9e014f332edd) - [3]Verilogの$readmemh活用法!初心者向け10ステップで学ぶ(閲覧日2025/04/17)
https://jp-seemore.com/iot/12263/ - [4]格安!2000円以下でUSBロジックアナライザ(2024年12月)
https://qiita.com/totuto/items/4472388469719b1a4fb2
付録1
以下FPGAのsin_outの信号波形の理想波形をプロットするコードです.
import numpy as np
import matplotlib.pyplot as plt
# CSVファイルの読み込み
filename = "sin.csv"
with open(filename, "r") as f:
sin_vals = [int(line.strip(), 16) for line in f]
# 各ビットに対応する信号を生成
sin_bits = np.array([[(val >> bit) & 1 for bit in range(8)] for val in sin_vals])
# プロット
plt.figure(figsize=(10, 6))
time = np.arange(len(sin_vals))
# 各ビットをプロット(縦方向にシフト)
shifted_sin_bits = sin_bits + np.arange(8) # 各ビットの信号を縦にシフト
for bit in range(8):
plt.plot(time, shifted_sin_bits[:, bit], label=f'Bit {bit}')
plt.title('Ideal Signal Waveform for sin_out Bits [0-7] (Shifted)')
plt.xlabel('Sample Index')
plt.xlim([0, len(sin_vals)-1])
plt.ylabel('Shifted Bit Value')
plt.legend(loc='upper right')
plt.grid(True)
plt.show()
出力結果↓
付録2
実際にFPGAに書き込んだ時に使った分周器用コード
fd.v
module fd(
input wire clk_row, // 50MHz クロック入力
output reg clk_fd // 1MHz 出力クロック
);
// === パラメータとレジスタ定義 ===
localparam DIVIDE_BY = 25; // トグルごとのカウント値(1周期で50クロック分)
reg [5:0] counter = 0; // カウンタ(6ビットで十分)
always @(posedge clk_row) begin
if (counter == (DIVIDE_BY - 1)) begin
counter <= 0;
clk_fd <= ~clk_fd; // トグルして周波数を半分に
end else begin
counter <= counter + 1;
end
end
endmodule
Discussion