Проектирование процессоров RISC-V в среде Logisim и на языке Verilog в среде Quartus

Проектирование счетчика инструкций, памяти инструкций и памяти данных

Разбить на страницы
Показывать лекцию целиком

Практическая работа 9. Проектирование счетчика инструкций, памяти инструкций и памяти данных

Необходимые материалы для лекции Verilog-09.zip.

Исходя из описанных в разделе "Архитектора процессора" блоков процессора RISC-V, осуществим проектирование блока счетчика инструкций. Для этого в среде Logisim, в ранее созданном проекте cpu.circ, добавить новую схему. Присвоить ей имя PC. В созданной схеме добавить регистр с разрядностью 32. К входу данных регистра подключить 32-х разрядный вход. Свойству основание задать "шестнадцатеричное", а метке присвоить имя PC_in.

К выходу данных регистра подключить элемент выход. С такими же свойствами разрядности и основания как у PC_in. Имя присвоить IMEM_addr.

Добавить вход с именем "Reset", а также вход для синхронизации регистра. Имя метки синхронизации присвоить "clkr".

К входу WE регистра подключить однобитную константу лог. "1".

Так как выборка следующей инструкции осуществляется со смещением 4. Добавить на схему 32-х разрядный сумматор. Выход с регистра подключить к одному из входов сумматора. К второму входу сумматора подключить константу, также четырехразрядную = 4. К выходу результата сумматора подключить элемент выход. Имя метки присвоить PC_4.

Итоговая функциональная схема счетчика приведена на рисунке 9.1.

(рис 9.1)

Далее получить УГО, проектируемого блока (рисунок 9.2).Необходимо обратить внимание на порядок размещения выходов при создании УГО счетчика инструкций.

(рис 9.2)

В среде Logisim существуют два вида памяти (находятся в разделе "Память":

  • постоянное запоминающее устройство (хранит данные постоянно) - ПЗУ (ROM);
  • статическое оперативное запоминающее (изменяемые данные) - ОЗУ (RAM).
  • Для хранения инструкций - память инструкций IMEM будет применятся ПЗУ. Для хранения данных будет применятся ОЗУ.

    В ПЗУ микропроцессора на кристалле загружается откомпилированная программа в машинных кодах посредством загрузочного кабеля если микропроцессор встроен либо посредством программатора.

    В программируемой логике FPGA память может подготавливается также в виде скомпилированного файла и подгружаться в проект.

    Для ПЗУ основные свойства:

  • "Разрядность адреса" - определяет количество ячеек памяти;
  • "Биты данных" - определяет разрядность данных ячеек памяти;
  • "Содержимое" - указывает на путь к файлу, содержащему данные в машинных кодах.
  • Разместим на поле main память ПЗУ с разрядностью адреса равной 14 и разрядностью данных равной 32, что соответствует емкости 16Кх32.

    На рисунке 9.3а приведены настройки для ПЗУ, на рисунке 9.3b приведен сам блок. Слева у ПЗУ поле адреса, справа поле данных.

    (рис 9.3a) (рис 9.3b)

    Для экономии пространства можно заменить свойство ПЗУ "Внешний вид" на Logisim-Классический (рисунок 9.4).

    (рис 9.4)

    Для проекта процессора также необходима память данных для этого выберем ОЗУ Для ОЗУ основные свойства:

  • "Разрядность адреса" - определяет количество ячеек памяти;
  • "Биты данных" - определяет разрядность данных ячеек памяти;
  • "Реализация шины данных" - определяет виды шин данных. Обычно шина данных на запись данных, отдельная шина данных на чтение.
  • Разместим на поле main память ОЗУ с разрядностью адреса равной 8 и разрядностью данных равной 32, что соответствует емкости 256х32. Свойству "Каналы памяти" зададим "Одноканальная". Свойству "Реализация шины" данных зададим "Отдельная шина данных для чтения и записи". В свойстве "Асинхронное чтение" выберем "Да".

    На рисунке 9.5a приведены настройки для ОЗУ, на рисунке 9.5b приведен сам блок. Слева у ПЗУ поле адреса, справа поле данных

    (рис 9.5a) (рис 9.5b)

    Для уменьшения места, занимаемого памятью необходимо заменить свойство ОЗУ "Внешний вид" на Logisim-Классический (рисунок 9.6). На рисунке А - шина адреса, D (слева) - шина данных записи, справа - шина данных чтения. WE- write enable - вход управления разрешения записи в память, ОЕ - output enable вход управления разрешения чтения из памяти.

    (рис 9.6)

    Далее разместим на поле счетчик инструкций PC, АЛУ (ALU) и регистровый файл (RegFile). Проведем соединения адресных линий от счетчика к ПЗУ. Подключение осуществить с применением элемента "Разветвитель". Которому задать свойства:

  • "Внешний вид" - леворукий;
  • "Веерный выход" - 1;
  • "Разрядность входа" - 32;
  • Бит 0 - нет, Бит 1 - нет;
  • Бит с 16 и до 32 - нет.
  • Выход АЛУ также через разветвитель подключить к входу адреса ОЗУ. Единственное изменить настройки таким образом, чтобы с ОЗУ соединялись биты со 2 по 9, что соответствует 256 ячейкам памяти.

    Выход данных rs2 регистрового файла подключим к входу D ОЗУ.

    Добавим на поле мультиплексор с разрядностью 32 и свойством "Выбирающие биты" 1. Вход 0 мультиплексора подключим к выходу АЛУ. Второй вход подключить к выходу D ОЗУ. Выход мультиплексора подключить к входу данных регистрового файла DataIn. Организация такой схемы как раз обусловлена тем, что сначала данные загружаются в регистры, выполняется соответствующая операция, затем данные загружаются в память. Также данные из памяти загружаются в регистры, а потом в память.

    За данный процесс отвечает дешифратор инструкций и блок управления.

    На рисунке 9.7 показан результат объединения счетчика инструкций PC и памяти инструкций (IMEM). На рисунке 9.8 приведена схема включения операционного блока с памятью данных.

    (рис 9.7) (рис 9.8)

    Для проектирования регистрового файла в среде Quartus, в существующем проекте RISC_V, создать новый Verilog HDL File. Сохранить файл как PCUnit.v.

    Проектируемый в целом отражает функциональную схему, приведенную на рисунке 9.1, но в него уже интегрированы входы переходов с блока управления ветвлениями, который будет рассмотрен далее. В листинге 9.1 приведен код счетчика инструкций.

    module PCUnit(
        input clk,                  // тактовый сигнал
        input reset,                // сигнал сброса
        input branchTaken,          // сигнал ветвления
        input [31:0] branchAddr,    // адрес для перехода при ветвлении
        output reg [31:0] pc        // выход счетчика
    );
        
    
        // Инициализация PC стартовым адресом
        initial pc = 32'b0;
    
        // анализ сигнала clk по фронту
        always @(posedge clk) begin
            if (reset) begin
                // Установка PC в начальный адрес при сбросе
                pc  <= 32'b0;
            end else if (branchTaken) begin
                // Если присутсвует сигнал перехода то счетчик указывает на адрес branchAddr
                pc  <= branchAddr;
            end else begin
                // иначе PC увеличивается на 4
                pc  <= pc + 4;
            end
        end
    endmodule
    

    На рисунке 9.9 приведен вид RTL созданного модуля.

    (рис 9.9)

    Оперативное запоминающее устройство, выполняющее функции памяти данных, создаваемая на Verilog HDL имеет аналогичные порты адреса и данных, а также входы управления записью и чтением. Для получения памяти данных в проекте создадим файл DataMemory.v и введем код из листинга 9.2.

    module DataMemory (
        input clk, //синхросигнал
        input [31:0] addr, // шина адреса
        input [31:0] writeData, //входная шина данных 
        input memWrite, // сигнал управления записи
        input memRead, //сигнал управления чтением
        output [31:0] readData //выход данных
    );
        reg [7:0] mem [0:255];// объем памяти равен 256 байт
    
        // Чтение данных
        assign readData = memRead ? {mem[addr+3], mem[addr+2], mem[addr+1], mem[addr]} : 32'b0;
        // здесь данные как раз объединаются с применением конкатенации
        // Запись данных по фронту clk
        always @(posedge clk) begin
            if (memWrite) begin 
                //здесь данные из 32-х битного слова делятся на 4 байта 
                    mem[addr]    <= writeData[7:0];
                mem[addr+1]  <= writeData[15:8];
                mem[addr+2]  <= writeData[23:16];
                mem[addr+3]  <= writeData[31:24];
                    //здесь данные из 32-х битного слова делятся на 4 байта 
            end
        end
    endmodule
    

    На рисунке 9.10 приведен вид RTL созданного модуля.

    (рис 9.10)

    Для процессора требуется описать память инструкций. На данном моменте требуется отметить, что память инструкций - обычно модуль ROM. Данный модуль содержит код, записанный заранее. Данная память может содержать код программы в виде констант с заранее определенными адресами:

    always @ (addr)
           case (addr)
    32'h00000000: instr  <= 32'b001000_00000_10000_0000000011111111;
    32'h00000004: instr  <= 32'b101011_00000_10000_1111000000000000;
    

    Второй вариант применение файла инициализации, чтобы загрузить данные в модуль ROM из внешнего текстового файла. Это применяется как правило для загрузки больших таблиц кода.

    Пример кода для данного метода имеет вид:

    initial begin 
    $readmemh("rom_data.hex", rom); 
    End
    

    Для первичной проверки работоспособности проектируемого процессора будет создана память инструкций, содержащая код программы непосредственно внутри модуля.

    Для получения памяти инструкций в проекте создадим файл InstructionMemory.v и введем код из листинга 9.3.

    module InstructionMemory(
        input [31:0] addr, // шина адреса
        output [31:0] instr // шина данных выхода 
    );
        reg [31:0] mem [0:63]; // объем памяти 64 инструкции по 32 бит
    
        initial begin
            // Пример инструкций
            mem[0] = 32'h00000013; // ADDI r1, r0, 0 (NOP)
            mem[1] = 32'h002081B3; // ADD x3, x1, x2
            mem[2] = 32'h40400013; // ADDI x1, x0, 64
            
        end
        assign instr = mem[addr[31:2]]; // Выравнивание по 4 байтам
    endmodule
    

    На рисунке 9.11 приведен вид RTL созданного модуля.

    (рис 9.11)
    Страницы:

    Практическая работа 9. Проектирование счетчика инструкций, памяти инструкций и памяти данных

    Необходимые материалы для лекции Verilog-09.zip.

    Исходя из описанных в разделе "Архитектора процессора" блоков процессора RISC-V, осуществим проектирование блока счетчика инструкций. Для этого в среде Logisim, в ранее созданном проекте cpu.circ, добавить новую схему. Присвоить ей имя PC. В созданной схеме добавить регистр с разрядностью 32. К входу данных регистра подключить 32-х разрядный вход. Свойству основание задать "шестнадцатеричное", а метке присвоить имя PC_in.

    К выходу данных регистра подключить элемент выход. С такими же свойствами разрядности и основания как у PC_in. Имя присвоить IMEM_addr.

    Добавить вход с именем "Reset", а также вход для синхронизации регистра. Имя метки синхронизации присвоить "clkr".

    К входу WE регистра подключить однобитную константу лог. "1".

    Так как выборка следующей инструкции осуществляется со смещением 4. Добавить на схему 32-х разрядный сумматор. Выход с регистра подключить к одному из входов сумматора. К второму входу сумматора подключить константу, также четырехразрядную = 4. К выходу результата сумматора подключить элемент выход. Имя метки присвоить PC_4.

    Итоговая функциональная схема счетчика приведена на рисунке 9.1.

    (рис 9.1)

    Далее получить УГО, проектируемого блока (рисунок 9.2).Необходимо обратить внимание на порядок размещения выходов при создании УГО счетчика инструкций.

    (рис 9.2)

    В среде Logisim существуют два вида памяти (находятся в разделе "Память":

  • постоянное запоминающее устройство (хранит данные постоянно) - ПЗУ (ROM);
  • статическое оперативное запоминающее (изменяемые данные) - ОЗУ (RAM).
  • Для хранения инструкций - память инструкций IMEM будет применятся ПЗУ. Для хранения данных будет применятся ОЗУ.

    В ПЗУ микропроцессора на кристалле загружается откомпилированная программа в машинных кодах посредством загрузочного кабеля если микропроцессор встроен либо посредством программатора.

    В программируемой логике FPGA память может подготавливается также в виде скомпилированного файла и подгружаться в проект.

    Для ПЗУ основные свойства:

  • "Разрядность адреса" - определяет количество ячеек памяти;
  • "Биты данных" - определяет разрядность данных ячеек памяти;
  • "Содержимое" - указывает на путь к файлу, содержащему данные в машинных кодах.
  • Разместим на поле main память ПЗУ с разрядностью адреса равной 14 и разрядностью данных равной 32, что соответствует емкости 16Кх32.

    На рисунке 9.3а приведены настройки для ПЗУ, на рисунке 9.3b приведен сам блок. Слева у ПЗУ поле адреса, справа поле данных.

    (рис 9.3a) (рис 9.3b)

    Для экономии пространства можно заменить свойство ПЗУ "Внешний вид" на Logisim-Классический (рисунок 9.4).

    (рис 9.4)

    Для проекта процессора также необходима память данных для этого выберем ОЗУ Для ОЗУ основные свойства:

  • "Разрядность адреса" - определяет количество ячеек памяти;
  • "Биты данных" - определяет разрядность данных ячеек памяти;
  • "Реализация шины данных" - определяет виды шин данных. Обычно шина данных на запись данных, отдельная шина данных на чтение.
  • Разместим на поле main память ОЗУ с разрядностью адреса равной 8 и разрядностью данных равной 32, что соответствует емкости 256х32. Свойству "Каналы памяти" зададим "Одноканальная". Свойству "Реализация шины" данных зададим "Отдельная шина данных для чтения и записи". В свойстве "Асинхронное чтение" выберем "Да".

    На рисунке 9.5a приведены настройки для ОЗУ, на рисунке 9.5b приведен сам блок. Слева у ПЗУ поле адреса, справа поле данных

    (рис 9.5a) (рис 9.5b)

    Для уменьшения места, занимаемого памятью необходимо заменить свойство ОЗУ "Внешний вид" на Logisim-Классический (рисунок 9.6). На рисунке А - шина адреса, D (слева) - шина данных записи, справа - шина данных чтения. WE- write enable - вход управления разрешения записи в память, ОЕ - output enable вход управления разрешения чтения из памяти.

    (рис 9.6)

    Далее разместим на поле счетчик инструкций PC, АЛУ (ALU) и регистровый файл (RegFile). Проведем соединения адресных линий от счетчика к ПЗУ. Подключение осуществить с применением элемента "Разветвитель". Которому задать свойства:

  • "Внешний вид" - леворукий;
  • "Веерный выход" - 1;
  • "Разрядность входа" - 32;
  • Бит 0 - нет, Бит 1 - нет;
  • Бит с 16 и до 32 - нет.
  • Выход АЛУ также через разветвитель подключить к входу адреса ОЗУ. Единственное изменить настройки таким образом, чтобы с ОЗУ соединялись биты со 2 по 9, что соответствует 256 ячейкам памяти.

    Выход данных rs2 регистрового файла подключим к входу D ОЗУ.

    Добавим на поле мультиплексор с разрядностью 32 и свойством "Выбирающие биты" 1. Вход 0 мультиплексора подключим к выходу АЛУ. Второй вход подключить к выходу D ОЗУ. Выход мультиплексора подключить к входу данных регистрового файла DataIn. Организация такой схемы как раз обусловлена тем, что сначала данные загружаются в регистры, выполняется соответствующая операция, затем данные загружаются в память. Также данные из памяти загружаются в регистры, а потом в память.

    За данный процесс отвечает дешифратор инструкций и блок управления.

    На рисунке 9.7 показан результат объединения счетчика инструкций PC и памяти инструкций (IMEM). На рисунке 9.8 приведена схема включения операционного блока с памятью данных.

    (рис 9.7) (рис 9.8)

    Для проектирования регистрового файла в среде Quartus, в существующем проекте RISC_V, создать новый Verilog HDL File. Сохранить файл как PCUnit.v.

    Проектируемый в целом отражает функциональную схему, приведенную на рисунке 9.1, но в него уже интегрированы входы переходов с блока управления ветвлениями, который будет рассмотрен далее. В листинге 9.1 приведен код счетчика инструкций.

    module PCUnit(
        input clk,                  // тактовый сигнал
        input reset,                // сигнал сброса
        input branchTaken,          // сигнал ветвления
        input [31:0] branchAddr,    // адрес для перехода при ветвлении
        output reg [31:0] pc        // выход счетчика
    );
        
    
        // Инициализация PC стартовым адресом
        initial pc = 32'b0;
    
        // анализ сигнала clk по фронту
        always @(posedge clk) begin
            if (reset) begin
                // Установка PC в начальный адрес при сбросе
                pc  <= 32'b0;
            end else if (branchTaken) begin
                // Если присутсвует сигнал перехода то счетчик указывает на адрес branchAddr
                pc  <= branchAddr;
            end else begin
                // иначе PC увеличивается на 4
                pc  <= pc + 4;
            end
        end
    endmodule
    

    На рисунке 9.9 приведен вид RTL созданного модуля.

    (рис 9.9)

    Оперативное запоминающее устройство, выполняющее функции памяти данных, создаваемая на Verilog HDL имеет аналогичные порты адреса и данных, а также входы управления записью и чтением. Для получения памяти данных в проекте создадим файл DataMemory.v и введем код из листинга 9.2.

    module DataMemory (
        input clk, //синхросигнал
        input [31:0] addr, // шина адреса
        input [31:0] writeData, //входная шина данных 
        input memWrite, // сигнал управления записи
        input memRead, //сигнал управления чтением
        output [31:0] readData //выход данных
    );
        reg [7:0] mem [0:255];// объем памяти равен 256 байт
    
        // Чтение данных
        assign readData = memRead ? {mem[addr+3], mem[addr+2], mem[addr+1], mem[addr]} : 32'b0;
        // здесь данные как раз объединаются с применением конкатенации
        // Запись данных по фронту clk
        always @(posedge clk) begin
            if (memWrite) begin 
                //здесь данные из 32-х битного слова делятся на 4 байта 
                    mem[addr]    <= writeData[7:0];
                mem[addr+1]  <= writeData[15:8];
                mem[addr+2]  <= writeData[23:16];
                mem[addr+3]  <= writeData[31:24];
                    //здесь данные из 32-х битного слова делятся на 4 байта 
            end
        end
    endmodule
    

    На рисунке 9.10 приведен вид RTL созданного модуля.

    (рис 9.10)

    Для процессора требуется описать память инструкций. На данном моменте требуется отметить, что память инструкций - обычно модуль ROM. Данный модуль содержит код, записанный заранее. Данная память может содержать код программы в виде констант с заранее определенными адресами:

    always @ (addr)
           case (addr)
    32'h00000000: instr  <= 32'b001000_00000_10000_0000000011111111;
    32'h00000004: instr  <= 32'b101011_00000_10000_1111000000000000;
    

    Второй вариант применение файла инициализации, чтобы загрузить данные в модуль ROM из внешнего текстового файла. Это применяется как правило для загрузки больших таблиц кода.

    Пример кода для данного метода имеет вид:

    initial begin 
    $readmemh("rom_data.hex", rom); 
    End
    

    Для первичной проверки работоспособности проектируемого процессора будет создана память инструкций, содержащая код программы непосредственно внутри модуля.

    Для получения памяти инструкций в проекте создадим файл InstructionMemory.v и введем код из листинга 9.3.

    module InstructionMemory(
        input [31:0] addr, // шина адреса
        output [31:0] instr // шина данных выхода 
    );
        reg [31:0] mem [0:63]; // объем памяти 64 инструкции по 32 бит
    
        initial begin
            // Пример инструкций
            mem[0] = 32'h00000013; // ADDI r1, r0, 0 (NOP)
            mem[1] = 32'h002081B3; // ADD x3, x1, x2
            mem[2] = 32'h40400013; // ADDI x1, x0, 64
            
        end
        assign instr = mem[addr[31:2]]; // Выравнивание по 4 байтам
    endmodule
    

    На рисунке 9.11 приведен вид RTL созданного модуля.

    (рис 9.11)
    Вернуться к учебному плану