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