Оглавление


§ Файлы на загрузку

§ tb.cc: Main

#include "obj_dir/Vvideo.h"
#include "tb.h"

// Запуск основного приложения
int main(int argc, char** argv)
{
    TB* tb = new TB(argc, argv);
    return tb->main();
}

§ tb.h: Основной файл

#include <SDL2/SDL.h>
#include <stdio.h>

class TB
{
protected:

    int width, height, scale, frame_length, pticks;
    int x, y, _hs, _vs;

    SDL_Surface*        screen_surface;
    SDL_Window*         sdl_window;
    SDL_Renderer*       sdl_renderer;
    SDL_PixelFormat*    sdl_pixel_format;
    SDL_Texture*        sdl_screen_texture;
    SDL_Event           evt;
    Uint32*             screen_buffer;

    // Коррекция тактов
    int     instr;
    float   target;

    // Модули
    Vvideo* vga_mod;

public:

    TB(int argc, char** argv)
    {
        Verilated::commandArgs(argc, argv);

        x   = 0; y   = 0;
        _hs = 1; _vs = 0;

        // 100% загрузка процессора
        instr       = 150000;
        target      = 100;

        pticks      = 0;
        vga_mod     = new Vvideo();

        // Удвоение пикселей (50 кадров в секунду)
        scale        = 2;
        width        = 640;
        height       = 480;
        frame_length = (1000/50);

        if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
            exit(1);
        }

        SDL_ClearError();
        sdl_window          = SDL_CreateWindow("Basic 6.0 Application", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                              scale*width, scale*height, SDL_WINDOW_SHOWN);
        sdl_renderer        = SDL_CreateRenderer(sdl_window, -1, SDL_RENDERER_PRESENTVSYNC);
        screen_buffer       = (Uint32*) malloc(width * height * sizeof(Uint32));
        sdl_screen_texture  = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, width, height);
        SDL_SetTextureBlendMode(sdl_screen_texture, SDL_BLENDMODE_NONE);
    }

    // Один кадр
    int main()
    {
        SDL_Rect dstRect;

        dstRect.x = 0;
        dstRect.y = 0;
        dstRect.w = scale * width;
        dstRect.h = scale * height;

        int quit  = 0;
        int maxln = frame_length * 25000;

        do {

            // Прием событий
            while (SDL_PollEvent(& evt))
            {
                // Событие выхода
                switch (evt.type) { case SDL_QUIT: quit = 1; break; }
            }

            // Автоматическая коррекция
            Uint32 ticks = SDL_GetTicks();
            for (int i = 0; i < instr; i++) tick();
            Uint32 delay = SDL_GetTicks() - ticks;

            instr = (instr * (0.5 * target) / (float) delay);
            instr = instr < 1000 ? 1000 : (instr > maxln ? maxln : instr);
            delay = frame_length - delay;

            // Дополнение Delay до необходимого количества
            SDL_Delay(delay < 1 ? 1 : (delay > frame_length ? frame_length : delay));

            // Обновить окно
            SDL_UpdateTexture       (sdl_screen_texture, NULL, screen_buffer, width * sizeof(Uint32));
            SDL_SetRenderDrawColor  (sdl_renderer, 0, 0, 0, 0);
            SDL_RenderClear         (sdl_renderer);
            SDL_RenderCopy          (sdl_renderer, sdl_screen_texture, NULL, & dstRect);
            SDL_RenderPresent       (sdl_renderer);
        }
        while (quit == 0);

        // Удаление окна из памяти
        free(screen_buffer);
        SDL_DestroyTexture(sdl_screen_texture);
        SDL_FreeFormat(sdl_pixel_format);
        SDL_DestroyRenderer(sdl_renderer);
        SDL_DestroyWindow(sdl_window);
        SDL_Quit();

        return 0;
    }

    // Один такт 25 мгц
    void tick()
    {
        vga_mod->clock = 0; vga_mod->eval();
        vga_mod->clock = 1; vga_mod->eval();

        vga(vga_mod->hs, vga_mod->vs, (vga_mod->r*16)*65536 + (vga_mod->g*16)*256 + (vga_mod->b*16));
    }

    // ------------------------------UTILS ------------------------------

    // Установка точки
    void pset(int x, int y, Uint32 cl)
    {
        if (x >= 0 && y >= 0 && x < width && y < height)
            screen_buffer[width*y + x] = cl;
    }

    // 640 x 480
    void vga(int hs, int vs, int color)
    {
        if (hs) x++;

        // Отслеживание изменений HS/VS
        if (_hs == 0 && hs == 1) { x = 0; y++; }
        if (_vs == 1 && vs == 0) { x = 0; y = 0; }

        // Сохранить предыдущее значение
        _hs = hs;
        _vs = vs;

        // Вывод на экран
        pset(x-48, y-33, color);
    }
};

§ video.v: Видеоадаптер

// verilator lint_off WIDTH
module video
(
    input               clock,
    output reg  [3:0]   r,
    output reg  [3:0]   g,
    output reg  [3:0]   b,
    output              hs,
    output              vs
);
// ---------------------------------------------------------------------
// Тайминги для горизонтальной|вертикальной развертки (640x400)
parameter
    hz_visible = 640, vt_visible = 480,
    hz_front   = 16,  vt_front   = 10,
    hz_sync    = 96,  vt_sync    = 2,
    hz_back    = 48,  vt_back    = 33,
    hz_whole   = 800, vt_whole   = 525;
// ---------------------------------------------------------------------
assign hs = x  < (hz_back + hz_visible + hz_front); // NEG.
assign vs = y >= (vt_back + vt_visible + vt_front); // POS.
// ---------------------------------------------------------------------
wire        xmax = (x == hz_whole - 1);
wire        ymax = (y == vt_whole - 1);
reg  [10:0] x    = 0;
reg  [10:0] y    = 0;
// ---------------------------------------------------------------------

// Вывод видеосигнала
always @(posedge clock)
begin

    {r, g, b} <= 12'h000;

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

    // Вывод окна видеоадаптера
    if (x >= hz_back && x < hz_visible + hz_back && y >= vt_back && y < vt_visible + vt_back)
    begin {r, g, b} <= 12'hFFF; end

end

endmodule

§ makefile

all:
	php makefile.php run
wav:
	gtkwave tb.gtkw
clean:
	rm -rf tb tb.qqq tb.vcd obj_dir tb.log .diff

§ makefile.php

<?php

// Подключение библиотек
$LIB = "/usr/share/verilator/include";
$INC = "$LIB/verilated.cpp $LIB/verilated_threads.cpp";
$OBJ = "";
$RUN = isset($argv[1]) && $argv[1] == 'run';

$touchfile = false;
$checkfile = [];

$files = [
    'tb.v'          => 'tb',
    'video.v'       => 'v',
    'tb.cc'         => 'c',
    'tb.h'          => 'c',
];

// Отыкарусить
echo `iverilog -g2005-sv -o tb.qqq tb.v video.v`;
echo `vvp tb.qqq -o tb.vcd > /dev/null`;

if (file_exists('tb.qqq')) {
    unlink('tb.qqq');
} else {
    exit;
}

// -----------------------------------------------------------------------------
// Компиляция Verilator, GCC
// -----------------------------------------------------------------------------

if ($RUN)
{
    $run = 1;

    if (!file_exists(".diff")) mkdir(".diff");

    // Скомпилировать, если надо)
    foreach ($files as $file => $type)
    {
        $tf = ".diff/.$file";
        $vf = str_replace('.v', '', $file);
        $ts = file_exists($tf) ? file_get_contents($tf) : 0;
        $mt = filemtime($file);
        $e1 = $e2 = false;

        $checkfile[] = $tf;

        if ($mt != $ts)
        {
            // Добавить в компиляцию
            if ($type == 'v')
            {
                $time = time();
                $e1 = (`verilator --threads 1 -cc $file`);
                $e2 = (`cd obj_dir && make -f V$vf.mk`);
                echo "[".(time() - $time)."] $file\n";
            }

            $touchfile = true;
        }

        if ($type == 'v') {
            $OBJ .= "obj_dir/V{$vf}__ALL.a ";
        }

        file_put_contents($tf, $mt);
    }

    // Требуется перекомпиляция
    if ($touchfile)
    {
        $time = time();
        $bash = "g++ -Ofast -Wno-unused-result -o tb -I$LIB tb.cc $INC $OBJ -lSDL2 2>&1";
        $err = `$bash`;

        echo "[".(time() - $time)."] GCC\n";

        // При обнаруженной ошибке -- сбросить успешные touch-файлы
        if ($err) { echo "ERROR:\n$bash\n$err"; foreach ($checkfile as $file) unlink($file); $run = 0; }
    }

    // Запуск программы -d
    if ($run) echo `./tb > tb.log`;
}

§ tb.v: Тестбенч

`timescale 10ns / 1ns
module tb;

// ---------------------------------------------------------------------
reg         clock;
reg         clock_25;
reg         reset_n = 1'b0;
reg [7:0]   memory[1024*1024];

always  #0.5  clock    = ~clock;
always  #1.5  clock_25 = ~clock_25;
initial begin clock = 0; clock_25 = 0; #3 reset_n = 1; #2000 $finish; end
initial begin $dumpfile("tb.vcd"); $dumpvars(0, tb); end
// ---------------------------------------------------------------------

wire [31:0] address;
reg  [ 7:0] in;
wire [ 7:0] out;
wire        we;
// Контроллер блочной памяти
always @(posedge clock) begin in <= memory[address[19:0]]; if (we) memory[address[19:0]] <= out; end
// ---------------------------------------------------------------------

endmodule