[Norwegian] VGA Kontroller
Forord
Dette er utsnitt fra en proskjektoppgave jeg gjorde i det andre FPGA faget jeg hadde I løpet av Bacheloren min.
Introduksjon
Denne bloggen går ut på å implementere en kommunikasjonsprotokoll i fastvare, og binde det til en NIOS II CPU, ved hjelp av DE2-115 FPGA utviklerkortet.
VGA protokollen
Video Graphics Array (VGA) is a video display controller and accompanying de facto graphics standard, first introduced with the IBM PS/2 line of computers in 1987, which became ubiquitous in the IBM PC compatible industry within three years. The term can now refer to the computer display standard, the 15-pin D-subminiature VGA connector, or the 640x480 resolution characteristic of the VGA hardware.
VGA har lenge vært brukt i industrien. Det er en analog standard som er relativt timing kritisk. Videoprotokollere krever at du sender informasjon hele tiden, dette betyr at å sende hver byte fra CPU vil bruke nesten 100% av utførelsestiden. I tillegg vil timing sannsynligvis være et problem ved enkelte oppløsninger og oppfriskningshastigheter. Å ha diskret fastvarekomponenter som står mellom CPU og VGA er derfor nødvendig.
Trinnene som skal beseires
- Forståelse av VGA-protokollen. Hvordan fungerer den?
- Implementasjon av protokollen i VHDL. Kan vi få en skjerm til å vise en enkel farge?
- Tyngre testing. Kan vi lage et avansert mønster som flytter seg?
- Simple Buss Interface. Kan vi tegne fra NIOS II, har vi nok VRAM?
- Text-mode i fastvare. Hvordan ble minne og hastighet løst før i tiden?
Forståelse av VGA
Mesteparten av ledningene i VGA kontakten er GND eller ikke nødvendig til grunnleggende bruk. I DE2-115 kortet er bare RGB, horisontal sync og vertikal sync koblet. Siden R, G, og B er analoge signaler, krever vi en DAC for å omgjøre signalene.
Figur 1. DE2-115 VGA schematic
På DE2-115 kortet så brukes det en 3 kanals 10-bit DAC som er spesielt laget for høy-hastighet videosignaler. Bare 8 av de 10 mulige bits-ene blir faktisk brukt, som vi kan se i skjemategningen. De to nederste bits-ene er alltid 0, så den minste ikke-null lysstyrken våres er faktisk høyere enn det en kanskje hadde forventet.
I VHDL må vi bare sette opp en std_logic_vector(7 downto 0)
for hver av disse signalene, og så legge inn et 8-bit tall på hver av dem.
ADV7123 chip-en som er på kortet har og koblet noen signaler for klokke, sync og blank. Klokke signalet kan vi bare koble til samme klokke vi bruker til å styre RGB-signalene. Sync og blank har vi ikke bruk for. Så blank kan vi bare holde høy, og sync kan vi holde lav.
Figur 2. ADV7123 block diagram.
Å bare sende farger er selvfølgelig ikke nok informasjon for skjermen å vite hvilke oppløsning og oppfriskningshastighet som brukes. I tillegg krever skjermen informasjon om hvilken kolonne og rad vi faktisk ønsker å tegne til. I VGA sendes dette gjennom h_sync
og v_sync
signalene. Disse signalene aktiveres under det som kalles “blanking period”. Altså, vi tegner ikke til skjermen 100% av tiden.
Figur 3. VGA timing diagram
Timingen består da av 4 perioder for hver kolonne, og likeledes 4 perioder for radene samlet. Den enkleste måten å finne hvor lenge hver av disse periodene skal være, er å bla de opp i en tabell.
Figur 4. VGA timing tabell.
Som vi kan se her er det en tredje parameter vi ikke har tatt i betenkning enda, “Polarity”. Alt polarity gjør er å invertere h_sync
og v_sync
signalene.
Med all informasjonen våres samlet, så kan vi formalisere logikken litt mer.
pseudo-kode for VGA timing:
let h_sync = 1;
let v_sync = 1;
const total_v_time = height+v_front_porch+v_sync_pulse+v_back_porch;
const total_h_time = width+h_front_porch+h_sync_pulse+h_back_porch;
const total_time = total_v_time * total_h_time;
let h_counter = 0;
let v_counter = 0;
while True do
if h_counter >= total_h_time-1 then
h_counter = 0;
if v_counter >= total_v_time-1 then
v_counter = 0;
else
v_counter++;
end;
else
h_counter++;
end;
if h_counter >= width+h_front_porch and
h_counter < width+h_front_porch+h_sync_pulse then
h_sync = h_polarity;
else
h_sync = not h_polarity;
end;
if v_counter >= height+v_front_porch and
v_counter < height+v_front_porch+v_sync_pulse then
v_sync = v_polarity;
else
v_sync = not v_polarity;
end;
wait 1;
end;
Så langt er det rett fram, la oss gå over til VHDL.
Implementasjon i VHDL
vga_process : process (pixel_clock) is
variable h_counter : integer range 0 to h_period - 1 := 0;
variable v_counter : integer range 0 to v_period - 1 := 0;
begin
if rising_edge(pixel_clock) then
if nreset = '0' then
h_counter := 0;
v_counter := 0;
draw_time <= '0';
pan <= 0;
vga_hs <= not h_polarity;
vga_vs <= not v_polarity;
else
-- inkrementer tellere, først horisontal scanlinje, så neste linje
if h_counter = h_period - 1 then
h_counter := 0;
if v_counter = v_period - 1 then
v_counter := 0;
if pan = pan_max then
pan <= 0;
else
pan <= pan + 1;
end if;
else
v_counter := v_counter + 1;
end if;
else
h_counter := h_counter + 1;
end if;
-- hold vsync/hsync low/high dersom vi er i sync området
if h_counter < h_pixels + h_front_porch or
h_counter >= h_pixels + h_front_porch + h_sync_pulse then
vga_hs <= not h_polarity;
else
vga_hs <= h_polarity;
end if;
if v_counter < v_pixels + v_front_porch or
v_counter >= v_pixels + v_front_porch + v_sync_pulse then
vga_vs <= not v_polarity;
else
vga_vs <= v_polarity;
end if;
if h_counter < h_pixels then
x <= h_counter;
else
x <= 0;
end if;
if v_counter < v_pixels then
y <= v_counter;
else
y <= 0;
end if;
if h_counter < h_pixels and v_counter < v_pixels then
draw_time <= '1';
else
draw_time <= '0';
end if;
end if;
end if;
end process;
Koden handler stort sett bare om noen tellere, og å skru på draw_time, h_sync og v_sync i de riktige periodene. Koden bruker konstanter/generics, så det er enkelt å endre på timing. Men så langt er det ikke planlagt å ha mulighet for å endre timing dynamisk fra NIOS II.
Figur 5. Skjerm koblet til DE2-115 kort.
Figur 6. Skjerm med figurer som endrer seg
if ((x + pan / 1) / 32) mod 2 = 0 and ((y + pan / 1) / 32) mod 2 = 0 then
vga_r <= (others => '1');
vga_g <= (others => '0');
vga_b <= (others => '1');
else
vga_r <= (others => '0');
vga_g <= (others => '1');
vga_b <= (others => '1');
end if;
Tilkobling av RAM modul
Første steget her er å flytte ut informasjon om x, y, og draw_time utenfor vga-modulen. Deretter skriver vi en enkel test for å se at det fortsatt fungerer
draw : process(CLOCK_25) is
begin
if rising_edge(CLOCK_25) then
if draw_time = '1' then
VGA_R <= (others => '1');
VGA_G <= (others => '1');
VGA_B <= (others => '1');
if x < 100 then
VGA_R <= (others => '1');
VGA_G <= (others => '0');
VGA_B <= (others => '1');
end if;
else
VGA_R <= (others => '0');
VGA_G <= (others => '0');
VGA_B <= (others => '0');
end if;
end if;
end process;
Dette gir lilla kolonne, og resten hvit, slik som forventet. Alt fungerer fortsatt.
Vi legger så til en ram modul gjennom IP-katalogen, vi trenger en 2-port ram, med egen klokke for read/write.
For å støtte hele fargespekteret, trenger vi 8(bits) * 3(farger) * 640(bredde) * 480(høyde) altså 7372800 bits. Vi velger en ord-størrelse på 24-bit, slik at vi kan lese alle tre fargene om gangen. Dette betyr 307200 ord totalt.
Figur 7. 2-port ram fra IP-katalog
Figur 8. Kompilasjons rapport, minneforbruk
Men som en kan se er det ikke nok plass til å ha 8:8:8 farger ved 640x480 oppløsning. Vi er nødt til å gjøre forenklinger. Dersom vi deler oppløsningen på 8, får vi da 80x60 * 24, som er 115200 bits. Det skal være godt innenfor.
Vi kobler RAM modulen
vram_instance : vram port map(
wrclock => CLOCK_50,
wraddress => vram_wraddress,
data => vram_data,
wren => vram_wren,
rdclock => CLOCK_25,
rdaddress => vram_rdaddress,
q => vram_out
);
vram_rdaddress <= std_logic_vector(to_unsigned(
(h_pixels / 8) * (y / 8) + ((x+1) / 8), 13));
VGA_R <= vram_out(23 downto 16) when draw_time = '1' else (others => '0');
VGA_G <= vram_out(15 downto 8) when draw_time = '1' else (others => '0');
VGA_B <= vram_out(7 downto 0) when draw_time = '1' else (others => '0');
[grunnen til at det er (x+1) dekkes i neste side]
process til testing av VRAM i VHDL, brukt sammen med signal tap logic analyzer.
fill_color : process(CLOCK_50) is
variable i : integer range 0 to v_pixels * h_pixels / 8;
variable slow : integer range 0 to 500000;
begin
if rising_edge(CLOCK_50) then
vram_wren <= '0';
if KEY(0) = '0' then
i := 0;
slow := 0;
end if;
if slow = 500000 and KEY(1) = '0' then
slow := 0;
if i < (v_pixels*h_pixels/8-1) and SW(17) = '1' then
vram_wren <= '1';
vram_wraddress <= std_logic_vector(to_unsigned(i, 13));
if SW(0) = '1' then
vram_data <= "111111110000000000000000";
elsif SW(1) = '1' then
vram_data <= "000000001111111100000000";
elsif SW(2) = '1' then
vram_data <= "000000000000000011111111";
else
vram_data <= "000000000000000000000000";
end if;
i := i + 1;
end if;
else
slow := slow + 1;
end if;
end if;
end process;
Siden systemet er synkront, trenger vi å laste inn fargen en klokkesyklus før den vises. Dette kan gjøres med å utvide rekkevidden til x integer til å inkludere -1, deretter endre på hvordan tellerne oppfører seg når det er blanking time.
-- I VGA entity
if h_counter < h_pixels then
x <= h_counter;
else
x <= -1;
end if;
if v_counter < v_pixels then
y <= v_counter;
if h_counter >= h_pixels and v_counter + 1 < v_pixels then
y <= v_counter + 1;
end if;
else
y <= 0;
end if;
Dersom vi ikke gjør dette, får vi følgende problemer
Figur 9. indeksering problemer som oppsto underveis
Til slutt så har vi et minneområde vi kan tegne rød/grønn/blå/svart til.
Figur 10. Sluttresultat av VRAM testing i VHDL, alle farger har blitt tegnet på med vilje.
Interface mot NIOS II
Vi lager et enkelt bus interface
entity main is
port(
CLK : in std_logic;
RESET : in std_logic;
CHIPSELECT : in std_logic;
WR : in std_logic;
RD : in std_logic;
ADDRESS : in std_logic_vector(12 downto 0);
WRITEDATA : in std_logic_vector(31 downto 0);
READDATA : out std_logic_vector(31 downto 0);
VGA_R, VGA_G, VGA_B : out std_logic_vector(7 downto 0);
VGA_CLK, VGA_SYNC_N, VGA_BLANK_N : out std_logic;
VGA_VS, VGA_HS : out std_logic
);
end entity main;
bus_interface : process (CLK) is
begin
if rising_edge(CLK) then
vram_wren <= '0';
if nreset = '0' then
elsif CHIPSELECT = '1' then
if WR = '1' then
vram_wren <= '1';
vram_data <= WRITEDATA(23 downto 0);
vram_wraddress <= ADDRESS(12 downto 0);
end if;
end if;
end if;
end process;
Også legger vi til denne i platform designer
Figur 11. vga kontroller i platform designer
Vi lager så programmer i C som rett og slett bare skriver til riktig adresse.
Gradvis fylling:
for (;;) {
for (uint32_t c = 0; c < 3; c++) {
for (uint32_t i = 0; i < 80*60; i++) {
IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, i*4, 0xff << (c*8));
usleep(100);
}
}
}
Gradient
for (uint32_t y = 0; y < 60; y++) {
uint32_t c = (y * 255/60);
for (uint32_t x = 0; x < 80; x++) {
if (x > 80 * 2/3) {
IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, (y*80+x)*4, c<<16);
} else if (x > 80 * 1/3) {
IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, (y*80+x)*4, c<<8);
} else {
IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, (y*80+x)*4, c<<0);
}
}
}
Bilde
for (uint32_t i = 0; i < 80*60; i++) {
uint32_t p = 0;
p |= gimp_image.pixel_data[i*3+0] << 16;
p |= gimp_image.pixel_data[i*3+1] << 8;
p |= gimp_image.pixel_data[i*3+2] << 0;
IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, i*4, p);
}
Figur 12. farger tegnet fra NIOS II
Figur 13. Bilde eksportert til C-kildekode fra GIMP
VGA Text-Mode
VGA text mode was introduced in 1987 by IBM as part of the VGA standard for its IBM PS/2 computers. Its use on IBM PC compatibles was widespread through the 1990s and persists today for some applications on modern computers. The main features of VGA text mode are colored (programmable 16 color palette) characters and their background, blinking, various shapes of the cursor (block/underline/hidden static/blinking), and loadable fonts (with various glyph sizes). The Linux console traditionally uses hardware VGA text modes, and the Win32 console environment has an ability to switch the screen to text mode for some text window sizes.
VGA text mode lar deg bruke et 8x16 font-atlas til å tegne på skjermen. font-atlaset består av et enkelt bitmap, du velger selv hvilke farger som skal være i forgrunnen og bakgrunnen. Kommunikasjon med en VGA text-mode enhet skjer i 16-bit ord, hvor hvert ord representerer en karakter.
Figur 14. Vga text mode oppbygning (fra wikipedia)
Figur 15. 4-bit og 3-bit fargepalett.
4-bit fargepalletet ble hentet fra CGA grafikkortet, mens 3-bit fargepalett ble hentet fra European Computer Manufacturers Association standarden stiftet i 1976.
Font-atlaset lagrer vi i en gigantisk std_logic_vector
, det er langt ifra optimalt, men det lar oss unngå timing problemer under lesing av atlaset.
signal bitmap : std_logic_vector(0 to 128*256-1);
Det første vi gjør er å utvide adresse-mappet, slik at vi gir brukeren mulighet til å endre på font-atlaset, samt mulighet til å velge om text-mode eller full-color skal brukes.
bus_interface : process (CLK) is
variable curr_32_bit_addr : integer;
begin
if rising_edge(CLK) then
vram_wren <= '0';
if nreset = '0' then
is_vga_text_mode <= '0';
elsif CHIPSELECT = '1' then
if address_integer < 4800 then
if WR = '1' then
vram_wren <= '1';
vram_wraddress <= ADDRESS(12 downto 0);
vram_data <= WRITEDATA(23 downto 0);
end if;
elsif address_integer = 4800 then
if WR = '1' then
is_vga_text_mode <= WRITEDATA(0);
end if;
if RD = '1' then
READDATA <= "0000000000000000000000000000000" & is_vga_text_mode;
end if;
elsif address_integer < 4800 + 128*256 then
if WR = '1' then
curr_32_bit_addr := (address_integer-(4800+1)) / 32;
bitmap(curr_32_bit_addr*32 to (curr_32_bit_addr+1)*32 - 1) <= WRITEDATA;
end if;
end if;
end if;
end if;
end process;
Vi bør og fylle inn en default-verdi i font-atlaset. Dersom du søker på nettet, er de fleste ressurser skrevet for DOS. Heldigvis har vi utrolig gode emulatorer som DOSBox nå til dags.
Figur 16. Fontraption kjørende i DOSBox
Dette lar oss eksportere mangfoldige text-mode skrifttyper til flere formater.
For å automatisk generere en std_logic_vector
i formatet vi selv ønsker, er vi nødt til å lage et lite skript som leser en bildefil og skriver ut masse tekst.
int main()
{
for (int i = 0; i < 256; i++) {
int xbegin = (i % 16) * 8;
int ybegin = i / 16 * 16;
for (int y = 0; y < 16; y++) {
printf("\"");
for (int x = 0; x < 8; x++) {
unsigned char b = gimp_image.pixel_data[((ybegin+y)*128 + xbegin+x) * 3];
printf("%d", b != 0);
}
printf("\" &\n");
}
printf("\n");
}
}
Figur 17. font i std_logic_vector
Så ikke minst, prosessen for å skrive til skjermen ut ifra hvilken modus. Her ble en asynkron prosess valgt, siden ellers ville timing og klokkedomener blitt et problem.
mode : process (x, y, draw_time, vram_out, blink_white, bitmap, nreset, is_vga_text_mode) is
variable current_character : std_logic_vector(0 to 127);
variable code_point : integer range 0 to 255;
variable foreground : integer range 0 to 15;
variable background : integer range 0 to 7;
variable blink : std_logic;
variable code_point_x : integer range 0 to 7;
variable code_point_y : integer range 0 to 15;
begin
VGA_R <= (others => '0');
VGA_G <= (others => '0');
VGA_B <= (others => '0');
vram_rdaddress <= (others => '0');
if nreset = '0' then
-- nothing
else
if is_vga_text_mode = '0' then
vram_rdaddress <= std_logic_vector(to_unsigned(
(h_pixels / 8) * (y / 8) + ((x+1) / 8), 13));
else
vram_rdaddress <= std_logic_vector(to_unsigned(
(h_pixels / 8) * (y / 16) + ((x+1) / 8), 13));
end if;
if draw_time = '1' then
if is_vga_text_mode = '0' then
VGA_R <= vram_out(23 downto 16);
VGA_G <= vram_out(15 downto 8);
VGA_B <= vram_out(7 downto 0);
else
code_point := to_integer(unsigned(vram_out(7 downto 0)));
foreground := to_integer(unsigned(vram_out(11 downto 8)));
background := to_integer(unsigned(vram_out(14 downto 12)));
blink := vram_out(15);
current_character := bitmap(code_point * 128 to (code_point+1) * 128 - 1 );
VGA_R <= color_map_8(background)(23 downto 16);
VGA_G <= color_map_8(background)(15 downto 8);
VGA_B <= color_map_8(background)(7 downto 0);
if blink = '0' or blink_white = '1' then
code_point_y := y mod 16;
code_point_x := x mod 8;
if current_character(code_point_y * 8 + code_point_x) = '1' then
VGA_R <= color_map_16(foreground)(23 downto 16);
VGA_G <= color_map_16(foreground)(15 downto 8);
VGA_B <= color_map_16(foreground)(7 downto 0);
end if;
end if;
end if;
end if;
end if;
end process;
blink_white
blir laget av en ganske enkel prosess, lignende heart.vhd
blink_white_gen : process(CLK) is
begin
if rising_edge(CLK) then
if nreset = '0' then
blink_counter <= (others => '0');
else
blink_counter <= blink_counter + 1;
end if;
end if;
end process;
blink_white <= blink_counter(24);
For å teste alle mulige kombinasjoner av farge, så tegner vi “A” på hele skjermen, også endrer vi forgrunn og bakgrunn ut fra x og y koordinat.
for (uint32_t y = 0; y < 30; y++) {
for (uint32_t x = 0; x < 80; x++) {
int cfg = x * 16 / 80;
uint32_t c = 0 << 15 | ((y * 8/30) << 12) | (cfg << 8) | 65;
IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, (y*80+x)*4, c);
}
}
Figur 18. VGA Text-Mode
VGA text-mode lar oss utnytte utrolig mye mer oppløsning selv om vi har begrenset minne og prosessorkraft. I tillegg gjør det skriving av software enklere, siden enhver applikasjon ikke trenger å implementere font-rendering.
C driver implementasjon
For å forenkle utviklingen av framtidig programvare lager vi en C-driver. Se datablad for mer informasjon om både driveren og for eksempel på bruk.
#include <io.h>
#include <system.h>
#include <inttypes.h>
typedef union vga_full_color {
struct __attribute__((packed)) {
union __attribute__((packed)) {
struct __attribute__((packed)) {
uint8_t b,g,r;
};
uint8_t bgr[3];
};
uint8_t _padding;
};
uint32_t raw_dword;
} vga_full_color_t;
typedef union vga_text_mode {
struct {
uint16_t codepoint : 8;
uint16_t fg : 4;
uint16_t bg : 3;
uint16_t blink : 1;
};
uint16_t raw_word;
} vga_text_mode_t;
#define VGA_MODE_FULL_COLOR 0
#define VGA_MODE_TEXT_MODE 1
uint8_t vga_get_mode(int base_address) {
return IORD_32DIRECT(base_address, 4800*4);
}
void vga_set_mode(int base_address, uint8_t mode) {
IOWR_32DIRECT(base_address, 4800*4, mode & 1);
}
void vga_clear_screen(int base_address) {
for (uint32_t i = 0; i < 60*80; i++) {
IOWR_32DIRECT(base_address, i*4, 0);
}
}
void vga_full_color_set(int base_address, uint32_t index, vga_full_color_t c) {
IOWR_32DIRECT(base_address, index*4, c.raw_dword);
}
void vga_full_color_set_xy(int base_address, unsigned char x, unsigned char y, vga_full_color_t c) {
if (x >= 80) {
// wrap
y += x/80;
x %= 80;
}
if (y >= 60) y %= 60;
IOWR_32DIRECT(base_address, (y*80+x)*4, c.raw_dword);
}
void vga_text_mode_set(int base_address, uint32_t index, vga_text_mode_t c) {
IOWR_32DIRECT(base_address, index*4, c.raw_word);
}
void vga_text_mode_set_xy(int base_address, unsigned char x, unsigned char y, vga_text_mode_t c) {
if (x >= 80) {
// wrap
y += x/80;
x %= 80;
}
if (y >= 30) y %= 30;
IOWR_32DIRECT(base_address, (y*80+x)*4, c.raw_word);
}
C eksempel [Terminal]
Figur 19. Bilde av terminalen
void random_tm(const char* cb)
{
vga_set_mode(VGA_CONTROLLER_0_BASE, VGA_MODE_TEXT_MODE);
for (uint32_t i = 0; i < 80*30; i++) {
vga_text_mode_t c = {
.raw_word = rand() % 0xffff,
};
vga_text_mode_set(VGA_CONTROLLER_0_BASE, i, c);
}
}
struct commands {
void(*func)(const char* cb);
const char* name;
const char* desc;
int quit_immidiately;
} commands[] = {
{cat, "cat", "shows a picture of a cat"},
{gradient, "gradient", "RGB gradient"},
{cycle_rgb_slow, "cycle", "slowly cycle rgb", 1},
{0, "clear", "clears screen", 1},
{display_char, "char", "displays all combinations of arg1 (1 character)"},
{random, "rand", "random colors"},
{random_tm, "tmrand", "random colors text mode"},
};
#define vga_printstr(_base, _x, _y, _str) do { \
for (uint32_t _i = 0; _i < strlen(_str); _i++) { \
c.codepoint = (_str)[_i]; \
vga_text_mode_set_xy(_base, _x, _y, c); \
(_x)++; \
} \
} while (0)
#define vga_printstr_ln(_base, _x, _y, _str) do { \
vga_printstr(_base, _x, _y, _str); \
(_y) += 1; \
(_y) += (_x) / 80; \
(_x) = 0; \
} while (0)
int main() {
clear_screen:
vga_clear_screen(VGA_CONTROLLER_0_BASE);
vga_set_mode(VGA_CONTROLLER_0_BASE, VGA_MODE_TEXT_MODE);
char command_buffer[128] = {0};
uint32_t x = 0;
uint32_t y = 0;
vga_text_mode_t c = {
.bg = 0,
.fg = 15,
};
line_begin:
vga_printstr(VGA_CONTROLLER_0_BASE, x, y, "$> ");
for (;;) {
c.codepoint = 221;
c.blink = 1;
vga_text_mode_set_xy(VGA_CONTROLLER_0_BASE, x, y, c);
c.blink = 0;
unsigned char codepoint = getchar();
c.codepoint = 0;
vga_text_mode_set_xy(VGA_CONTROLLER_0_BASE, x, y, c);
if (codepoint == '\n' || codepoint == '\r') {
vga_printstr_ln(VGA_CONTROLLER_0_BASE, x, y, "");
for (uint32_t i = 0; i < sizeof(commands) / sizeof(*commands); i++) {
if (memcmp(commands[i].name, command_buffer, strlen(commands[i].name)) == 0) {
if (commands[i].func)
commands[i].func(command_buffer);
command_buffer[0] = 0;
if (!commands[i].quit_immidiately)
getchar();
goto clear_screen;
}
}
if (memcmp("help", command_buffer, strlen("help")) == 0) {
for (uint32_t i = 0; i < sizeof(commands) / sizeof(*commands); i++) {
vga_printstr(VGA_CONTROLLER_0_BASE, x, y, "> ");
vga_printstr(VGA_CONTROLLER_0_BASE, x, y, commands[i].name);
vga_printstr(VGA_CONTROLLER_0_BASE, x, y, " | ");
vga_printstr_ln(VGA_CONTROLLER_0_BASE, x, y, commands[i].desc);
}
} else if (*command_buffer != 0) {
if (strchr(command_buffer, ' ')) *strchr(command_buffer, ' ') = 0;
vga_printstr(VGA_CONTROLLER_0_BASE, x, y, "[term: error]: command '");
vga_printstr(VGA_CONTROLLER_0_BASE, x, y, command_buffer);
vga_printstr_ln(VGA_CONTROLLER_0_BASE, x, y, "' not found");
}
command_buffer[0] = 0;
goto line_begin;
continue;
}
if (codepoint == 127) {
if (x > 3) x--;
c.codepoint = 0;
vga_text_mode_set_xy(VGA_CONTROLLER_0_BASE, x, y, c);
command_buffer[x-3] = codepoint;
command_buffer[x-3+1] = 0;
continue;
}
c.codepoint = codepoint;
vga_text_mode_set_xy(VGA_CONTROLLER_0_BASE, x, y, c);
if (x-3 < sizeof(command_buffer)) {
command_buffer[x-3] = codepoint;
command_buffer[x-3+1] = 0;
}
x++;
}
}