▲開発環境
・Windows10
・Ollama cloud
・deepseek-v4.1-flash
・VS Code + Copilot agent
・Quartus Prime Lite + Questa
▲ハードウェア
・max10(Altera)
・特別版No.3のボード + 自作Pmod拡張ボード
・PMOD-TFTLCD v1.1 (muse lab)
・PMOD-microphone v1.0 (muse lab)
●プロンプト
▲初期プロンプト
FPGAデザインを作る
# Application
マイクを使って音声情報を取得し,その音声波形をLCDに表示する
Quartus Primerのプロジェクトとして実装する
# Hardware
## FPGA
10M08SCE144C8G
## LCD
PMOD-TFTLCD v1.1(muse lab)
Resolusion:320x240
https://github.com/wuxx/icesugar/blob/master/schematic/pmod-tftlcd-v1.1.pdf
## Microphone
PMOD-Microphone v1.0(muse lab)
https://github.com/wuxx/icesugar/blob/master/schematic/pmod-microphone-v1.0.pdf
# Software
## Development Evironment
Quartus Prime
## HDL
SystemVerilog
# Physical constraint
Pmod 1 | Microphone
46 | NC
44 | NC
41 | NC
38 | NC
45 | WS
43 | NC
39 | SD
47 | SCK
Pmod 2 | LCD
80 | NC
79 | NC
77 | RS
76 | NC
81 | CS
78 | MOSI
74 | NC
75 | CLK広げる
▲追加プロンプト
アプリケーションは正しく動いているようです.次の点を改良してください.
・ノイズが多いので移動平均を取る
・振幅後入力値のゲインを下げる
●生成されたRTLコード
・top.sv
// ============================================================
// top.sv
// マイクで取り込んだ音声波形を LCD に表示する
//
// [PMOD 1] PMOD-Microphone v1.0 (I2S MEMS マイク)
// FPGA が I2S マスタとして SCK / WS を生成し SD を受信
//
// [PMOD 2] PMOD-TFTLCD v1.1 (ILI9341, 320x240, SPI)
// 音声波形を 1 サンプル 1 列で描画
//
// 構成:
// i2s_rx : I2S 受信 -> sample (16bit 符号付き)
// moving_avg : 移動平均 -> ノイズ低減
// audio_buf : 波形用 RAM -> 4096 サンプルのリングバッファ
// lcd_wave : ILI9341 制御 + 波形描画
//
// ボタン (btn_rst) を押すとリセット
// ============================================================
module top (
// --- クロック / リセット ---
input logic clk, // PIN_88 : 50 MHz
input logic btn_rst, // PIN_17 : 押下 = Low
// --- PMOD 1 : Microphone ---
output logic mic_sck, // PIN_47 : SCK
output logic mic_ws, // PIN_45 : WS
input logic mic_sd, // PIN_39 : SD
// --- PMOD 2 : LCD ---
output logic lcd_cs, // PIN_81 : CS
output logic lcd_dc, // PIN_77 : RS
output logic lcd_mosi, // PIN_78 : MOSI
output logic lcd_sck, // PIN_75 : CLK
// --- LED ---
output logic led, // PIN_85 : フレーム描画ごとにトグル
output logic led0, // PIN_123: マイク入力検出
output logic led1, // PIN_122: サンプル受信ごとにトグル
output logic led2, // PIN_121: 未使用 (0)
output logic led3 // PIN_120: LCD 初期化完了
);
// ========================================================
// パラメータ
// ========================================================
localparam int CLK_HZ = 50_000_000; // 入力クロック
localparam int SCK_HZ = 1_000_000; // I2S ビットクロック (Fs = 15.625 kHz)
localparam int BUF_AW = 12; // 波形 RAM アドレス幅 (4096 サンプル)
localparam int AVG_N = 8; // 移動平均のサンプル数
localparam int AMP_GAIN_SHIFT = 0; // 波形表示ゲイン (1 -> 2倍)
// ========================================================
// 電源投入時リセット + ボタン 2 段同期化
// rst_n : Low 有効の非同期リセット
// ========================================================
logic [15:0] por_cnt;
logic btn_r1, btn_r2;
logic rst_n;
always_ff @(posedge clk) begin
if (por_cnt != 16'hFFFF) por_cnt <= por_cnt + 16'd1;
end
always_ff @(posedge clk) begin
btn_r1 <= btn_rst;
btn_r2 <= btn_r1;
end
assign rst_n = (por_cnt == 16'hFFFF) & btn_r2;
// ========================================================
// I2S マイク受信
// ========================================================
logic signed [15:0] sample;
logic sample_valid;
i2s_rx #(
.CLK_HZ (CLK_HZ),
.SCK_HZ (SCK_HZ)
) u_i2s_rx (
.clk (clk),
.rst_n (rst_n),
.mic_sck (mic_sck),
.mic_ws (mic_ws),
.mic_sd (mic_sd),
.sample (sample),
.sample_valid (sample_valid)
);
// ========================================================
// 移動平均フィルタ (ノイズ低減)
// AVG_N サンプル分の平均をとることで、波形に乗る
// 高周波ノイズを抑える。
// ========================================================
logic signed [15:0] avg_sample;
logic avg_valid;
moving_avg #(
.N (AVG_N),
.DW (16)
) u_moving_avg (
.clk (clk),
.rst_n (rst_n),
.valid_in (sample_valid),
.data_in (sample),
.data_out (avg_sample),
.valid_out (avg_valid)
);
// ========================================================
// 波形用リングバッファ
// ========================================================
logic [BUF_AW-1:0] rd_addr;
logic [BUF_AW-1:0] wr_ptr;
logic signed [15:0] rdata;
audio_buf #(
.AW (BUF_AW),
.DW (16)
) u_audio_buf (
.clk (clk),
.we (avg_valid),
.wdata (avg_sample),
.wr_ptr (wr_ptr),
.rd_addr (rd_addr),
.rdata (rdata)
);
// ========================================================
// LCD コントローラ + 波形描画
// ========================================================
logic init_done;
logic frame_tick;
lcd_wave #(
.CLK_HZ (CLK_HZ),
.BUF_AW (BUF_AW),
.LCD_W (320),
.LCD_H (240),
.AMP_GAIN_SHIFT (AMP_GAIN_SHIFT),
.FRAME_WAIT (2_500_000) // 50 ms
) u_lcd_wave (
.clk (clk),
.rst_n (rst_n),
.lcd_cs (lcd_cs),
.lcd_dc (lcd_dc),
.lcd_mosi (lcd_mosi),
.lcd_sck (lcd_sck),
.rd_addr (rd_addr),
.rdata (rdata),
.wr_ptr (wr_ptr),
.init_done (init_done),
.frame_tick (frame_tick)
);
// ========================================================
// LED インジケータ
// ========================================================
logic led_frame;
logic led_sample;
logic [22:0] mic_act_cnt;
// フレーム描画ごとにトグル
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) led_frame <= 1'b0;
else if (frame_tick) led_frame <= ~led_frame;
end
// サンプル受信ごとにトグル
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) led_sample <= 1'b0;
else if (sample_valid) led_sample <= ~led_sample;
end
// マイク入力がある間点灯 (約 0.17 秒間保持)
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) mic_act_cnt <= '0;
else if (sample_valid) mic_act_cnt <= '0;
else if (mic_act_cnt != 23'h7FFFFF) mic_act_cnt <= mic_act_cnt + 23'd1;
end
assign led = led_frame;
assign led0 = (mic_act_cnt != 23'h7FFFFF);
assign led1 = led_sample;
assign led2 = 1'b0;
assign led3 = init_done;
endmodule広げる
・moving_avg.sv
// ============================================================
// moving_avg.sv
// 移動平均フィルタ (ノイズ低減用)
//
// N サンプルの単純移動平均を出力する。
// ランニングサム方式なので乗算器は不要 (加算 2 回のみ)。
//
// Fs = 15.625 kHz, N = 8 の場合の -3dB 遮断周波数は約 1.1 kHz。
// 耳障りな高周波ノイズを落としつつ、音声波形の形は保たれる。
//
// 平均は「サンプル列を N 個ずつ矩形窓で平均」する FIR フィルタと
// 等価。乗算が不要で FPGA では最も安価な平滑化手段。
// ============================================================
module moving_avg #(
parameter int N = 8, // 平均するサンプル数 (2 の冪)
parameter int DW = 16 // データ幅
) (
input logic clk,
input logic rst_n, // 非同期リセット (Low 有効)
input logic valid_in, // 入力有効 (1 クロックパルス)
input logic signed [DW-1:0] data_in,
output logic signed [DW-1:0] data_out, // 移動平均値
output logic valid_out // 1 クロック遅れて出力
);
localparam int AW = $clog2(N); // 履歴バッファのアドレス幅
// 和のビット幅を 1bit 余分に取る。
// これにより acc - hist[wp] の中間結果が 19bit からはみ出しても
// 切り捨てが起きない (Verilog は全オペランドの最大幅で演算するため)。
localparam int SW = DW + AW + 1;
logic signed [DW-1:0] hist [0:N-1]; // 過去 N サンプルの履歴
logic [AW-1:0] wp; // 履歴の書き込み位置
logic signed [SW-1:0] acc; // 現在の窓の合計値
logic [AW:0] fill; // 有効なサンプル数 (0 .. N)
// 窓が埋まったかどうかの判定用 (幅を明示して切捨て警告を避ける)
localparam logic [AW:0] N_CNT = N[AW:0];
// 次の合計
// 窓がまだ埋まっていない間は hist[wp] を引かない
// (hist は 1 度も書かれていない位置を参照するため)
// 窓が埋まった後はランニングサムで最古の値を引く
wire signed [SW-1:0] acc_next = (fill == N_CNT)
? (acc - hist[wp] + data_in)
: (acc + data_in);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// hist をリセットするループを置くと合成時にラッチを
// 推論するツールがあるため、acc を 0 に戻し
// fill カウンタで窓が埋まるまで hist を参照しない方式にする
wp <= '0;
acc <= '0;
fill <= '0;
data_out <= '0;
valid_out <= 1'b0;
end else begin
valid_out <= valid_in;
if (valid_in) begin
hist[wp] <= data_in;
wp <= wp + 1'b1;
acc <= acc_next;
if (fill != N_CNT)
fill <= fill + 1'b1;
// 合計 / N : 符号付きなので算術シフト相当のビット選択
// acc_next[SW-2:AW] は符号ビットを含む DW bit になる
data_out <= acc_next[SW-2:AW];
end
end
end
endmodule広げる
・lcd_wave.sv
// ============================================================
// lcd_wave.sv
// ILI9341 SPI LCD コントローラ + 波形描画
// PMOD-TFTLCD v1.1 (320x240, RGB565)
//
// 画面構成 (横向き: MADCTL = 0x28)
// 背景 : 黒
// グリッド: 32px ごとの暗い線
// 中央線 : グレー (振幅 0 のライン)
// 波形 : 緑 (1 サンプル = 1 列)
//
// 描画方式:
// フレーム開始時に書き込みポインタ (wr_ptr) をラッチし、
// その 320 サンプル前から現在までのデータを
// 左→右に 1 サンプル 1 列で描画する。
//
// SPI : Mode 0, SCK = CLK_HZ / 4 (= 12.5 MHz @ 50 MHz)
// ============================================================
module lcd_wave #(
parameter int CLK_HZ = 50_000_000,
parameter int BUF_AW = 12, // 波形 RAM アドレス幅
parameter int LCD_W = 320,
parameter int LCD_H = 240,
parameter int AMP_GAIN_SHIFT = 2, // 振幅ゲイン (左シフト量)
parameter int FRAME_WAIT = 2_500_000, // フレーム間隔 (50MHz -> 50ms)
parameter int POWERON_WAIT = 7_500_000, // 電源ON後待機 (50MHz -> 150ms)
parameter int SWRST_WAIT = 250_000, // SWRESET 後待機 (5ms)
parameter int SLPOUT_WAIT = 6_000_000 // SLPOUT 後待機 (120ms)
) (
input logic clk,
input logic rst_n, // 非同期リセット (Low 有効)
// --- PMOD-TFTLCD ---
output logic lcd_cs, // CS (Low 有効)
output logic lcd_dc, // RS (1=Data, 0=Command)
output logic lcd_mosi, // MOSI
output logic lcd_sck, // SCK
// --- 波形 RAM 読み出し ---
output logic [BUF_AW-1:0] rd_addr,
input logic signed [15:0] rdata,
input logic [BUF_AW-1:0] wr_ptr, // RAM 書き込みポインタ
// --- ステータス ---
output logic init_done,
output logic frame_tick // 1 フレーム描画完了パルス
);
// ========================================================
// 定数
// ========================================================
// SPI バイト送信器は 1 ビットあたり 4 クロック使うため
// SPI クロック = CLK_HZ / 4 = 12.5 MHz となる。
localparam int TOTAL_PIX = LCD_W * LCD_H; // 76800
localparam int CNT_W = $clog2(TOTAL_PIX); // 17
localparam int Y_CENTER = LCD_H / 2; // 120
localparam int Y_OFFSET = Y_CENTER - 1; // 119 (振幅 0 の行)
localparam int Y_MAXROW = LCD_H - 1; // 239
// 定数 (比較用は int のままにしておくとビット幅の切捨て警告が出ない)
localparam int LAST_PIX = TOTAL_PIX - 1;
localparam int LAST_COL = LCD_W - 1;
localparam int LAST_ROW = LCD_H - 1;
// サイズ付き定数 (演算に使うものだけ幅を明示する)
localparam logic [BUF_AW-1:0] OFF_PREV = LCD_W;
localparam logic [23:0] POWERON_CNT = POWERON_WAIT;
localparam logic [23:0] FRAME_WAIT_C = FRAME_WAIT;
localparam logic [23:0] SWRST_CNT = SWRST_WAIT;
localparam logic [23:0] SLPOUT_CNT = SLPOUT_WAIT;
localparam logic [8:0] Y_OFFSET_C = Y_OFFSET;
localparam logic [7:0] Y_MAXROW_C = Y_MAXROW;
localparam logic [15:0] COL_BG = 16'h0000; // 黒
localparam logic [15:0] COL_GRID = 16'h1082; // 暗い緑 (グリッド)
localparam logic [15:0] COL_AXIS = 16'h4208; // グレー (中央線)
localparam logic [15:0] COL_WAVE = 16'h07E0; // 緑 (波形)
// ========================================================
// 初期化シーケンス ROM
// bit[8] = 0 : CMD (DC = 0)
// bit[8] = 1 : DAT (DC = 1)
// ========================================================
localparam [6:0] ROM_DEPTH = 7'd64;
localparam [8:0]
R00=9'h001, R01=9'h011, // SWRESET, SLPOUT
R02=9'h03A, R03=9'h155, // COLMOD = 0x55
R04=9'h036, R05=9'h128, // MADCTL = 0x28 (横)
R06=9'h0B1, R07=9'h100, R08=9'h118, // FRMCTR1
R09=9'h0C0, R10=9'h123, // PWCTR1
R11=9'h0C1, R12=9'h110, // PWCTR2
R13=9'h0C5, R14=9'h13E, R15=9'h128, // VMCTR1
R16=9'h0C7, R17=9'h186, // VMCTR2
R18=9'h026, R19=9'h101, // GAMMASET = 1
R20=9'h0E0, // GMCTRP1
R21=9'h10F, R22=9'h131, R23=9'h12B, R24=9'h10C,
R25=9'h10E, R26=9'h108, R27=9'h14E, R28=9'h1F1,
R29=9'h137, R30=9'h107, R31=9'h110, R32=9'h103,
R33=9'h10E, R34=9'h109, R35=9'h100,
R36=9'h0E1, // GMCTRN1
R37=9'h100, R38=9'h10E, R39=9'h114, R40=9'h103,
R41=9'h111, R42=9'h107, R43=9'h131, R44=9'h1C1,
R45=9'h148, R46=9'h108, R47=9'h10F, R48=9'h10C,
R49=9'h131, R50=9'h136, R51=9'h10F,
R52=9'h02A, R53=9'h100, R54=9'h100, // CASET (0..319)
R55=9'h101, R56=9'h13F,
R57=9'h02B, R58=9'h100, R59=9'h100, // PASET (0..239)
R60=9'h100, R61=9'h0EF,
R62=9'h029, // DISPON
R63=9'h02C; // Memory Write
function automatic [8:0] rom_read(input [6:0] addr);
case (addr)
7'd0: rom_read = R00; 7'd1: rom_read = R01;
7'd2: rom_read = R02; 7'd3: rom_read = R03;
7'd4: rom_read = R04; 7'd5: rom_read = R05;
7'd6: rom_read = R06; 7'd7: rom_read = R07;
7'd8: rom_read = R08; 7'd9: rom_read = R09;
7'd10: rom_read = R10; 7'd11: rom_read = R11;
7'd12: rom_read = R12; 7'd13: rom_read = R13;
7'd14: rom_read = R14; 7'd15: rom_read = R15;
7'd16: rom_read = R16; 7'd17: rom_read = R17;
7'd18: rom_read = R18; 7'd19: rom_read = R19;
7'd20: rom_read = R20; 7'd21: rom_read = R21;
7'd22: rom_read = R22; 7'd23: rom_read = R23;
7'd24: rom_read = R24; 7'd25: rom_read = R25;
7'd26: rom_read = R26; 7'd27: rom_read = R27;
7'd28: rom_read = R28; 7'd29: rom_read = R29;
7'd30: rom_read = R30; 7'd31: rom_read = R31;
7'd32: rom_read = R32; 7'd33: rom_read = R33;
7'd34: rom_read = R34; 7'd35: rom_read = R35;
7'd36: rom_read = R36; 7'd37: rom_read = R37;
7'd38: rom_read = R38; 7'd39: rom_read = R39;
7'd40: rom_read = R40; 7'd41: rom_read = R41;
7'd42: rom_read = R42; 7'd43: rom_read = R43;
7'd44: rom_read = R44; 7'd45: rom_read = R45;
7'd46: rom_read = R46; 7'd47: rom_read = R47;
7'd48: rom_read = R48; 7'd49: rom_read = R49;
7'd50: rom_read = R50; 7'd51: rom_read = R51;
7'd52: rom_read = R52; 7'd53: rom_read = R53;
7'd54: rom_read = R54; 7'd55: rom_read = R55;
7'd56: rom_read = R56; 7'd57: rom_read = R57;
7'd58: rom_read = R58; 7'd59: rom_read = R59;
7'd60: rom_read = R60; 7'd61: rom_read = R61;
7'd62: rom_read = R62; 7'd63: rom_read = R63;
default: rom_read = 9'h000;
endcase
endfunction
// ========================================================
// 電源 ON 後 150ms 待機
// ========================================================
logic [23:0] poweron_cnt;
logic init_start;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
poweron_cnt <= '0;
init_start <= 1'b0;
end else if (poweron_cnt == POWERON_CNT) begin
init_start <= 1'b1;
end else begin
poweron_cnt <= poweron_cnt + 24'd1;
end
end
// ========================================================
// SPI バイト送信器 (Mode 0, MSB ファースト)
// tx_cnt[1:0] : 0=MSB 出力, 1=SCK 立上, 2=保持, 3=SCK 立下
// tx_cnt[5:2] : ビット番号 0..7
// ========================================================
logic [7:0] tx_byte;
logic tx_dc_reg;
logic tx_start;
logic tx_busy;
logic [5:0] tx_cnt;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
lcd_cs <= 1'b1;
lcd_sck <= 1'b0;
lcd_mosi <= 1'b0;
lcd_dc <= 1'b0;
tx_busy <= 1'b0;
tx_cnt <= '0;
end else if (tx_start && !tx_busy) begin
tx_busy <= 1'b1;
tx_cnt <= '0;
lcd_cs <= 1'b0;
lcd_dc <= tx_dc_reg;
lcd_sck <= 1'b0;
lcd_mosi <= tx_byte[7];
end else if (tx_busy) begin
tx_cnt <= tx_cnt + 6'd1;
case (tx_cnt[1:0])
2'b00: lcd_mosi <= tx_byte[7 - tx_cnt[5:2]];
2'b01: lcd_sck <= 1'b1;
2'b10: ; // 半周期保持
2'b11: begin
lcd_sck <= 1'b0;
if (tx_cnt[5:2] == 4'd7) begin
tx_busy <= 1'b0;
lcd_cs <= 1'b1;
end
end
endcase
end
end
// ========================================================
// ステートマシン
// ========================================================
// ステートマシン (typedef enum は古いシミュレータで扱いにくいため
// localparam の整数で表現する)
localparam [3:0]
S_WAIT = 4'd0,
S_INIT_TX = 4'd1,
S_INIT_WAIT = 4'd2,
S_SWRST_DLY = 4'd3,
S_SLPOUT_DLY = 4'd4,
S_FRAME_START= 4'd5,
S_PIX_CALC = 4'd6,
S_PIX_RD = 4'd7,
S_PIX_HI_TX = 4'd8,
S_PIX_HI_W = 4'd9,
S_PIX_LO_TX = 4'd10,
S_PIX_LO_W = 4'd11,
S_FRAME_END = 4'd12,
S_NEXT_FRAME = 4'd13;
logic [3:0] state;
logic [6:0] init_idx;
logic [23:0] dly_cnt;
logic [23:0] frm_cnt;
logic [CNT_W-1:0] pix_cnt;
logic [8:0] col_cnt;
logic [7:0] row_cnt;
logic [15:0] cur_color;
logic [BUF_AW-1:0] rd_base;
// 読み出したサンプルから波形の Y 座標を求める
// 16bit 符号付き -> ゲイン (左シフト) を掛け、振幅 1 ビット = 1 px
// として中央 (Y_OFFSET) からの変位に変換する
wire signed [31:0] samp_off = $signed(rdata) <<< AMP_GAIN_SHIFT;
wire signed [31:0] y_raw = $signed({23'd0, Y_OFFSET_C}) - samp_off;
wire [7:0] y_wave = (y_raw < 32'sd0) ? 8'd0 :
(y_raw > $signed({24'd0, Y_MAXROW_C})) ? Y_MAXROW_C :
y_raw[7:0];
// 波形は 1 サンプル = 1 列で描画する。
// 隣接列とのつながりを良くするため WAVE_THICK px の太さで描く。
localparam int WAVE_THICK = 3; // 奇数 (1 = 1px 幅)
localparam int WAVE_HALF = WAVE_THICK / 2;
localparam logic [7:0] WAVE_HALF_C = WAVE_HALF;
wire [7:0] y_lo = (y_wave < WAVE_HALF_C) ? 8'd0
: (y_wave - WAVE_HALF_C);
wire [7:0] y_hi = ((y_wave + WAVE_HALF_C) > Y_MAXROW_C) ? Y_MAXROW_C
: (y_wave + WAVE_HALF_C);
// 現在の列を描画するサンプル位置
// rd_base は「最新サンプル + 1」の位置なので、1 サンプル戻してから
// LCD_W サンプル前を読み出す (最新の LCD_W サンプルを左→右に描画)
// col_cnt は 9bit 固定なので BUF_AW 幅へゼロ拡張する
wire [BUF_AW-1:0] col_off;
generate
if (BUF_AW <= 9) begin : g_col_off
assign col_off = col_cnt[BUF_AW-1:0];
end else begin : g_col_off
assign col_off = {{(BUF_AW-9){1'b0}}, col_cnt};
end
endgenerate
wire [BUF_AW-1:0] rd_idx = rd_base - 1'b1 - OFF_PREV + col_off;
assign rd_addr = rd_idx;
// ピクセル色 (S_PIX_RD で rdata が有効)
function automatic [15:0] pixel_color(
input [8:0] x,
input [7:0] y,
input [7:0] wy_lo,
input [7:0] wy_hi
);
if ((y >= wy_lo) && (y <= wy_hi)) pixel_color = COL_WAVE;
else if (y == Y_OFFSET_C[7:0]) pixel_color = COL_AXIS;
else if (x[4:0] == 5'd0) pixel_color = COL_GRID;
else if (y[4:0] == 5'd0) pixel_color = COL_GRID;
else pixel_color = COL_BG; endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_WAIT;
init_idx <= '0;
dly_cnt <= '0;
frm_cnt <= '0;
pix_cnt <= '0;
col_cnt <= '0;
row_cnt <= '0;
cur_color <= '0;
tx_start <= 1'b0;
tx_byte <= '0;
tx_dc_reg <= 1'b0;
rd_base <= '0;
init_done <= 1'b0;
frame_tick <= 1'b0;
end else begin
tx_start <= 1'b0;
frame_tick <= 1'b0;
case (state)
// ---- 電源 ON 待機 ----
S_WAIT: begin
if (init_start) begin
init_idx <= '0;
state <= S_INIT_TX;
end
end
// ---- 初期化コマンド/データ送信 ----
S_INIT_TX: begin
if (init_idx < ROM_DEPTH) begin
if (!tx_busy && !tx_start) begin
{tx_dc_reg, tx_byte} <= rom_read(init_idx);
tx_start <= 1'b1;
state <= S_INIT_WAIT;
end
end else begin
init_done <= 1'b1;
state <= S_FRAME_START;
end
end
S_INIT_WAIT: begin
if (!tx_busy && !tx_start) begin
case (tx_byte)
8'h01: begin dly_cnt <= '0; state <= S_SWRST_DLY; end
8'h11: begin dly_cnt <= '0; state <= S_SLPOUT_DLY; end
default: begin
init_idx <= init_idx + 7'd1;
state <= S_INIT_TX;
end
endcase
end
end
// SWRESET 後 5ms
S_SWRST_DLY: begin
if (dly_cnt == SWRST_CNT) begin
init_idx <= init_idx + 7'd1;
state <= S_INIT_TX;
end else begin
dly_cnt <= dly_cnt + 24'd1;
end
end
// SLPOUT 後 120ms
S_SLPOUT_DLY: begin
if (dly_cnt == SLPOUT_CNT) begin
init_idx <= init_idx + 7'd1;
state <= S_INIT_TX;
end else begin
dly_cnt <= dly_cnt + 24'd1;
end
end
// ---- フレーム開始 ----
// 書き込みポインタをラッチして描画範囲を固定する
S_FRAME_START: begin
rd_base <= wr_ptr;
pix_cnt <= '0;
col_cnt <= '0;
row_cnt <= '0;
state <= S_PIX_CALC;
end
// ---- 読み出しアドレス設定 ----
S_PIX_CALC: begin
state <= S_PIX_RD;
end
// ---- 波形 RAM 読み出し待ち (1 サイクル) ----
S_PIX_RD: begin
cur_color <= pixel_color(col_cnt, row_cnt, y_lo, y_hi);
state <= S_PIX_HI_TX;
end
// ---- 上位バイト送信 ----
S_PIX_HI_TX: begin
if (!tx_busy && !tx_start) begin
tx_byte <= cur_color[15:8];
tx_dc_reg <= 1'b1;
tx_start <= 1'b1;
state <= S_PIX_HI_W;
end
end
S_PIX_HI_W: begin
if (!tx_busy && !tx_start) state <= S_PIX_LO_TX;
end
// ---- 下位バイト送信 ----
S_PIX_LO_TX: begin
if (!tx_busy && !tx_start) begin
tx_byte <= cur_color[7:0];
tx_dc_reg <= 1'b1;
tx_start <= 1'b1;
state <= S_PIX_LO_W;
end
end
// ---- カウンタ更新 ----
S_PIX_LO_W: begin
if (!tx_busy && !tx_start) begin
if (col_cnt == LAST_COL) begin
col_cnt <= '0;
row_cnt <= row_cnt + 8'd1;
end else begin
col_cnt <= col_cnt + 9'd1;
end
if (pix_cnt == LAST_PIX) begin
frame_tick <= 1'b1;
frm_cnt <= '0;
state <= S_FRAME_END;
end else begin
pix_cnt <= pix_cnt + 1'b1;
state <= S_PIX_CALC;
end
end
end
// ---- フレーム間待機 ----
S_FRAME_END: begin
if (frm_cnt == FRAME_WAIT_C)
state <= S_NEXT_FRAME;
else
frm_cnt <= frm_cnt + 24'd1;
end
// ---- Memory Write 再発行 -> 次フレーム開始 ----
// ILI9341 は 0x2C を送るとアドレスポインタが先頭に戻る
S_NEXT_FRAME: begin
if (!tx_busy && !tx_start) begin
tx_byte <= 8'h2C;
tx_dc_reg <= 1'b0;
tx_start <= 1'b1;
state <= S_FRAME_START;
end
end
default: state <= S_WAIT;
endcase
end
end
endmodule広げる
・i2s_rx.sv
// ============================================================
// i2s_rx.sv
// PMOD-Microphone v1.0 (I2S MEMS マイク) 受信回路
//
// FPGA がマスタとなり SCK (ビットクロック) と
// WS (LRCLK / ワードセレクト) を生成し、
// SD から I2S フォーマットの音声データを取り込む。
//
// フォーマット : Philips I2S
// - WS は SCK の立ち下がりエッジで変化
// - データは MSB ファースト
// - WS 変化の 1 SCK 後に MSB が出力される
// - 受信側は SCK の立ち上がりエッジでサンプリング
// - 1 フレーム = 64 SCK (32bit x 2ch)
//
// サンプリング周波数 Fs = SCK_HZ / 64
// SCK_HZ = 1 MHz -> Fs = 15.625 kHz
//
// 出力 sample : 24bit データの上位 16bit (符号付き, 2 の補数)
// ============================================================
module i2s_rx #(
parameter int CLK_HZ = 50_000_000, // 入力クロック周波数
parameter int SCK_HZ = 1_000_000 // ビットクロック周波数
) (
input logic clk,
input logic rst_n, // 非同期リセット (Low 有効)
// --- PMOD-Microphone ---
output logic mic_sck, // ビットクロック (FPGA -> MIC)
output logic mic_ws, // ワードセレクト (FPGA -> MIC)
input logic mic_sd, // シリアルデータ (MIC -> FPGA)
// --- 取り込んだ音声データ ---
output logic signed [15:0] sample, // 24bit の上位 16bit
output logic sample_valid // 1 サンプル確定時の 1 クロックパルス
);
// --------------------------------------------------------
// ビットクロック生成
// div_cnt : 0 .. DIV-1
// SCK : div_cnt < HALF -> Low, それ以外 -> High
// --------------------------------------------------------
localparam int DIV = (CLK_HZ + SCK_HZ / 2) / SCK_HZ; // 50
localparam int HALF = DIV / 2; // 25
logic [15:0] div_cnt;
logic [5:0] bit_idx; // 0 .. 63 (フレーム内ビット位置)
logic ws_r;
logic [23:0] shift; // 受信シフトレジスタ
logic signed [15:0] sample_r;
logic valid_r;
wire div_wrap = (div_cnt == 16'(DIV - 1));
wire rise_tick = (div_cnt == 16'(HALF - 1)); // SCK 立ち上がり
wire fall_tick = div_wrap; // SCK 立ち下がり
wire [4:0] bit_pos = bit_idx[4:0]; // チャネル内 0..31
wire in_data = (bit_pos >= 5'd1) && (bit_pos <= 5'd24);
wire is_left = (ws_r == 1'b0);
wire [23:0] shift_next = {shift[22:0], mic_sd};
wire [5:0] bit_next = (bit_idx == 6'd63) ? 6'd0 : (bit_idx + 6'd1);
assign mic_sck = (div_cnt >= 16'(HALF));
assign mic_ws = ws_r;
assign sample = sample_r;
assign sample_valid = valid_r;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
div_cnt <= '0;
bit_idx <= '0;
ws_r <= 1'b0;
shift <= '0;
sample_r <= '0;
valid_r <= 1'b0;
end else begin
div_cnt <= div_wrap ? '0 : (div_cnt + 16'd1);
valid_r <= 1'b0;
// --- SCK 立ち下がり: ビット位置と WS を更新 ---
// 左ch = bit_idx 0..31, 右ch = bit_idx 32..63
if (fall_tick) begin
bit_idx <= bit_next;
ws_r <= (bit_next >= 6'd32);
end
// --- SCK 立ち上がり: データを取り込む ---
// bit_pos = 0 は I2S の 1 ビット遅延なので取り込まない
if (rise_tick && in_data) begin
shift <= shift_next;
// 左チャネル (= モノラルマイクのデータ) を採用
if ((bit_pos == 5'd24) && is_left) begin
sample_r <= shift_next[23:8]; // 24bit の上位 16bit
valid_r <= 1'b1;
end
end
end
end
endmodule広げる
・audio_buf.sv
// ============================================================
// audio_buf.sv
// 音声波形表示用 シングルクロック デュアルポート RAM
//
// 書き込み : i2s_rx が取り込んだサンプル (Fs = 15.625 kHz)
// 読み出し : lcd_wave の画面描画 (ランダムアクセス)
//
// 深さ 4096 サンプル (≈ 262 ms)
// LCD 1 フレームの描画には約 110 ms かかる。
// フレーム開始時に書き込みポインタをスナップショットして
// 描画中の領域が上書きされないようにするため、
// 深さは「1 フレーム中に進むサンプル数 (約 1720) +
// 表示サンプル数 (240)」より十分大きくとる。
// 4096 - 240 = 3856 サンプル (≈ 246 ms) > 110 ms
// ============================================================
module audio_buf #(
parameter int AW = 12, // アドレス幅 (2^12 = 4096)
parameter int DW = 16 // データ幅
) (
input logic clk,
// --- 書き込みポート ---
input logic we,
input logic signed [DW-1:0] wdata,
output logic [AW-1:0] wr_ptr,
// --- 読み出しポート ---
input logic [AW-1:0] rd_addr,
output logic signed [DW-1:0] rdata
);
localparam int DEPTH = 1 << AW;
logic signed [DW-1:0] mem [DEPTH];
logic [AW-1:0] wp;
always_ff @(posedge clk) begin
if (we) begin
mem[wp] <= wdata;
wp <= wp + 1'b1;
end
end
always_ff @(posedge clk) begin
rdata <= mem[rd_addr];
end
assign wr_ptr = wp;
endmodule広げる