Chisel 学習(VGA出力)



バージョン:2024/10/07

目次:

  1. 前提条件
  2. ディスプレイ出力タイミング
  3. ディレクトリ構成
  4. VGA出力ロジック
  5. テスト
  6. トップ(vga_top.v)の作成
  7. 変換スクリプト(arty_a7.tcl)の作成
  8. RGBデータの生成

1. 前提条件

本資料の前提条件

Caravel

2. ディスプレイ出力タイミング

ディスプレイ制御
  • • HSYNC, VSYNCの2つの信号により制御
  • • 1920 x 1080 (1080P)のタイミング

ドットクロック周波数 2,200 × 1,125 × 60 [Hz] = 148.5 [MHz]

Caravel

3. ディレクトリ構成

Chisel examples を参考に構成

Caravel

4. VGA出力ロジック

インタフェース
  • • クロック、リセットは定義しない
  • • カラーは4ビット(Pmod VGAの仕様に合わせた)
信号 本数 内容
hSync 1 水平同期信号
vSync 1 垂直同期信号
colorR 4 赤色
colorG 4 緑色
colorB 4 青色
class PmodVga extends Module { val io = IO(new Bundle { val hSync = Output(UInt(1.W)) val vSync = Output(UInt(1.W)) val colorR = Output(UInt(4.W)) val colorG = Output(UInt(4.W)) val colorB = Output(UInt(4.W)) })

HSYNCの生成
  • • 水平方向の12ビット・カウンタ(0~HDISP+HFP+HSYNC+HBP-1)
  • • HSYNCのアサートタイミング
    • 開始: HDISP+HFP (カウンタ値 HDISP+HFP-1)
    • 終了: HDISP+HFP+HSYNC (カウンタ値 HDISP+HFP+HSYNC-1)
  • • テストのため、小さい数値に設定(実機確認で変更)
/* Horizontal */ val hDispWidth = 10.U(12.W)     /* テスト用に小さい数値で設定 */ val hFrontPorch = 3.U(12.W) val hSyncWidth = 3.U(12.W) val hTotalWidth = 20.U(12.W) val hCountReg = RegInit(0.U(12.W)) hCountReg := Mux(hCountReg < (hTotalWidth - 1.U), hCountReg + 1.U, 0.U) val hSyncReg = RegInit(1.U(1.W)) hSyncReg := Mux(hCountReg >= (hDispWidth + hFrontPorch - 1.U) && hCountReg < (hDispWidth + hFrontPorch + hSyncWidth - 1.U), 0.U, 1.U) io.hSync := hSyncReg

VSYNCの生成
  • • 垂直向の12ビット・カウンタ(0~VDISP+VFP+VSYNC+VBP-1)
    • • カウンタは、HSYNCのアサートと同時に更新(水平に1回)
  • • VSYNCのアサートタイミング
    • 開始: VDISP+VFP (カウンタ値 VDISP+VFP-1)
    • 終了: VDISP+VFP+VSYNC (カウンタ値 VDISP+VFP+VSYNC-1)
  • • テストのため、小さい数値に設定(実機確認で変更)
/* Vertical parameters */ val vDispHeight = 6.U(12.W)     /* テスト用に小さい数値で設定 */ val vFrontPorch = 1.U(12.W) val vSyncWidth = 2.U(12.W) val vTotalHeight = 10.U(12.W) val vCountReg = RegInit(0.U(12.W)) val vSyncReg = RegInit(1.U(1.W)) when (hCountReg === (hDispWidth + hFrontPorch - 1.U)) {     /* カウンタの更新タイミングはHSYNC出力開始時と同じ */ vCountReg := Mux(vCountReg < (vTotalHeight - 1.U), vCountReg + 1.U, 0.U) vSyncReg := Mux(vCountReg >= (vDispHeight + vFrontPorch - 1.U) && vCountReg < (vDispHeight + vFrontPorch + vSyncWidth - 1.U), 0.U, 1.U) } io.vSync := vSyncReg

RGBデータ
  • • とりあえず固定値(0出力)
/* Other signals */ io.colorR := 0.U io.colorG := 0.U io.colorB := 0.U

5. テスト

テストの方針
  • • 確認内容
    • • HSYNCの出力タイミングが設計通りであること
    • • VSYNCの出力タイミングが設計通りであること
  • • 1フレーム分のデータを取得して表示
確認イメージ

Caravel


テストコード(前半:テスト実施まで)
import chiseltest._ import org.scalatest.flatspec.AnyFlatSpec class PmodVgaTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "PmodVga" it should "pass" in { test(new PmodVga) { c => c.clock.setTimeout(0) val hTotal = 20 val vTotal = 10 val frameH = Array.ofDim[BigInt](vTotal, hTotal) val frameV = Array.ofDim[BigInt](vTotal, hTotal) val lineV = Array.ofDim[BigInt](vTotal) var hSyncPrev = BigInt(-1) /* Test */ for (y <- 0 until vTotal) { for (x <- 0 until hTotal) { c.clock.step(1) val hSyncNow = c.io.hSync.peek().litValue val vSyncNow = c.io.vSync.peek().litValue frameH(y)(x) = hSyncNow frameV(y)(x) = vSyncNow if ((hSyncPrev === 1) && (hSyncNow === 0)) { lineV(y) = vSyncNow } hSyncPrev = hSyncNow } }
  • Yellow: テスト用に小さい数値で設定実装と合わせる. トレイトを使ってスマートにできるはず
  • Green: HSYNC立下り検出用
  • Orange: HSYNCとVSYNCの取得毎クロック行う
  • Dark green: HSYNC立下り時にVSYNCの値を取得

テストコード(後半:結果出力)
/* Print */ println("HSYNC frame") for (y <- 0 until vTotal) { print(lineV(y)) print(": ") for (x <- 0 until hTotal) { print(frameH(y)(x)) } print("¥n") } /* Print vSync frame */ println("VSYNC frame") for (y <- 0 until vTotal) { for (x <- 0 until hTotal) { print(frameV(y)(x)) } print("¥n") } println("¥nEnd of test") } } }
  • Yellow: HSYNCを毎クロック出力 VSYNCはHSYNC立下り時のみ出力
  • Green: VSYNCを毎クロック出力

テスト実施結果
$ sbt test [info] welcome to sbt 1.10.2 (Ubuntu Java 17.0.12) [info] loading project definition from /home/masatakak/work/chisel/pmod-vga/project [info] loading settings for project pmod-vga from build.sbt ... [info] set current project to pmod-vga (in build file:/home/masatakak/work/chisel/pmod-vga/) [info] compiling 1 Scala source to /home/masatakak/work/chisel/pmod-vga/target/scala-2.12/classes ... [info] compiling 1 Scala source to /home/masatakak/work/chisel/pmod-vga/target/scala-2.12/test-classes ... start the Pmod VGA HSYNC frame 1: 11111111111100011111 1: 11111111111100011111 1: 11111111111100011111 1: 11111111111100011111 1: 11111111111100011111 1: 11111111111100011111 0: 11111111111100011111 0: 11111111111100011111 1: 11111111111100011111 1: 11111111111100011111 VSYNC frame 11111111111111111111 11111111111111111111 11111111111111111111 11111111111111111111 11111111111111111111 11111111111111111111 11111111111100000000 00000000000000000000 00000000000011111111 11111111111111111111 End of test
  • Yellow: HSYNC設計通り
  • Green: VSYNC設計通り

6. トップ(vga_top.v)の作成

hello_top.v を参考に作成
  • • XDCファイルに合わせる
/* * vga_top.v */ module vga_top(input CLK_I, output [3:0] VGA_R, output [3:0] VGA_G, output [3:0] VGA_B, output VGA_HS_O, output VGA_VS_O); wire res; wire clk_vga; /* VGA clock */ wire locked; /* PLL lock status (not used) */ assign res = 1'h0; clk_wiz_0 pll(.clk_out1(clk_vga), .reset(res), .clk_in1(CLK_I), .locked(locked)); PmodVga vgactl(.clock(clk_vga), .reset(res), .io_hSync(VGA_HS_O), .io_vSync(VGA_VS_O), .io_colorR(VGA_R), .io_colorG(VGA_G), .io_colorB(VGA_B)); endmodule

7. 変換スクリプト(arty_a7.tcl)の作成

hello_worldのスクリプトを修正(赤字部分)
# Add Sources read_verilog {../../PmodVga.v} read_verilog {../../verilog/vga_top.v} # 1. Verilogファイル名の変更 # Add IPs create_ip -name clk_wiz -vendor xilinx.com -library ip -module_name clk_wiz_0 set_property -dict [list CONFIG.PRIM_IN_FREQ {100.00} ¥ CONFIG.CLKOUT1_REQUESTED_OUT_FREQ {148.500}] [get_ips clk_wiz_0] generate_target {all} [get_ips clk_wiz_0]+ # 2. クロック周波数変更 read_verilog {./arty_a7.gen/sources_1/ip/clk_wiz_0/clk_wiz_0.v} read_verilog {./arty_a7.gen/sources_1/ip/clk_wiz_0/clk_wiz_0_clk_wiz.v} # Add constraints read_xdc Arty-A7-100-Master.xdc set_property PROCESSING_ORDER EARLY [get_files Arty-A7-100-Master.xdc] # Add pre-synthesis commands # Synthesis synth_design -directive default -top vga_top -part xc7a100tcsg324-1 # 3. トップレベルのモジュール名変更

8. RGBデータの生成

縦じまの表示
  • • RGB、Whiteの順番で縦に表示
  • • 水平カウンタのbit5:4で色を選択、bit3:0をそのまま出力
/* RGB signals */ val active = Mux((hCountReg < hDispWidth) && (vCountReg < vDispHeight), 1.U, 0.U) val colSelect = hCountReg(5, 4) val colValue = hCountReg(3, 0) when (active === 1.U) { io.colorR := Mux(colSelect === 0.U || colSelect === 3.U, colValue, 0.U) io.colorG := Mux(colSelect === 1.U || colSelect === 3.U, colValue, 0.U) io.colorB := Mux(colSelect === 2.U || colSelect === 3.U, colValue, 0.U) } .otherwise { io.colorR := 0.U io.colorG := 0.U io.colorB := 0.U }

Caravel


DigilentのVHDLデモを移植(Box部分は未実装)
/* RGB signals */ val active = Mux((hCountReg < hDispWidth) && (vCountReg < vDispHeight), 1.U, 0.U) when (active === 1.U) { io.colorR := Mux(hCountReg < 512.U && vCountReg < 256.U && hCountReg(8) === 1.U, hCountReg(5, 2), Mux(hCountReg < 512.U && vCountReg < 256.U && pixelInBox === 0.U, 15.U, Mux((hCountReg >= 512.U && vCountReg(8) === 1.U && hCountReg(3) === 1.U) || (hCountReg >= 512.U && vCountReg(8) === 0.U && vCountReg(3) === 1.U), 15.U, 0.U))) io.colorB := Mux(hCountReg < 512.U && vCountReg < 256.U && hCountReg(6) === 1.U, hCountReg(5, 2), Mux(hCountReg < 512.U && vCountReg < 256.U && pixelInBox === 0.U, 15.U, Mux((hCountReg >= 512.U && vCountReg(8) === 1.U && hCountReg(3) === 1.U) || (hCountReg >= 512.U && vCountReg(8) === 0.U && vCountReg(3) === 1.U), 15.U, 0.U))) io.colorG := Mux(hCountReg < 512.U && vCountReg < 256.U && hCountReg(7) === 1.U, hCountReg(5, 2), Mux(hCountReg < 512.U && vCountReg < 256.U && pixelInBox === 0.U, 15.U, Mux((hCountReg >= 512.U && vCountReg(8) === 1.U && hCountReg(3) === 1.U) || (hCountReg >= 512.U && vCountReg(8) === 0.U && vCountReg(3) === 1.U), 15.U, 0.U))) } .otherwise { io.colorR := 0.U io.colorG := 0.U io.colorB := 0.U }

Caravel


BOXを追加
/* Box */ val boxWidth = 8.U val boxXMax = 512.U - boxWidth val boxYMax = vDispHeight - boxWidth val boxXMin = 0.U val boxYMin = 256.U val boxXInit = 0.U(12.W) val boxYInit = 400.U(12.W) val boxClockDiv = 1000000.U val boxCountReg = RegInit(0.U(25.W)) boxCountReg := Mux(boxCountReg === boxClockDiv - 1.U, 0.U, boxCountReg + 1.U) val updateBox = Mux(boxCountReg === boxClockDiv - 1.U, 1.U, 0.U) val boxXReg = RegInit(0.U(12.W)) val boxYReg = RegInit(0.U(12.W)) val boxXDirReg = RegInit(1.U(1.W)) val boxYDirReg = RegInit(1.U(1.W)) when (updateBox === 1.U) { boxXReg := Mux(boxXDirReg === 1.U, boxXReg + 1.U, boxXReg - 1.U) boxYReg := Mux(boxYDirReg === 1.U, boxYReg + 1.U, boxYReg - 1.U) boxXDirReg := Mux(boxXDirReg === 1.U && boxXReg === (boxXMax - 1.U), 0.U, Mux(boxXDirReg === 0.U && boxXReg === (boxXMin + 1.U), 1.U, boxXDirReg)) boxYDirReg := Mux(boxYDirReg === 1.U && boxYReg === (boxYMax - 1.U), 0.U, Mux(boxYDirReg === 0.U && boxYReg === (boxYMin + 1.U), 1.U, boxYDirReg)) } val pixelInBox = Mux(hCountReg >= boxXReg && hCountReg < (boxXReg + boxWidth) && vCountReg >= boxYReg && vCountReg < (boxYReg + boxWidth), 1.U, 0.U)

赤いBOXが移動するように変更
when (active === 1.U) { io.colorR := Mux(pixelInBox === 1.U, 15.U, Mux(hCountReg < 512.U && vCountReg < 256.U && hCountReg(8) === 1.U, hCountReg(5, 2), Mux(hCountReg < 512.U && vCountReg < 256.U && pixelInBox === 0.U, 15.U, Mux((hCountReg >= 512.U && vCountReg(8) === 1.U && hCountReg(3) === 1.U) || (hCountReg >= 512.U && vCountReg(8) === 0.U && vCountReg(3) === 1.U), 15.U, 0.U))))

Caravel