Computer Laboratory

Bluepsec Examples

VGA and ROM Example

Overview

First VGA timing signals are generated for an Altera/Terasic DE2 board. Bluepsec interface produces a stream of screen pixel coordinates and it expects the result some number of cycles later (which is parameterisable). Note that the VGA timing parameters were probably not fully VGA complient since they were tweaked to improve stability on some old LCD monitors we have in our teaching lab.

There are two test modules: one test module produces a test patterns of squares and another module displays a font. The latter uses a font ROM loaded onto the FPGA using a memory initialization (MIF) file. The ROM is provided in Verilog is then wrapped in Bluespec.


VGA code

/*****************************************************************************
 VGA_Example
 ===========
 Simon Moore, Feb 2010
 
 Provides 3 modules:
  1. mkVGA_timing - provides physical interface (h-sync and v-sync, etc) on
                    one side and has a simpler interface create video

  2. mkVGA_test_pattern - simple test pattern generator
 
  3. mkVGA_test_font - output a bit mapped font
 
 Build produces two verilog files:
  1. mkVGA_test_pattern.v  - the test pattern
 
  2. mkVGA_test_font.v - the test font
 
 *****************************************************************************/

package VGA_Example;

import FIFO::*;
import FIFOF::*;
import FIFOLevel::*;
import GetPut::*;
import ClientServer::*;
import Vector::*;
import AlteraROM::*;

// DE2 board has 10-bits per pixel
typedef UInt#(10) VGA_colourT;

typedef Int#(11) VGA_coordinateT;
	      
typedef struct {
   VGA_coordinateT x;
   VGA_coordinateT y;
   } VGA_coordinatesT deriving (Bits);

typedef struct {
   VGA_colourT r;
   VGA_colourT g;
   VGA_colourT b;
   } VGA_pixelT deriving (Bits);

// physical interface to VGA video output
(* always_ready, always_enabled *)
interface VGA_output_Ifc;
   method Bool vga_hsync;
   method Bool vga_vsync;
   method VGA_colourT vga_R;
   method VGA_colourT vga_G;
   method VGA_colourT vga_B;
endinterface

// nested version of the above
interface Nested_VGA_output_Ifc;
   interface VGA_output_Ifc vga_out;
endinterface

// higher-level video interface
//  - 50MHz input clock with 25MHz pixel "clock"
//    - i.e. provide next pixel every other clock cycle
// methods:
//  - get the xy coordinate for next pixel (must supply within 8 cycles)
//     - provided every other clock cycle
//  - put the pixel (every other clock cycle)
//  - start_of_frame_reset - pulses high for 1 cycle at the beginning of each frame
//  - resolution - returnes current resolution e.g. (640x480)
interface VGA_timing_Ifc;
   interface Get#(VGA_coordinatesT) xy;
   interface Put#(VGA_pixelT) pixel;
   method Bool start_of_frame_reset;
   method VGA_coordinatesT resolution;
   interface VGA_output_Ifc vga_out;
endinterface


module mkVGA_timing(VGA_timing_Ifc);
   
   // number of clock cycles between issuing pixel coordinates and expecting a pixel colour back
   // N.B. it must be the case that earliness<(ha+hb)
   let earliness = 8;
   let earliness_fifo = earliness*2;

   Reg#(VGA_coordinatesT)  early_hv <- mkReg(unpack(0));
   Reg#(VGA_coordinatesT)  hv <- mkReg(unpack(0));
   Reg#(Bool)              early_hsync <- mkReg(False);
   Reg#(Bool)              early_vsync <- mkReg(False);
   Reg#(Bool)              early_visible_x <- mkReg(False);
   Reg#(Bool)              very_early_visible_x <- mkReg(False);
   Reg#(Bool)              early_visible_y <- mkReg(False);
   Reg#(Bool)              hsync <- mkReg(False);
   Reg#(Bool)              vsync <- mkReg(False);
   FIFO#(Bool)             start_of_frame_sig <- mkFIFO;
   PulseWire               start_of_frame_pulse <- mkPulseWire;
   Reg#(UInt#(1))          slow_down <- mkReg(0);// 50MHz input clock, 25MHz outputs
   Reg#(Bool)              enabled <- mkReg(False);
   FIFO#(VGA_pixelT)       pixel_fifo <- mkSizedFIFO(earliness);
   FIFO#(VGA_coordinatesT) xy_fifo <- mkSizedFIFO(earliness);
   Reg#(VGA_pixelT)        pixel_r <- mkReg(unpack(0));
   
   // timing for 640x480 VGA in 25MHz pixel clock units
   let ha = 96;   // H-sync
   let hb = 16;   // front porch
   let hc = 640;  // actual
   let hd = 48;   // back porch
   let hmax = ha+hb+hc+hd;
   
   let va = 2;    // V-sync
   let vb = 10;   // vertical blank top
   let vc = 480;  // actual
   let vd = 33;   // vertical blank bottom
   let vmax = va+vb+vc+vd;
   
   rule do_slow_down;
      slow_down <= slow_down+1;
      enable <= slow_down==0;
   endrule
	
   rule do_reset_fifo (!enabled);
      start_of_frame_sig.deq;
      pixel_fifo.clear;
      xy_fifo.clear;
   endrule

   rule do_timing_coordinates (enabled);
      VGA_coordinateT h=early_hv.x;
      VGA_coordinateT v=early_hv.y;
      h=h+1;
      if(h>=hmax)
	 begin
	    h=0;
	    v=v+1;
	    if(v>=vmax)
		 v=0;
	 end
      early_hv <= VGA_coordinatesT{x:h, y:v};
   endrule
   
   rule do_start_of_frame_sig (enabled && (early_hv.x==0) && (early_hv.y==0));
      start_of_frame_sig.enq(True);
      start_of_frame_pulse.send();
   endrule
   
   rule do_timing (enabled);
      hv <= early_hv;
      early_hsync <= early_hv.x>ha;
      early_vsync <= early_hv.y>va;
      early_visible_x <= (early_hv.x >= (ha + hb)) && (early_hv.x < (ha + hb + hc));
      early_visible_y <= (early_hv.y >= (va + vb)) && (early_hv.y < (va + vb + vc));
      very_early_visible_x <= (early_hv.x >= (ha + hb - earliness))
                           && (early_hv.x <  (ha + hb + hc - earliness));
   endrule
   
   rule pushd_coordinates_early (enabled);
      let x_early = hv.x-(ha+hb-earliness);
      let y_early = hv.y-(va+vb);
      if(very_early_visible_x && early_visible_y)
	 xy_fifo.enq(VGA_coordinatesT{x:x_early, y:y_early});
   endrule
   
   rule sync (enabled);
      hsync <= early_hsync;
      vsync <= early_vsync;
   endrule

   (* preempts="draw,draw_error" *)
   rule draw (enabled && early_visible_x && early_visible_y);
      pixel_r <= pixel_fifo.first;
      pixel_fifo.deq;
   endrule
   
   // should only fire if draw was supposed to be able to deq from pixel_fifo
   rule draw_error (enabled && early_visible_x && early_visible_y);
      pixel_r <= VGA_pixelT{r:10'h3ff, g:0, b:0};
   endrule

   rule blank (enabled && !(early_visible_x && early_visible_y));
      pixel_r <= VGA_pixelT{r:0, g:0, b:0};
   endrule
   
   interface Get xy = toGet(xy_fifo);
   interface Put pixel = toPut(pixel_fifo);
   interface VGA_output_Ifc vga_out;
      method vga_hsync; return hsync;     endmethod
      method vga_vsync; return vsync;     endmethod
      method vga_R;     return pixel_r.r; endmethod
      method vga_G;     return pixel_r.g; endmethod
      method vga_B;     return pixel_r.b; endmethod
   endinterface
   method start_of_frame_reset; return start_of_frame_pulse;         endmethod
   method resolution;           return VGA_coordinatesT{x:hc, y:vc}; endmethod
endmodule



(* synthesize *)
module mkVGA_test_pattern(Nested_VGA_output_Ifc);
   VGA_timing_Ifc vga_timing <- mkVGA_timing;
   let square_size = 16;

   rule do_display;
      let coord <- vga_timing.xy.get();
      let on_green = (coord.x/square_size) == (coord.y/square_size);
      let odd_x = (truncate(pack(coord.x/square_size)) & 1'b1);
      let odd_y = (truncate(pack(coord.y/square_size)) & 1'b1);
      let on_red = (odd_x == odd_y);
      let red = on_red ? 10'h3ff : 0;
      let green = on_green ? 10'h3ff : 0;
      let blue = 0;
      vga_timing.pixel.put(VGA_pixelT{r:red, g:green, b:blue});
   endrule
   
   interface vga_out = vga_timing.vga_out;
endmodule



(* synthesize *)
module mkVGA_test_font(Nested_VGA_output_Ifc);
   VGA_timing_Ifc vga_timing <- mkVGA_timing;
   Server#(UInt#(11),UInt#(8)) fontrom <- mkAlteraROMServer("fontrom.mif");
   Reg#(UInt#(11)) scroll <- mkReg(0);
   FIFO#(UInt#(3)) px <- mkFIFO;
   
   rule scroll_counting (vga_timing.start_of_frame_reset);
      scroll <= scroll+1;
   endrule
   
   rule look_up_font_info;
      VGA_coordinatesT coord <- vga_timing.xy.get();
      UInt#(11) x = extend(unpack(pack(coord.x)>>3));
      UInt#(11) y = extend(unpack(pack(coord.y)))+scroll;
      fontrom.request.put((x<<4)+y); // +((y>>4)<<4));
      px.enq(truncate(unpack(pack(coord.x))));
   endrule

   rule do_display;
      let p <- fontrom.response.get();
      Bit#(8) pixels = pack(p);
      UInt#(3) pixel_index = 7-px.first;
      px.deq;
      let on    = pixels[pixel_index]==1;
      let red   = 0;
      let green = on ? 10'h3ff : 0;
      let blue  = 0;
      vga_timing.pixel.put(VGA_pixelT{r:red, g:green, b:blue});
   endrule
   
   interface vga_out = vga_timing.vga_out;
endmodule


		       
endpackage

Link to the VGA_Example.bsv source

AlteraROM

This provides a wrapper to some Verilog code which creates a ROM based on a MIF file. I particularly like the way that the filename for the MIF file can be passed from Bluespec through to the Verilog.

package AlteraROM;

import GetPut::*;
import ClientServer::*;
import FIFO::*;

interface AlteraROM_Ifc#(type addrT, type dataT);
   method Action read_request(addrT addr);
   method dataT read_response;
endinterface


import "BVI" VerilogAlteraROM =
  module mkAlteraROM #(String filename) (AlteraROM_Ifc#(addrT, dataT))
     provisos(Bits#(addrT, addr_width),
	      Bits#(dataT, data_width));
     parameter FILENAME = filename;
     parameter ADDRESS_WIDTH = valueOf(addr_width);
     parameter DATA_WIDTH = valueof(data_width);
     method read_request (v_addr)
	enable (v_en);
     method v_data read_response;
	default_clock clk(clk, (*unused*) clk_gate);
	default_reset no_reset;
	schedule (read_response) SBR (read_request);
	schedule (read_response) C (read_response);
	schedule (read_request) C (read_request);
  endmodule


module mkAlteraROMServer#(String romfile)(Server#(UInt#(11),UInt#(8)));

   AlteraROM_Ifc#(UInt#(11),UInt#(8)) rom <- mkAlteraROM(romfile);
   FIFO#(Bool) seq_fifo <- mkLFIFO;
   
   interface Put request;
      method Action put(addr);
	 rom.read_request(addr);
	 seq_fifo.enq(True);
      endmethod
   endinterface
   interface Get response;
      method ActionValue#(UInt#(8)) get;
	 seq_fifo.deq;
	 let data = rom.read_response();
	 return data;
      endmethod
   endinterface
endmodule



endpackage

Link to the AlteraROM.bsv source

/*****************************************************************************
 Paramererised Verilog Altera ROM
 ================================
 Simon Moore
 
 Verilog stub for Altera's Quartus tools to provide a generic ROM interface 
 for AlteraROM.bsv
 *****************************************************************************/

module VerilogAlteraROM(clk, v_addr, v_data, v_en, v_rdy);

   parameter ADDRESS_WIDTH=11;
   parameter MEM_SIZE=(1<<ADDRESS_WIDTH);
   parameter DATA_WIDTH=8;
   parameter FILENAME="your_rom_data.mif";

   input                       clk;
   input [ADDRESS_WIDTH-1:0]   v_addr;
   output reg [DATA_WIDTH-1:0] v_data;
   input 					   v_en;
   output reg 				   v_rdy;

   (* ram_init_file = FILENAME *) reg [DATA_WIDTH-1:0] 	 rom [0:MEM_SIZE-1];

   always @(posedge clk) begin
	  v_rdy <= v_en;
	  if(v_en)
		v_data <= rom[v_addr];
   end

endmodule // Verilog_AlteraROM

Link to the VerilogAlteraROM.v source

Font ROM

The font ROM data is provided as a memory initialization file (a MIF file). This isn't particularly interesting, so it is not shown here, but can be downloaded: fontrom.mif