§ Шаблонный код
Ну что, начинаем новый цикл, снова и снова круговорот процессоров 86-х в природе. Теперь же я нацелился на поддержку 386-го, сделать процессор, который будет поддерживать 32-х битные инструкции, а также защищенный режим.Как и обычно, надо начать с некоторого шаблона, и он будет предельно простым пока что:
#include "src/app.cc" int main(int argc, char** argv) { App* v386 = new App(argc, argv); while (v386->main()) { } return v386->destroy(); }Код файла app.cc я уже ранее приводил, да и к тому же, он будет в прикрепленном файле. Одна немаловажная вещь, которую я добавил сюда — это дизассемблер, который будет постоянно далее использоваться для отладки.
§ Структура проекта
Как выглядит структура проектаsrc -- app.h Заголовочный файл для класса App -- disasm.h Для класса Disassemble -- app.cc Коды App -- disasm.cc Код для Disassemble bin -- font.bin Шрифты 8x16 bios -- bios.asm Биос для компьютера -- makefile Сборка биоса tb.cc Исполняемый файл tb.v Тестбенч для проверки процессора v386.v Сам процессор vga.v Для отображения видеоданных makefile И сборкаС биосом (bios.asm) пока что все очень просто:
org 0 jmp $ times 65536-16-$ db 0 jmp far 0F000h : 0 db '27/04/22' db 0x00, 0xFE, 0x00Создается 64Кб файл, который будет находится в старших адресах, начиная с F0000h и заканчивая FFFFFh.
§ Добавим VGA
Теперь быстрыми темпами усложняем tb.cc:#include "src/app.cc" #include "obj_dir/Vvga.h" int main(int argc, char** argv) { int instr = 125000; float target = 100; Verilated::commandArgs(argc, argv); App* v386 = new App(argc, argv); while (v386->main()) { Uint32 start = SDL_GetTicks(); // Автоматическая коррекция кол-ва инструкции в секунду for (int i = 0; i < instr; i++) v386->tick(); // Коррекция тактов Uint32 delay = (SDL_GetTicks() - start); instr = (instr * (0.5 * target) / (float)delay); instr = instr < 1000 ? 1000 : instr; if (Verilated::gotFinish()) break; } return v386->destroy(); }В проект был добавлен модуль VGA
#include "obj_dir/Vvga.h"
, но пока что без процессора, но есть одна интересная особенность, это автоматическая подстройка на % загрузки хост-процессора. Перед исполнением 125000 тактов, они всегда исполняются, замеряется время в миллисекундах, и корректируется в сторону уменьшения или увеличения. Если, к примеру, target=100%, то 0.5*100 = 50 мс (20 кадров в секунду) — это целевое время, за которое должен проходить instr. Если это время составило 100 мс, то количество instr уменьшается в 2 раза, и наоборот, увеличивается, если прошло быстрее, тем самым постоянно автоматически корректируясь.Поскольку модуль vga я и так ранее разрабатывал, то ни код, ни разбор приводить здесь не буду, он все равно есть в файле.
Команда сборки makefile:
VRL=/usr/share/verilator/include all: verilate tbc icarus: tbc: g++ -o tb -I$(VRL) $(VRL)/verilated.cpp tb.cc \ obj_dir/Vvga__ALL.a \ -lSDL2 ./tb verilate: verilator -cc vga.v cd obj_dir && make -f Vvga.mkЦель
all
собирает сначала секцию verilate, а потом tbc. Модуль пока что один.Метод tick работает пока что очень просто. Как и в прошлых статьях, здесь процессор работает на скорости 25 мгц, так же, как и vga-модуль.
void tick() { // Чтение из памяти vga_mod->data = memory[0xb8000 + vga_mod->address]; // Запуск модулей 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)); }Вот и скриншот экрана. Он пока что работает довольно быстро, потому что в данный момент не навешано разнообразных модулей, которые до невозможности замедляют симуляцию кода.
§ Тестбенч
Теперь же надо создать код для запуска и отладки будущего процессора. Первым будет файл tb.v:module tb; // --------------------------------------------------------------------- reg clock; reg clock_25; 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; #2000 $finish; end initial begin $dumpfile("tb.vcd"); $dumpvars(0, tb); end initial begin $readmemh("bios/bios.hex", memory, 20'hF0000); 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`timescale 10ns / 1ns Как и обычно, задаются две частоты — это 100 (clock) и 25 мгц (clock_25), и читается bios.hex в память, последние 64Кб, якобы там ROM BIOS, хотя на самом деле там будет RAM BIOS. Мне кажется, нельзя так делать, честно говоря, но я все равно так делаю, потому что bios по определению, затирать нельзя, а у меня можно, вот такие вот странные вещи творятся.
Далее, контроллер блочной памяти. При появлении сигнала we, на следующем такте будет записано новое значение и прочитано тоже, как и работает в реальной схеме внутри ПЛИС.
Объявляются пины у процессора:
module v386 ( // Тактовый генератор input clock, input reset_n, input locked, // Магистраль данных 8 битная output [31:0] address, input [ 7:0] in, output reg [ 7:0] out, output reg we ); endmoduleЛегко заметить, как я постоянно на одни и те же грабли наступаю, например, что шина данных 8 битная, хотя в 386 она уже была 32-х битная, но мне как-то то ли лень делать, то ли влом, но делаю так, чтобы данные читались байт за байтом, что замедляет скорость работы в процессора в разы, но упрощает его разработку.
В файле tb.v объявляю блок процессора:
1'b1), .locked (1'b1), .address (address), .in (in), .out (out), .we (we) );v386 v386_inst ( .clock (clock_25), .reset_n (Просто прицепляя нужные пину к нужным проводам. На этом мои размышления на сегодня заканчиваются.
Файлы проекта
[Оглавление] [Следующая >>]