In this lab, we will learn how to display images on a VGA terminal using the DE1-SoC's video-out port and implement a line-drawing algorithm.
The DE1-SoC includes a video-out port with a VGA controller that can be connected to a standard VGA monitor. The VGA controller supports a screen resolution of 640 × 480. The image that is displayed by the VGA controller is derived from two sources: a pixel buffer and a character buffer, however, we will only discuss and use the pixel buffer in this lab.
The pixel buffer for the video-out port holds the data (color) for each pixel that is displayed by the VGA controller, with the coordinate (0,0) referring to the top-left corner of the image (Figure 1).
The color of a pixel is represented within a 16-bit halfword, with five bits for the red and blue components and six bits for the green component (Figure 2a). The address of an individual pixel in the pixel buffer is given by the sum of a base address and an (x,y) offset (represented as seen in Figure 2b).
In the DE1-SoC, the default base address of the pixel buffer is 0xC8000000, which corresponds to the starting address of the FPGA on-chip memory.
e.g., | pixel (0,0) has address | 0xC8000000 + 0b0…0 00000000 000000000 0 = 0xC8000000. | ||
pixel (1,0) has address | 0xC8000000 + 0b0…0 00000000 000000001 0 = 0xC8000002. | |||
pixel (0,1) has address | 0xC8000000 + 0b0…0 00000001 000000000 0 = 0xC8000400. | |||
pixel (319,239)'s addr | 0xC8000000 + 0b0…0 11101111 100111111 0 = 0xC803BE7E. |
You can create an image by writing color values into the pixel addresses as described above. A dedicated pixel buffer controller reads this pixel data from the memory and sends it to the VGA display. The controller reads the pixel data in sequential address order, starting with the pixel data corresponding to the upper-left corner of the VGA screen and proceeding horizontally, then vertically until it reaches the data for the lower-right corner; this process repeats continuously. You can modify the pixel data at any time by writing to the pixel addresses. Writes to the pixel buffer are automatically interleaved in the hardware with the read operations that are performed by the pixel buffer controller.
It is also possible to prepare a new image for the VGA display without changing the content of the pixel buffer using the concept of double-buffering. In this scheme, you switch between two pixel buffers, front and back, one of which is being sent to the VGA while the other is used to prepare the next frame. You don't need to worry about double-buffering for this lab, but may find it helpful in Lab 6.
Drawing a line on a screen requires coloring pixels between two points (x1,y1) and (x2,y2), such that the pixels represent the desired line as closely as possible. For example, if we want to draw a line between points (1,1) and (12,5), we cannot draw the line precisely – we can only draw a shape that is similar to the line by coloring the pixels that fall closest to the line's ideal location on the screen. This is illustrated in Figure 3, where the squares represent pixels on the screen.
We can use algebra to determine which pixels to color based on the slope and end points of the line. The slope of our example line is (y2-y1)/(x2-x1)=4/11. Starting at one endpoint (1,1), we move along the x-axis to the other endpoint and compute the y-position for each x-position as: y=y1+slope*(x-x1).
Thus, for x=2, which you can think of as a column of pixels, the y-position of the pixel for the line is 1+(4/11)*(2-1)=15/11. Since pixel locations are defined by integer values, we round this result to the nearest integer, and determine that we should color the pixel at (2,1). For x=3, we compute y=19/11 and color the pixel at (3,2). Similarly, we compute pixels to color through x=12=x2.
This approach (compute a y-position for every x-position) has drawbacks when a line is steep (i.e., |slope|>1), since it will span more rows than it does columns. In this case, our calculations will not produce a smooth-looking line and will not work at all for a vertical line. To address this problem, we can alter the algorithm to instead move along the y-axis when a line is steep. With this change, we can implement what is known as Bresenham's line algorithm; two different pseudocodes are given in Figure 4 and the first link below – either will work for this lab.
If you would like to learn more about how Bresenham's line algorithm works:
The first 15 lines of the algorithm make the needed adjustments
depending on whether or not the line is steep.
Lines 17 to 22 then increment the x
variable one step at
a time and computes the corresponding y-value.
The y-value is incremented when needed to stay as close to
the ideal location of the line as possible.
Bresenham's line algorithm calculates an error
variable
to decide whether or not to increment each y-value.
The version of the algorithm shown in Figure 4 uses only integers.
Some notes about the line_drawer.sv:
x0
, y0
, x1
,
y1
, corresponding to the coordinate pairs
(x0,y0) and (x1,y1).
clk
, the outputs x
and y
form a coordinate pair on the line between (x0,y0) and
(x1,y1).
On any given clock cycle, x
and y
should
each change by at most one pixel, since we're guaranteed to use
|slope|≤1.
logic
variables)
to keep track of things.
Notice that the example signal error
is declared as
signed
and is a bit longer than the coordinate ports.
Your task is to draw a line between any two arbitrary points on the monitor. However, Bresenham's line algorithm can get complicated and you need to handle drawing to the left, right, up, or down, and both steep and gradual lines. Instead of doing this all at once, you'll probably want to work in smaller steps. The following are suggestions on how to approach this problem, but you can complete this task in whatever way makes the most sense to you:
y0 = y1 > 0
(in case of monitor cut-off) and
use line_drawer.sv to draw horizontal lines –
i.e., implement the for-loop mechanics and
x0 > x1
conditional.
y_step
.
deltax
, deltay
, and
error
.
is_steep
.
While you won't be demoing this task, you will need to include simulation results for drawing left-up, right-up, left-down, and right-down for both steep and gradual slopes (8 total).
Modify DE1_SoC.sv to implement the following:
pixel_color
port
connection to the VGA_framebuffer
module to choose
between drawing black or white.
Note that you don't need to provide simulation results for your animation, but instead will include a drawing or image of what it produces.
Due by the end of Friday, submitted as a PDF on .
Due within one week of the lab report deadline, but typically during your assigned demo slot or a scheduled office hour.
line_drawer
module code to the TA.