Controlling VGA Memory with x86 Assembly Code
Controlling VGA Memory with x86 Assembly Code
A quick tutorial on a lesser used, but still effective, web development language, x86 Assembly Code.
Join the DZone community and get the full member experience.Join For Free
Learn how error monitoring with Sentry closes the gap between the product team and your customers. With Sentry, you can focus on what you do best: building and scaling software that makes your users’ lives better.
This article is intended to make you familiar with controlling VGA with x86 assembly. Yes, we don’t hear much in the field of Assembly Language programming, as it is not quite as popular as compared to other programming and web development languages such as Java, HTML, Bootstrap, or SEM. But it is essential on its own and knowledge of it proves to be quite helpful in understanding how computers work internally. Of course, you need to be familiar with x86 assembly beforehand which includes knowing the instruction set as well as concepts of registers, operations, etc.
The setup that you will need includes installing ‘dosbox’ as well as ‘EMU8086’ on your machine. Install both of them. EMU8086 is intended to compile our source code into machine code, and then we will run the compiled code inside the environment that Dosbox will provide. After you’ve installed them, open up EMU8086, create a new ‘.COM’ file, and then paste the following code inside the editor:
org 100 h .data temp dw 80 dup(0); declares an array of 80 words and initializes them with 0 .code mov ax, 0xb800 mov es, ax; es register now points towards video memory here: mov si, 0 mov di, 0 mov cx, 80; counter for copying first line of display ; copies the first line of the display to a temp array loop1: mov ax, es: si; character from video memory to ax register mov[temp + di], ax; copying that character to temp array add si, 2; incrementing the source and destination registers add di, 2 loop loop1 mov cx, 3840; counter for shifting the remaining rows to top mov si, 0; destination => first box mov di, 160; source => box right below it ; copies each digit from source to the memory cell above it loop2: mov ax, es: di mov es: si, ax add si, 2 add di, 2 loop loop2 mov di, 0 mov si, 3840 mov cx, 80 ; pasting the data of the top row at bottom loop3: mov ax, [temp + di] mov es: si, ax add si, 2 add di, 2 loop loop3 jmp here
It might feel a little bit overwhelming, but you’ll understand it by the end of the article. Actually, you can run this code inside EMU8086, but the running of this program will be very tedious and there will be no content on the screen to scroll.
1): The screen is 80x25 => 80 columns and 25 rows.
2): Store the first row data in a temporary array.
3): Shift the data from the remaining rows to the above rows.
4): Paste the data stored in the temp array at the end row.
5): Use an unconditional jump to go back and do the same process again.
The VGA screen is
80 (columns) x 25 (rows) . And each memory cell is in
(2-bytes) size. So we have a total of 2000 memory cells. And in terms of size, we have 4000 bytes. So the first location is at ‘0’, the last memory cell of the first row is at
‘158’ (80x2) - 2 ‘ because we start with 0. The bottom left memory cell is at ‘3840’ and the bottom right memory cell is at ‘3998.’
temp dw 80 dup(0); declares an array of 80 words and initializes them with 0.
This is the temporary array declaration inside the data segment. This will declare 80-word size memory locations inside the memory and put ‘0’ in them.
mov ax, 0xb800 mov es, ax
The VGA memory is at the address:
‘0xB800’. So we put that address inside the 'ax' register and then we move that address to ‘es’ (extended segment) register. Keep in mind that we cannot copy the address directly into the ‘es’ register.
here: mov si, 0 mov di, 0 mov cx, 80 loop1: mov ax, es:si mov [temp + di], ax add si, 2 add di, 2 loop loop1
‘0’ to SI and DI registers and initialize our counter register cx with ‘80,' because we want to extract only the first row of the temp array.
‘mov ax, es:si’ extracts the character and puts in the ax register and ‘mov [temp + di], ax’ puts that character from 'ax' into the temp array. And we increment the SI and DI register by ‘2’ (word size) to point to the next consecutive memory location. And the loop starts again until it reaches its limit, i.e. cx is equal to 0.
mov cx, 3840 ; counter for shifting the remaining rows to top mov si, 0 ; destination => first box mov di, 160 ; source => box right below it ; copies each digit from source to the memory cell above it loop2: mov ax, es:di mov es:si, ax add si, 2 add di, 2 loop loop2
Now we have the first row in the temp array, and we are left with 24 rows.
80x24 = 1920 (1920x2=3840) So we have 3840 in our 'cx' register now for shifting the other rows to the top one by one. SI initially points to the first memory cell, and DI points to the memory cell directly below it that reads ‘160.’ We copy the contents of the memory cell pointed by the ‘DI’ register and paste them into the SI memory cell directly above it. And then we increment both by 2. This way we will copy the content of each memory cell to the memory cell above it until we reach the last memory cell, and that is where our loop will terminate.
Now, we have to paste the data from our temp array to the last row.
mov di, 0 mov si, 3840 mov cx, 80 ; pasting the data of the top row at bottom loop3: mov ax, [temp + di] mov es: si, ax add si, 2 add di, 2 loop loop3 jmp here
This code will do it. DI points to the first memory cell in the temp array. And SI points to the bottom left memory cell. The counter is again at 80 (the number of memory cells in a Row) and we start pasting data from temp to the VGA memory and incrementing both pointers SI and DI by 2.
Up to Now, We’ve:
1): Copied the first row to a temporary location.
2): Shifted the rest of the rows up.
3): Pasted the data from a temporary array to the last row.
That’s when the ‘jmp here’ comes into play. This fellow will jump back to the ‘here’ label inside the code and the process will start again. Copying the new top line to a top array and then shifting all the rows up, and then finally passing the data to the last row. As ‘jmp here’ is an unconditional jump so the process is infinite. And you will see the screen scrolling in DosBox.
1): Paste the code in EMU8086. Compile it, which will give us a COM file.
2): Then open DosBox. And mount the drive with the path where your.com file resides. Eg, ‘mount c: c:\emu8086\mybuild\”
3): Shift to the newly mounted drive by entering this ‘c:’
4): Run the .com file by entering its name. Eg, ‘noname.com’
‘Ctrl + F11’ the cycles in the title bar become
‘1’ . That way the program execution will drastically slow down and you will notice what is happening in the execution.
Opinions expressed by DZone contributors are their own.