Chisel Learning (VGA Output)



Version: 2024/10/07

Table of Contents:

  1. Prerequisites
  2. Display Output Timing
  3. Directory Structure
  4. VGA Output Logic
  5. Testing
  6. Creating Top (vga_top.v)
  7. Creating Conversion Script (arty_a7.tcl)
  8. Generating RGB Data

1. Prerequisites

Prerequisites for this document

Caravel

2. Display Output Timing

Display Control
  • • Controlled by two signals: HSYNC and VSYNC
  • • Timing for 1920 x 1080 (1080P)

Dot clock frequency: 2,200 × 1,125 × 60 [Hz] = 148.5 [MHz]

Caravel

3. Directory Structure

Refer to Chisel examples for structure

Caravel

4. VGA Output Logic

Interface
  • • Clock and reset are not defined
  • • Color is 4 bits (as per Pmod VGA specification)
Signals Count Content
hSync 1 Horizontal sync signal
vSync 1 Vertical sync signal
colorR 4 Red color
colorG 4 Green color
colorB 4 Blue color
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 Generation
  • • 12-bit counter in the horizontal direction (0 to HDISP + HFP + HSYNC + HBP - 1)
  • • Timing for HSYNC assertion
    • Start: HDISP + HFP (Counter value HDISP + HFP - 1)
    • End: HDISP + HFP + HSYNC (Counter value HDISP + HFP + HSYNC - 1)
  • • Set small values for testing (to be changed after practical confirmation)
/* Horizontal */ val hDispWidth = 10.U(12.W)     /* Set small values for testing */ 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 Generation
  • • 12-bit counter in the vertical direction (0 to VDISP + VFP + VSYNC + VBP - 1)
    • • Counter is updated simultaneously with HSYNC assertion (once horizontally)
  • • Timing for VSYNC assertion
    • Start: VDISP + VFP (Counter value VDISP + VFP - 1)
    • End: VDISP + VFP + VSYNC (Counter value VDISP + VFP + VSYNC - 1)
  • • Set small values for testing (to be changed after practical confirmation)
/* Vertical parameters */ val vDispHeight = 6.U(12.W)     /* Set small values for testing */ 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)) {     /* Counter update timing is the same as when HSYNC output starts */ 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 Data
  • • Temporarily set fixed values (0 output)
/* Other signals */ io.colorR := 0.U io.colorG := 0.U io.colorB := 0.U

5. Testing

Testing Policy
  • • Confirmation Content
    • • HSYNC output timing adheres to design
    • • VSYNC output timing adheres to design
  • • Acquire and display data for one frame
Confirmation Image

Caravel


Test Code (first half: until testing is executed)
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: Set small values for testing, Match with implementation. It should be possible to make it smarter using traits
  • Green: For HSYNC falling edge detection
  • Orange: Get HSYNC and VSYNC Perform every clock. Perform every clock
  • Dark green: Get VSYNC value at HSYNC falling edge

Test Code (second half: output results)
/* 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 output every clock. VSYNC output only at HSYNC falling edge
  • Green: VSYNC output every clock

Test Execution Results
$ 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: As per HSYNC design
  • Green: As per VSYNC design

6. Creating Top (vga_top.v)

Created with reference to hello_top.v
  • • Align with the XDC file
/* * 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. Creating Conversion Script (arty_a7.tcl)

Revised hello_world's script (red text portion)
# Add Sources read_verilog {../../PmodVga.v} read_verilog {../../verilog/vga_top.v} # 1. Change Verilog file name # 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. Change clock frequency 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. Change top-level module name

8. Generating RGB Data

Display vertical stripes
  • • Display vertically in the order of RGB, White
  • • Select color using bits 5:4 of the horizontal counter, output bits 3:0 as is
/* 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


Porting Digilent's VHDL demo (BOX part is not implemented)
/* 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


Add 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)

Modified to enable the red BOX to move
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