§ Небольшое описание

Этот видеоадаптер, это вывод символов на экран 80 на 25, для OMDAZZ CYCLONE IV. Так как на этой отладочной плате установлено только 3 бита для цвета (R,G,B), то он может выводить только 8 цветов, к сожалению. А внешний переходник мне подключать что-то лень.
Сначала надо определиться с тем, что подключать. Для вывода символов 8x16 нам потребуется хранить знакоместа для них, и так как символов 256, и на каждый символ тратится по 16 строк (по 8 бит одна строка), то необходимо 4096 байт для хранения знакомест. Аналогично, для хранения номеров знакомест потребуется 80 на 25 = 2000 байт, и напротив каждого существует атрибут, что дает по итогу 4000 байт (без 96 байт в конце).
Как объявить блоки памяти в TOP модуле, всем понятно. Просто и легко.
» Diяect download шаблон of проект
wire [11:0] ram_a, rom_a;
wire [ 7:0] ram_i, rom_i;
wire [10:0] cursor;

video U1
(
    .clock      (clock_25),

    // Физический интерфейс
    .r          (VGA_R),
    .g          (VGA_G),
    .b          (VGA_B),
    .hs         (VGA_HS),
    .vs         (VGA_VS),

    // Подключение к видеопамяти и шрифтам
    .ram_a      (ram_a),
    .rom_a      (rom_a),
    .ram_i      (ram_i),
    .rom_i      (rom_i),
    .cursor     (cursor)
);

vram U2
(
    .clock      (~clock_25),
    .a          (ram_a),
    .q          (ram_i)
);

vrom U3
(
    .clock      (~clock_25),
    .a          (rom_a),
    .q          (rom_i)
);

§ Сам модуль

Теперь приведу код самого модуля, который выводит на экран необходимые символы. Он, как и все остальное с видеоадаптерами, крайне прост и тратит всего лишь 3 такта (из 8 тактов на 1 символ), чтобы извлечь.
  • Номер знакоместа
  • Атрибут
  • Значение знакоместа
Ниже приведен полный код.
module video
(
    input               clock,
    output  reg         r,
    output  reg         g,
    output  reg         b,
    output              hs,
    output              vs,
    // ---
    output reg  [11:0]  ram_a,
    output reg  [11:0]  rom_a,
    input       [ 7:0]  ram_i,
    input       [ 7:0]  rom_i,
    input       [10:0]  cursor
);

// ---------------------------------------------------------------------
// Тайминги для горизонтальной и вертикальной развертки
parameter
//  Visible     Back        Sync        Front       Whole
    hzv =  640, hzb =   48, hzs =   96, hzf =   16, hzw =  800,
    vtv =  400, vtb =   35, vts =    2, vtf =   12, vtw =  449;
// ---------------------------------------------------------------------
assign hs = x < (hzb + hzv + hzf);
assign vs = y < (vtb + vtv + vtf);
// ---------------------------------------------------------------------
reg  [ 9:0] x = 0;
reg  [ 9:0] y = 0;
wire [ 9:0] rx = x - hzb + 8;
wire [ 9:0] ry = y - vtb;
// ---------------------------------------------------------------------
wire xmax = (x == hzw - 1);
wire ymax = (y == vtw - 1);
wire show = (x >= hzb && x < hzb + hzv) && (y >= vtb && y < vtb + vtv);
// ---------------------------------------------------------------------
reg         flash;
reg  [ 7:0] mask, attr;
reg  [22:0] incr;
wire [10:0] curr  = rx[9:3] + ry[9:4]*80;
wire        cbit  = mask[ ~rx[2:0] ];
wire        cur   = flash && (curr == cursor+1 && ry[3:0] >= 14);
wire        blink = (flash & attr[7]) || (attr[7] == 1'b0);

always @(posedge clock) begin

    // Черный цвет по краям
    {r, b, g} <= 3'b000;

    // Кадровая развертка
    x <= xmax ?         0 : x + 1;
    y <= xmax ? (ymax ? 0 : y + 1) : y;

    // Чтение следующего символа
    case (rx[2:0])
    5: begin ram_a <= 2*curr; end
    6: begin rom_a <= {ram_i, ry[3:0]};
             ram_a <= ram_a + 1; end
    7: begin {mask, attr} <= {rom_i, ram_i}; end
    endcase

    // Вывод окна видеоадаптера
    if (show) begin {r,g,b} <= ((cbit && blink) || cur ? attr[2:0] : attr[6:4]); end

    // Мерцающий курсор
    incr  <= (incr == 6250000) ? 0 : incr+1;
    flash <= !incr ? ~flash : flash;

end

endmodule