Глава 3. моделирование конвейерного процессора

В этой главе мы шаг за шагом построим функциональную модель 32-х разрядного процессора. Сначала мы создадим модель системы команд, затем опишем уровень регистровых передач. В следующей главе мы приступим к описанию структурной модели, в которой представим процессор как совокупность отдельных блоков. При этом мы объясним, как моделировать конвейерную обработку, параллелизм, выполнение команд, функциональное разбиение и создание тестовых векторов.

Основное внимание уделено процессу моделирования, а не описанию архитектуры процессора. Мы не собираемся подробно объяснять функционирование коммерческого микропроцессора или архитектуру. Некоторое описание архитектуры дано для объяснения понятий и процесса моделирования.

ПРИМЕР SISC ПРОЦЕССОРА

Обычный процессор на СБИС определяется своей архитектурой и системой команд. Давайте определим ЭВМ с малым набором команд (SISC) как компьютер, который выполняет только 10 команд: считывание из памяти (load), запись в память (store), сложение (add), умножение (multiply), дополнение (complement), сдвиг (shift), циклический сдвиг (rotate), пустая команда (nop), команда останова (halt) и команда перехода (branch). Мы будем проектировать процессор, который может выполнять этот малый набор команд.

Перед обсуждением модели процессора мы должны понять, как он выполняет программы, содержащие смесь этих команд. Именно это мы собираемся определить с помощью модели системы команд.

Структурная схема SISC системы представлена на рис. 3.1, а система команд - на рис.3.2.

,-------------.

| Генератор | ,--------------------------------.

| тактовых |---------| ПРОЦЕССОР |

| импульсов | | |

`-------------' | ,----------. ,-------------. |

| | РС | | PSR | |

| | (Счетчик | | (Регистр | |

| | команд) | | состояние | |

,-------------. | | | | процессора)| |

| | | `----------' `-------------' |

| | Адрес | |

| |<------/-| ,-----------------. |

| | | | IR | |

| Память | Данные | | (Регистр команд)| |

| |------/->| `-----------------' |

| |<----/---| |

| | | |

| | | |

| | | ,---------------------------. |

`-------------' | | ALU | |

| | (АЛУ) | |

| `---------------------------' |

| |

| |

| ,---------------------------. |

| | Register File | |

| | (Регистровый файл) | |

| | | |

| `---------------------------' |

`--------------------------------'

Рис. 3.1 Пример структурной схемы SISC процессора

,-------------------------------------------------------.

| Команды |

| |

| Имя Обозначение Код Формат (inst dst scr)|

| операции |

| |

| NOP NOP 0 NOP |

| BRANCH BRA 1 BRA mem, cc |

| LOAD LD 2 LD reg, mem1 |

| STORE STR 3 STR mem, srс |

| ADD ADD 4 ADD reg, srс |

| MULTIPLY MUL 5 MLT reg, srс |

| COMPLEMENT CMP 6 CMP reg, srс |

| SHIFT SHF 7 SHF reg, cnt |

| ROTATE ROT 8 ROT reg, cnt |

| HALT HLT 9 HLT |

| |

| Коды условий |

| |

| A Always (всегда) 0 |

| C Carry (перенос) 1 |

| E Even (четный результат) 2 |

| P Parity (четность результата) 3 |

| Z Zero (нуль) 4 |

| N Negative (отрицательный) 5 |

| |

| Адресация операндов |

| |

| mem - адрес в памяти |

| mem1 - адрес в памяти или непосредственный операнд |

| reg - индекс регистра |

| scr - индекс регистра или непосредственный операнд |

| cc - код условий |

| cnt - величина сдвига в командах shift/rotate, |

| >0=вправо, <0=влево |

| |

| Формат команды |

| |

| IR[31:28] код операции |

| IR[27:24] коды условий |

| IR[27] тип источника 0=reg(mem), 1=непосред- |

| ственный операнд (imm) |

| IR[26] тип назначения 0=reg, 1=imm |

| IR[23:12] адрес источника |

| IR[23:12] величина сдвига в командах shift/rotate |

| IR[11:0] адрес назначения |

| |

| Регистр состояния процессора (PSR) |

| |

| PSR[0] Carry (перенос) |

| PSR[1] Even (четный результат) |

| PSR[2] Parity (четность результата) |

| PSR[3] Zero (ноль) |

| PSR[4] Negative (меньше нуля) |

`-------------------------------------------------------'

Рис. 3.2 Система команд SISC процессора

МОДЕЛЬ СИСТЕМЫ КОМАНД

Модель системы команд процессора описывает выполнение команд и взаимодействие между ними. При этом не рассматривается аппаратная реализация этих команд. Например, команда сложения (add) может быть промоделирована просто как

{carry, sum}=in1+in2;

без уточнения деталей, а именно: было ли сложение выполнено с помощью сумматора со сквозным переносом, сумматора с ускоренным переносом или с использованием какого-либо другого алгоритма; в вычислении суммы и переноса при сложении участвуют только два входных сигнала (in1 и in2), как показано выше.

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

Модель SISC процессора является модулем "закрытой системы" без каких-либо портов входа или выхода и имеет следующий вид:

module system;

...// Элементы модели, включая описания,

...// задачи, функции, блоки initial и

...// always и т.п.

endmodule // система

В следующих разделах мы опишем каждый из элементов модуля.

Описания.

Так как моделирование осуществляется на высоком уровне, мы должны оперировать в терминах регистров и разрядовых полей регистров, а не в терминах вентилей и переключателей. Некоторыми необходимыми нам регистрами являются:

32-разрядный регистр для хранения команд

12-разрядный регистр для адресации памяти

5-разрядный регистр для признаков кодов условий

33-разрядный регистр для хранения результатов.

Эти и другие регистры, а также параметры описаны, как показано на рис. 3.3.

Память и регистровый файл описаны как массивы регистров, размер которых равен WIDTH. Это описание позволяет осуществлять произвольный доступ ко всем ячейкам памяти и регистрового файла без моделирования протокола взаимодействия. Для доступа к ячейкам памяти или регистрового файла теперь можно просто указать структуру, где индексом является соответствующая ячейка. Например,

RFILE[3]=MEM[20];

передаст содержимое ячейки памяти 21 в 4-ую ячейку регистрового файла. Обратим внимание, что мы использовали 0 в качестве начального индекса в обеих структурах.

Максимальный размер адресуемой памяти определяется наибольшим размером поля адреса (12 разрядов). Размер регистрового файла был произвольно выбран равным 16.

,---------------------------------------------.

| //Описание параметров |

| |

| parameter WIDTH = 32 ; |

| parameter CYCLE = 10 ; |

| parameter ADDRSIZE = 12 ; |

| parameter MAXREGS = 16 ; |

| parameter MEMSIZE =(1<<ADDRSIZE); |

| |

| //Описание регистров |

| |

| reg [WIDTH-1:0] MEM[0:MEMSIZE-1], |

| RFILE[0:MAXREGS-1], |

| ir, // |

| src1, src2; |

|reg[WIDTH:0] result ; |

|reg [ADDRSIZE-1:0] pc ; |

|reg[4:0] psr ; |

|reg dir; |

|reg reset; |

| |

|integer i; |

`---------------------------------------------'

Рис. 3.3 Описания для модели системы команд

Регистр результата выбран 33-разрядным для хранения разряда переноса после выполнения арифметических операций. Счетчик команд содержит адрес команды в памяти, и поэтому его размер равен 12 разрядам. Регистр состояния процессора psr содержит 5 признаков кода условий: перенос (carry), четный результат (even), четность результата (parity), равенство нулю (zero) и признак отрицательного результата (negative).

,--------------------------------------------------.

| //Описание полей команды |

| |

| `define OPCODE ir[31:28] |

| `define SRC ir[23:12] |

| `define DST ir[11:0] |

| `define SRCTYPE ir[27] |

| `define DSTTYPE ir[26] |

| `define CCODE ir[27:24] |

| `define SRCNT ir[23:12] |

| |

| // Типы операндов |

| |

| `define REGTYPE 0 |

| `define IMMTYPE 1 |

| |

| // Определение кодов операций для каждой команды|

| |

| `define NOP 4'b0000 |

| `define BRA 4'b0001 |

| `define LD 4'b0010 |

| `define STR 4'b0011 |

| `define ADD 4'b0100 |

| `define MUL 4'b0101 |

| `define CMP 4'b0110 |

| `define SHF 4'b0111 |

| `define ROT 4'b1000 |

| `define HLT 4'b1001 |

`--------------------------------------------------'

Рис. 3.4 Определение символических имен для полей разрядов

Оператор параметр (parameter) объявляет символьные константы WIDTH и CYCLE. Эти символьные константы дают возможность создавать легко адаптируемые модели. Например, если объем памяти равен 16 Кб, то мы должны изменить параметр ADDRSIZE с 12 на 14. Это изменит значение параметра MEMSIZE с 4К до 16К, и индекс массива памяти будет изменяться от 0 до 16К-1.

Оператор 'define (определить), показанный на рис. 3.4, позволяет ссылаться на различные поля регистра команд и кодов условий с помощью символьных имен, а не чисел. Использование символьных имен для различных полей обеспечивает независимость модели от размещения (расположения друг относительно друга) этих полей в регистре команд. Например, если поля SRCTYPE и DSTTYPE поменяются местами, то во всей модели необходимо будет изменить только 2 строки, а именно:

`define SCRTYPE ir[26]

`define DSTTYPE ir[27]

Рис. 3.4 демонстрирует описание символьных имен для полей разрядов.

Основной процесс

Для того, чтобы понять процесс моделирования, можно представить его себе в виде трехступенчатого процесса. Во-первых, должно быть выполнено определение основных объектов или структур. Далее надо описать способ обработки этих структур. И наконец, убедиться, что модель соответствует спецификации.

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

Без углубления в подробности выполнения операций процессором достаточно сказать, что процессор непрерывно выполняет цикл "выборка-исполнение". Показанное на рис. 3.5 описание на Verilog моделирует основной (main) процесс. Основной процесс, описанный блоком always, помечен как main_process и состоит из трех задач: выборка (fetch), выполнение (execution) и запись результата (write_result). Задача fetch извлекает команды из памяти, задача execute выполняет их, и задача write_result записывает результат в регистровый файл. Эта последовательность повторяется для всех команд.

,---------------------------------------------.

| always begin : main_process |

| if (!reset) begin |

| #CYCLE fetch; |

| #CYCLE execute; |

| #CYCLE write_result; |

| end |

| else #CYCLE; |

| end |

`---------------------------------------------'

Рис. 3.5 Основной процесс

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

Сигнал сброса (reset) (точнее, регистр сброса) проверяется для определения того, находится ли процессор в состоянии сброса. Если да, то процесс main ждет один такт до следующей проверки сигнала reset. В операторе if-then-else предложение else является необходимым. Без него процесс always войдет в бесконечный цикл с нулевой задержкой при установленном сигнале reset.

ИНИЦИАЛИЗАЦИЯ СИСТЕМЫ

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

,-------------------------------------------------.

|task apply_reset; |

| begin |

| reset = 1 ; |

| #СYCLE ; |

| reset = 0 ; |

| pc = 0 ; |

| end |

|endtask |

| |

| initial begin : prog_load |

| $readmemb("sisc.prog",MEM); |

| $monitor("%d %d %h %h %h",$time,pc, |

| RFILE[0],RFILE[1],RFILE[2]); |

| apply_reset; |

| end |

`-------------------------------------------------'

Рис. 3.6 Процесс инициализации

Системная задача Verilog $readmemb используется для загрузки тестовой программы из файла данных ASCII в массив памяти mem.

Установочная последовательность включается с помощью задачи apply_reset (рис. 3.6). В данном примере установочная последовательность является очень простой, она переключает сигнал сброса и устанавливает счетчик команд в 0.

Задача $monitor обеспечивает отслеживание информации о времени моделирования, счетчике команд и определенных регистров из регистрового файла.

Функции и задачи

После описаний, процесса main и инициализации системы следующий шаг состоит в том, чтобы создать задачи и функции,обеспечивающие функционирование процессора. Функциями в нашем описании являются getsrc, getdst и checkcond. Задачами являются fetch, execute, write_results, set_condcode, clear_condcode и apply_reset.

Задача execute представляет наибольший интерес (см.рис.3.7). Она использует оператор выбора case для декодирования кода операции OPCODE и обеспечивает выполнение определенных действий, которые соответствуют каждой отдельной команде.

Обратите внимание на использование символьных имен для доступа к различным полям регистра команд. Если расположение или длина полей должна быть изменена, то необходимо будет скорректировать только описания, при этом сами задачи останутся неизменными.

Пустая операция (nop) увеличивает время моделирования на один такт без каких-либо других действий. Команда halt выполняется с помощью системной задачи $stop. Действия при несовпадении обеспечивает механизм обнаружения неправильных команд. Команда branch показывает, как вызывается функция в Verilog.

Команды load и store осуществляют доступ к памяти по простому протоколу. Команды shift и rotate также реализуют довольно сложные аппаратные операции с помощью простых функциональных конструкций Verilog.

Команды add и multiply на первый взгляд кажутся очень простыми. Однако это не так. Результат сложения двух чисел может быть на один разряд больше размера большего из двух слагаемых, а размер произведения может быть равен сумме размеров множителя и множимого. Назначение произведению двух чисел, размер которых равен WIDTH, (WIDTH+1)-разрядного регистра сохраняет младшие WIDTH+1 разряды результата, и только WIDTH младших разрядов сохраняются в памяти или в ячейке регистрового файла.

Одно из решений этой проблемы состоит в том, чтобы использовать для вычислений более длинные регистры (размером 2*WIDTH) и пару регистров или две ячейки памяти для хранения результата. Другое решение - уменьшить размер операндов таким образом, чтобы он не превышал половину размера поля результата. Еще один подход - определить, что точностью результата являются младшие WIDTH+1 разряда. В примере с SISC процессором мы выбрали точность результата в 32 разряда.

Важно помнить, что в Verilog арифметические операции выполняются в дополнительном коде.

,---------------------------------------------------------.

| task execute ; |

| begin |

| case (`OPCODE) |

| `NOP :; |

| `BRA : begin |

| if (checkcond(`CCODE)) pc = `DST ; |

| end |

| `HLT : begin |

| $display("Halt ..."); $stop; |

| end |

| `LD : begin |

| clearcondcode ; |

| if (`SRC) RFILE[`DST] =`SRC ; |

| else RFILE[`DST] = MEM[`SRC]; |

| setcondcode({1'b0,RFILE[`DST]}) ; |

| end |

| `STR : begin |

| clearcondcode ; |

| if (`SRC) MEM[`DST] = `SRC ; |

| else MEM[`DST] = RFILE[`SRC] ; |

| end |

| `ADD : begin |

| clearcondcode ; |

| src1 =getsrc(ir) ; |

| src2 = getdst(ir) ; |

| result = src1 + src2 ; |

| setcondcode(result); |

| end |

| . . . |

| . . . |

| default: $display("Invalid Opcode found"); |

| endcase |

|end |

|endtask |

`---------------------------------------------------------'

Рис. 3.7 Задача execute

Тестовая программа

Когда модель написана, мы должны подготовить тестовую программу для моделирования. Для этого используется программа, подсчитывающая количество "1" в двоичном коде. Двоичный код такой программы представлен на рис. 3.21. Эта программа

хранится в файле sisc.prog и загружается в массив памяти mem с помощью системной задачи $readmemb. Код программы в 16-ричном виде может быть загружен с помощью системной задачи $readmemh.

Запуск модели

С помощью тестовой программы для нашей модели мы можем продемонстрировать, каким образом команды выбираются из памяти, дешифрируются и выполняются, а также как запоминаются результаты. Эта имитирующая модель сейчас может быть использована для разработки дополнительной диагностики, системного программного обеспечения или прикладных программ для целевой аппаратуры . Начать нам позволит следующая команда в ответ на приглашение операционной системы:

%verilog sisc_instruction_set_model.v

где sisc_instruction_set_model.v является именем файла, содержащего описание на Verilog модели системы команд SISC процессора.

Существуют две стадии при моделировании в среде Verilog. Первая состоит в устранении всех ошибок, выявляемых при трансляции и установке связей. Ошибки трансляции обусловлены в основном синтаксическими ошибками и неописанными структурами. Ошибки связей связаны с размерами порта, созданием модуля, потерей связей и т.п.

Синтаксически правильное описание модели на Verilog теперь может быть промоделировано для определения правильности работы. Обычно этот этап занимает гораздо больше времени, чем первый. Использование диалоговой отладки и некоторых уже

испробованных средств может значительно сократить это время. Некоторые полезные отладочные средства будут описаны ниже.

ОТЛАДКА

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

Пример задачи disprm, предназначенной для облегчения отладки модели, показан на рис. 3.8. Она используется для отображения содержимого некоторого диапазона регистров и ячеек памяти. Достаточно неудобно набирать в теле задачи соответствующую команду каждый раз, когда необходимо посмотреть содержимое ячеек памяти, скажем, 20-ой, 21-ой и 22-ой; кроме того, набор на клавиатуре отдельных команд $display также занимает много времени, если конечный адрес значительно отличается от начального.

,----------------------------------------------------.

|// Для отображения содержимого регистров |

|// и ячеек памяти |

| task disprm ; |

| input rm ; |

| input [ADDRSIZE-1:0] adr1, adr2 ; |

| begin |

| if (rm ==`REGTYPE) begin |

| while (adr2 >= adr1) begin |

| $display("REGFILE[%d]=%d\n", |

| adr1,RFILE[adr1]) ; |

| adr1 = adr1 + 1 ; |

| end |

| end |

| else begin |

| while (adr2 >= adr1) begin |

| $display("MEM[%d]=%d\n", |

| adr1,MEM[adr1]) ; |

| adr1 = adr1 + 1 ; |

| end |

| end |

|end |

|endtask |

`----------------------------------------------------'

Рис. 3.8 Задача для печати состояний при отладке

Задача apply_reset используется для перезапуска моделирования без необходимости выхода и нового входа. Она может быть значительно улучшена установкой всех регистров и ячеек памяти в неопределенное состояние (х), ноль или состояние заранее определенного набора разрядов. Задача apply_reset также может быть изменена для включения системной задачи $readmemb, чтобы при каждом запуске системы загружалась новая программа.

МОДЕЛИРОВАНИЕ УПРАВЛЕНИЯ КОНВЕЙЕРОМ

До этого момента мы моделировали SISC архитектуру на уровне системы команд. Сейчас давайте обсудим управление процессором. Мы кратко опишем принцип конвейерной обработки для создания функциональной модели путем внесения изменений

в уже разработанную модель SISC архитектуры.

Что такое конвейер ?

В основном конвейерная архитектура применяется для использования внутреннего параллелизма или для обеспечения ресурсами при необходимости введения параллелизма. Простой конвейер может быть основан на одновременности выборки одной команды из памяти и выполнения выбранной перед этим команды.

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

,----------------------------------------------------.

| такт # без конвейера с конвейером |

| 1 F1 F1 |

| 2 E1 F2 E1 |

| 3 W1 F3 E2 W1 |

| 4 F2 F4 E3 W2 |

| 5 E2 F5 E4 W3 |

| 6 W2 E5 W4 |

| 7 F3 W5 |

| 8 E3 |

| 9 W3 |

| 10 F4 |

| 11 E4 |

| 12 W4 F12 |

| 13 F5 F13 E12 W11 |

| 14 E5 F14 E13 W12 |

| 15 W5 F15 E14 W13 |

| |

| Без конвейера 5 команд выполняются за 15 тактов |

| (кол-во команд)*3 |

| С конвейером 5 команд выполняются за 7 тактов |

| (кол-во команд)+2 |

| |

|Обозначения: F=выборка, Е=выполнение, W=запись |

| результата |

`----------------------------------------------------'

Рис. 3.9 Табличное представление 3-х уровневого конвейера

Рис. 3.9 поясняет, каким образом будет обрабатывать команды 3-х уровневая конвейерная архитектура. Уровнями являются выборка (F), выполнение (Е) и запись (W). Предполагается, что для выполнения на каждом уровне необходим один такт. Таким образом, процессор без конвейерной обработки выполняет каждую команду за три такта, тогда как процессор с конвейером может выполнять в среднем одну команду каждый такт. Для увеличения производительности может использоваться конвейер с большим количеством уровней.

Для повышения производительности следующая команда готовится к выполнению (локальность ссылок). Однако, даже в достаточно простых случаях существуют определенные сложности. Например, выполнение условий команды перехода означает, что за командой перехода должна выполняться не следующая по адресу, а совсем другая команда. Поэтому та команда, которая уже была выбрана (заранее выбранная команда), должна быть уничтожена, и должна быть выбрана та команда, на которую происходит переход. Так как процессор должен уничтожить заранее выбранную команду, исполнительное устройство простаивает в течении одного или большего количества тактов ( в зависимости от того, сколько тактов занимает выборка команды из памяти).

В процессоре с большим количеством уровней конвейера могут потребоваться более сложные действия для правильного завершения команд в конвейере. Это явление обычно называют "очисткой конвейера". Команда halt также очищает конвейер.

Другой причиной снижения эффективности конвейера являются команды load и store. Так как эти команды необходимы для доступа в память и передачи данных в или из регистрового файла внутри процессора, процессор не может выбирать следующую команду. Он пропускает цикл выборки, что в свою очередь вызывает потерю цикла выполнения, если у процессора есть исходная информация только для заранее выбранной команды. Для заполнения конвейера используются многопортовые память и регистровый файл.

Функциональное разбиение

Сейчас давайте усовершенствуем разработанную ранее SISC модель, добавив в нее 3-х уровневый конвейер. Тремя уровнями конвейерной обработки являются выборка команды (fetch), выполнение команды (execute) и запись результата (write_result). Когда выполняется текущая команда, происходит выборка следующей команды и запись в регистровый файл результата предыдущей команды. Но этот порядок нарушается при командах перехода (brаnch), считывания из памяти (load) и записи в память (store).

,------------------------------------------------.

| always @(posedge clock) begin : phase1_loop |

| if (!reset) begin |

| fetched = 0 ; |

| executed = 0 ; |

| if (!queue_full && !mem_access) |

| -> do_fetch ; |

| if (qsize || mem_access) |

| -> do_execute ; |

| if (result_ready) |

| -> do_write_results ; |

| end |

| end |

`------------------------------------------------'

Рис. 3.10 Запуск одновременных событий

Мы упростили конвейер SISC процессора, сделав его синхронным. Как видно из рис. 3.10 по переднему фронту синхросигнала (по фазе 1, если генератор тактовых импульсов является двухфазным) одновременно порождаются три события - do_fetch, do_execute и do_write_result. Блок phase1_loop получается изменением блока main_process разработанной ранее SISC модели. По отрицательному фронту синхросигнала информация передается между уровнями конвейера. На рис. 3.11 показаны дополнительные описания переменных, которые потребуются для модели конвейера. Последующее объяснение модели конвейера будет содержать ссылки на эти регистры (reg), провода(wire) и события (event).

,-------------------------------------------------------.

| parameter QDEPTH=3; // Длина очереди команд |

| // Очередь команд и регистр команд для записи |

| reg [WIDTH-1:0] IR_Queue[0:QDEPTH-1],wir; |

| |

| // Копия результата и указатели выборки и выполнения |

| reg [WIDTH:0] wresult; |

| reg [2:0] eptr, fptr, qsize ; |

| |

| // Различные управляющие сигналы/признаки |

| reg mem_access, branch_taken, halt_found ; |

| reg result_ready ; |

| reg executed, fetched ; |

| reg queue_full ; |

| |

| event do_fetch, do_execute, do_write_results; |

`-------------------------------------------------------'

Рис. 3.11 Дополнительные описания для моделирования конвейера

Обратите внимание, что порядок операторов if в блоке phase1_loop не является существенным, так как эти операторы только порождают события, а соответствующие события и задачи не обязательно выполняются в этом порядке. Эти события моделируют работу трех функциональных модулей программы.

Устройство выборки

Основная функция устройства выборки состоит в том, что бы передать команду из памяти в процессор и обеспечить доступ исполнительного устройства к ней. Чтобы исполнительное устройство не простаивало, необходимо организовать очередь команд с предварительной их выборкой. Это достигается использованием очереди IR_Queue. Мы выбрали длину очереди равную трем, что соответствует количеству уровней конвейера. 2-х разрядный регистр fptr содержит ссылку на текущую позицию в очереди IR_Queue, в которую должна быть помещена последняя выбранная команда. Содержимое регистра qsize определяет количество заранее выбранных команд. Этот регистр используется также для определения того, является ли очередь IR_Queue заполненной или пустой. Если в очереди нет команд, уровень выполнения команд не может работать, что вызывает пропуск циклов выполнения команд и задержку в конвейере; в то же время, если очередь IR_Queue полностью заполнена, то считывание из памяти новой команды будет затирать еще не выполненную команду в очереди.

Признак mem_access дает сигнал пропуска такта устройству выборки при выполнении команды load или store. Это отражает оператор if, управляющий событием do_fetch. Задача fetch (см. рис. 3.12) была изменена так, чтобы выбранная команда помещалась в очередь IR_Queue, а не записывалась непосредственно в регистр команд IR. При записи команды в очередь также устанавливается в "1" признак выборки, указывая тем самым на завершение цикла выборки.

,-----------------------------------------.

| task fetch; |

| begin |

| IR_Queue[fptr]=MEM[pc]; |

| fetched=1; |

| end |

| endtask |

`-----------------------------------------'

Рис. 3.12 Задача выборки fetch

Исполнительное устройство

Исполнительное устройство подобно описанному ранее. Оно дешифрирует и выполняет текущую команду. Предполагается, что все команды, кроме load и store, выполняются за один такт. Предположим также следующее: все команды занимают в памяти одно слово, операнды всех арифметических команд - сложения, умножения, дополнения, сдвига и циклического сдвига - являются либо непосредственными (заданными в слове команды), либо хранятся в регистровом файле.

При дальнейшем изучении арифметических операций оказывается, что в АЛУ необходимы два входных регистра - src1 и src2 и один регистр на выходе - result. Так как адреса операндов и адрес результата арифметических команд являются адресами в регистровом файле, последний должен иметь три независимых порта: два для считывания операндов и один для записи результата. Напомним, что результат предыдущей команды записывается в регистровый файл одновременно со считыванием операндов для текущей команды.

В рассматриваемой архитектуре до сих пор не учитывалась память. Работа с памятью имитируется в исполнительном устройстве командами load и store, занимающими по два такта. На рис. 3.13 представлена часть этой модели, показывающая, как моделируется команда load. При выполнении команды load исполнительное устройство устанавливает в "1" признак mem_access для резервирования доступа в память в следующем такте. Устройство выборки команды в текущем такте завершает выборку команды из памяти. В следующем такте оно простаивает, а исполнительное устройство обратится в память. Исполнительное устройство выполняет команду load предыдущего такта, так как в регистр команд не записывается новая информация, пока признак mem_access находится в состоянии "1". По окончании обмена с памятью исполнительное устройство сбрасывает признак mem_access, позволяя тем самым себе записать в регистр команд следующую команду из очереди, а устройству выборки пополнить очередь новыми командами из памяти.

Исполнительное устройство пропускает такт при выполнении перехода или, если очередь команд пуста. Очередь команд в SISC процессоре становится пустой только в двух случаях: при переходе (branch) и в случае команды останова (halt). Показанная на рис. 3.7 задача flush_queue описывает необходимые при этом действия.

,-------------------------------------------------------.

|if (!mem_access) ir=IR_Queue[eptr]; |

| `LD : begin |

| if (mem_access == 0) // Резервирование |

| mem_access = 1 ; // следующего такта |

| else begin // Доступ к памяти |

| ...... // в следующем |

| // такте |

| end |

|end |

`-------------------------------------------------------'

Рис. 3.13 Доступ к памяти в случае команды load

,-------------------------------------------------.

| task flush_queue ; |

| begin |

| // pc уже изменен выполнением команды branch |

| fptr = 0 ; |

| eptr = 0 ; |

| qsize = 0 ; |

| branch_taken = 0 ; |

| end |

| endtask |

`-------------------------------------------------'

Рис. 3.14 Очистка конвейера

Остальная часть модели исполнительного устройства остается неизменной для всех арифметических операций, как показано в задаче task на рис. 3.7. Задачи set_condcode, clear_condcode и функция checkcond также не изменяются при добавлении конвейерной обработки.

Устройство записи результатов

Уровни выполнения и записи результата связаны с помощью двух регистров: wresult и wir. Если результат нужно записать в регистровый файл, исполнительное устройство устанавливает признак result_ready. Результат переписывается из регистра результата result в регистр wresult, а содержимое регистра команд ir - в wir. Устройство записи переписывает результат из регистра wresult по адресу назначения в регистровом файле, который определяется в следующем такте полем dest в регистре wir. Так как wir является копией команды, при выполнении которой был получен данный результат, поле dest содержит адрес назначения в регистровом файле для пре-

дыдущей команды.

Следует отметить, что мы упростили устройство записи с помощью копирования текущей команды из регистра ir в wir вместо того, чтобы организовывать указатель в очереди команд. Задача copy_result (см. рис. 3.15) определяет, каким образом результат из исполнительного устройства передается в устройство записи, а рис. 3.16 показывает, как происходит запись результата по адресу назначения в регистровом файле.

,----------------------------------------------------.

| task copy_results ; |

| begin |

| if ((`OPCODE >= `ADD) && (`OPCODE < `HLT)) begin |

| setcondcode(result) ; |

| wresult = result ; |

| wir = ir ; |

| result_ready = 1 ; |

| end |

| end |

| endtask |

`----------------------------------------------------'

Рис. 3.15 Копирование результата и команды

Существует два альтернативных подхода, исключающие необходимость копирования результата из одного регистра результата в другой. Один подход состоит в том, чтобы результат записывался в регистр результата с помощью исполнительного устройства во время отрицательной фазы синхросигнала, а содержимое регистра результата копировалось в регистровый файл во время положительной фазы следующего такта с помощью устройства записи. Еще одна возможность - полностью удалить уровень записи в конвейере и записывать результат из АЛУ непосредственно в регистровый файл. С помощью моделирования можно исследовать каждый из этих подходов.

,--------------------------------------------------------.

| task write_result ; |

| begin |

| if ((`WOPCODE >= `ADD) && (`WOPCODE < `HLT)) begin |

| if (`WDSTTYPE == `REGTYPE) |

| RFILE[`WDST] = wresult; |

| else MEM[`WDST] = wresult ; |

| result_ready = 0 ; |

| end |

| end |

| endtask |

`--------------------------------------------------------'

Рис. 3.16 Запись результата в регистровый файл

Операции управления фазы 2

В общем SISC процессор является синхронной схемой. Описанные выше функциональные блоки соответствуют трем уровням конвейерной обработки. Во время положительной фазы синхроимпульса происходит следующее:

* изменяется счетчик команд (рс) в зависимости от того,

выполняется или нет команда перехода.

* устанавливаются коды условий вновь вычисленного

результата.

* копируется содержимое регистра команд (ir) и регистра

результата в теневые регистры (wir и wresult соот-

ветственно).

* обновляются указатели выборки и выполнения (eptr и

fptr) в очереди команд.

,--------------------------------------------------------.

| task set_pointers ; // Управление указателями очереди |

| begin |

| case ({fetched,executed}) |

| 2'b00 : ; // Пропуск такта выборки |

| 2'b01 : begin // Нет выборки |

| qsize = qsize - 1 ; |

| eptr = (eptr + 1)%QDEPTH ; |

| end |

| 2'b10 : begin // Нет выполнения |

| qsize = qsize + 1 ; |

| fptr = (fptr + 1) % QDEPTH ; |

| end |

| 2'b11 : begin // Выборка и выполнение |

| eptr = (eptr + 1)%QDEPTH ; |

| fptr = (fptr + 1) % QDEPTH ; |

| end |

| endcase |

| end |

| endtask |

| always @(negedge clock) begin : phase2_block |

| if (!reset) begin |

| if (!mem_access && !branch_taken) |

| copy_results ; |

| if (branch_taken) pc = `DST ; |

| else if (!mem_access) pc = pc+1;... |

| if (branch_taken || halt_found) |

| flush_queue ; |

| else set_pointers ; |

| if (halt_found) begin |

| $stop ; |

| halt_found = 0 ; |

| end |

| end |

| end |

`--------------------------------------------------------'

Рис. 3.17 Операции управления фазы 2

Все эти действия показаны на рис. 3.17. Они выполняются задачей set_pointers и процессом phase2_loop.

Очередь команд необходимо очищать, но эта процедура откладывается до отрицательной половины такта, так как надо закончить выполнение команды, находящейся в конвейере. По той же причине в случае команды halt моделирование не останавливается до тех пор, пока не будут проделаны все действия в отрицательной половине такта. Это дает возможность устройству записи запоминать результат предыдущей команды во время положительной половины следующего такта, когда исполнительное устройство устанавливает одноразрядный признак halt_found.

Проблема взаимных блокировок

В модели конвейерной архитектуры существует неочевидная проблема. Ее обычно называют "блокировкой регистра". Эта проблема проявляется, когда за командой, изменяющей содержимое какого-либо регистра в регистровом файле, следует команда, которая пытается произвести считывание из того же самого регистра. В конвейерной архитектуре с большим количеством уровней проявление этого факта может быть более сложным и трудно распознаваемым.

Ситуацию блокировки регистра лучше объяснить на примере. Рассмотрим два программных сегмента, показанные на рис. 3.18. В первом сегменте не существует конфликта из-за ресурса - регистра R2 в регистровом файле при условии, что регистровый файл является многопортовым.

Ситуация изменяется с вторым программным сегментом. Первая команда (I3) считывает содержимое двух регистров (R1 и R2) из регистрового файла и записывает результат сложения в регистр R1. Вторая команда (I4) считывает содержимое регистра R1 и запоминает его дополнение в регистре R3. Из-за одновременной работы исполнительного устройства и устройства записи исполнительное устройство может считать неверное значение из регистра R1 во время выполнения команды I4. (Результат команды I3 в это время будет записываться в регистр R1.)

,-------------------------------------------.

| Первый программный сегмент |

| I1: ADD R1, R2 // R1= R1 + R2 |

| I2: CMP R3, R2 // R3= ~R2 |

| |

| Второй программный сегмент |

| I3: ADD R1, R2 // R1= R1 + R2 |

| I4: CMP R3, R1 // R3 = ~R1 |

`-------------------------------------------'

Рис. 3.18 Проблема блокировки

Если величина из R1 считывается до того, как закончится запись результата, I4 будет оперировать с устаревшим содержимым регистра R1. В реальной аппаратуре это может привести к сбою или к гонке сигналов в зависимости от реализации команд записи и чтения для многопортового регистрового файла. Ниже будут рассмотрены два возможных решения.

Простейшее решение - вставить пустую команду nop между двумя командами, вызывающими блокировку регистра. Преимущество этого решения состоит в том, что не нужно изменять архитектуру и проект. Однако, есть и недостаток: выполнение дополнительных команд, вызывающее потерю одного такта на каждую блокировку регистра и уменьшение производительности. Это также потребует изменений в математическом обеспечении, в частности, в SISC компиляторе и оптимизаторе. Модифицированный второй сегмент программы представлен на рис. 3.19.

,-----------------------------------------------------.

| I3: ADD R1, R2 // R1 = R1 + R2 |

| IX: NOP // Избежание блокировки |

| I4: CMP R3, R1 // R3= ~R1 |

`-----------------------------------------------------'

Рис. 3.19 Измененный второй программный сегмент

,--------------------------------------------------.

| reg bypass; |

| |

| function [31:0] getsrc; |

| input [31:0] i ; |

| begin |

| if (bypass) getsrc = result ; |

| else if (`SRCTYPE === `REGTYPE) |

| getsrc = RFILE[`SRC] ; |

| else getsrc = `SRC ; // непосредственный тип |

| end |

| endfunction |

| |

| function [31:0] getdst; |

| input [31:0] i ; |

| begin |

| if (bypass) getdst=result; |

| else if (`DSTTYPE === `REGTYPE) |

| getdst = RFILE[`DST] ; |

| else $display("Error : Immediate data |

| cannot be destination."); |

| end |

| endfunction |

| |

| always @(do_execute) begin : execute_block |

| if (!mem_access ) begin |

| ir=IR_Queue[eptr]; |

| bypass=((`SCR == `WDST) || |

| (`DST == `WDST)); |

| end |

| execute ; |

| if (!mem_access) executed = 1; |

| end |

`--------------------------------------------------'

Рис. 3.20 Измененные функции и процесс выполнения для метода обхода

Другой метод состоит в реализации в схеме дополнительной логики для распознавания условий регистровой блокировки. После определения ситуации аппаратура может копировать содержимое регистра результата предыдущей команды в один из операндов (src1 или src2) текущей команды. В то же время должно быть предотвращено чтение операнда из заблокированного регистра для избежания затирания правильного значения. Этот метод известен как метод "обхода". Часть модели, показанная на рис. 3.20, реализует метод обхода в SISC архитектуре для предотвращения блокировки.

Следует рассмотреть случай, подобный этому, когда регистр назначения команды используется как источник для команды store, следующей за данной командой. Анализ результатов моделирования следует использовать для определения того, какой из рассмотренных подходов лучше.

ГЕНЕРАЦИЯ ТЕСТОВЫХ ВЕКТОРОВ

После того, как написана модель какой-либо системы, важно убедиться в том, что она написана правильно. При тестировании выявляются некоторые неочевидные ситуации, связанные с архитектурой или реализацией, например регистровая блокировка. Это одинаково верно для моделей от простого вентиля NAND до сложного микропроцессора и до значительно более сложных микропроцессорных систем и даже сетей таких систем. Если модели небольшие и легки для понимания, применяются простые методы проверки правильности написания моделей. В этом случае возможно полное тестирование. В случае сложных систем не всегда можно сгенерировать все возможные комбинации. В качестве тестовых векторов для проверки отдельных частей системы используется большое количество диагностических программ.

Эти диагностические программы могут быть написаны на языке программирования высокого уровня, таком как Си, и написанный для конкретной системы транслятор может генерировать соответствующий объектный (машинный) код. Если в распоряжении нет транслятора, то для создания машинного кода закодированных вручную программ может быть использован простой ассемблер для конечной системы команд. Такие средства облегчают выполнение промежуточной задачи создания тестовых векторов из "0" и "1", при выполнении которой очень легко допустить ошибку и которая отнимает очень много времени. Еще один недостаток ручного кодирования часто проявляется при изменении формата команд (порядка различных полей в команде).

После трансляции диагностической программы в последовательность команд эта программа загружается в память, отведенную для моделирования, точно так же, как выполняемая программа загружается в системную память. Запуск системы вызывает извлечение команды из памяти, начиная с команды, на которую указывает счетчик команд. Сама по себе команда подобна подаче тестового вектора на выводы процессора. Выполнение команды в процессоре управляет извлечением следующих команд (тестовых векторов).

Моделирующая программа Verilog-XL может загружать диагностическую программу, записанную в ASCII файле, в моделируемую память с помощью одной из двух системных задач: $readmemb или $readmemh. Задача $readmemb используется, когда команды представлены в двоичном виде, а $readmemh – когда в 16-ричном.Для удобства чтения файлы программы могут содержать комментарии, символы подчеркивания (_) и пробелы.

Обычно программа загружается в моделируемую память в начале моделирования. Это происходит при помощи системной задачи $readmemb в операторе initial (см. на рис. 3.6 блок, помеченный как prog_load). Образец программы, которая вычисляет количество "1" в двоичном коде - на рис. 3.21. Здесь конец программы отмечен остановкой моделирования для возможной проверки содержимого регистров, ячеек памяти и других сигналов и переменных. Для ускорения процесса верификации и выполнения программы без ошибок существует общепринятая практика сравнения вычисленных результатов с ожидаемыми. В диагностической программе, содержащей сотни или тысячи команд, такие сравнения могут быть разбросаны по различным частям программы.

----------------------------------------------------------.

// Программа для подсчета количества '1 |

// в заданном двоичном числе |

// |

0010_1000_0000_0000_0000_0000_0000_0001// LD R1,#0 |

0010_0000_0000_0000_1001_0000_0000_0000// LD R2,NMBR |

0001_0010_0000_0000_0000_0000_0000_0100//STRT:BRA L1 |

0100_1000_0000_0000_0001_0000_0000_0001// ADD R1,#1 |

0111_1000_0000_0000_0001_0000_0000_0000//L1 :SHF R2,#1 |

0001_0100_0000_0000_0000_0000_0000_0111// BRA L2,ZERO |

0001_0000_0000_0000_0000_0000_0000_0010// BRA STRT,ALW|

0011_0000_0000_0000_0001_0000_0000_1010//L2 :STR RSLT, R2|

1001_1111_1111_1111_1111_1111_1111_1111// HLT |

0101_0101_0101_0101_1010_1010_1010_1010//NMBR:5555аааa |

0000_0000_0000_0000_0000_0000_0000_0000//RSLT:00000000 |

----------------------------------------------------------'

Рис. 3.21 Программа на ассемблере в качестве тестового вектора

Когда имитационная модель является очень большой, выход и повторный вход в моделирующую программу для загрузки новой диагностической программы может занимать много времени. Если смоделировать поведение сигнала reset для возвращения машины в первоначальное состояние, системная задача $readmemb может быть использована в диалоговом режиме для загрузки новой программы при окончании работы текущей программы.

РЕЗЮМЕ

В этой главе было показано, как моделировать процессор на СБИС с помощью ЯОА Verilog. Модель была усовершенствована от первоначального описания архитектуры без конвейера до функционального описания на высоком уровне с тремя уровнями конвейерной обработки. Была подробно рассмотрена проблема регистровой блокировки для того, чтобы показать пользу моделирования при решении проблем или принятии конструкторских решений. Были также представлены понятия создания тестового вектора.

Полные описания обеих этих моделей на ЯОА Verilog - на рис. 3.22. Первая часть содержит описания, функции и задачи, общие для двух моделей. Следующая часть является описанием модели без конвейера, за которым следует модель процессора, включающая конвейерную обработку.

/*

* Описания, функции и задачи

* общие для обеих SISC моделей.

* sisc_declarations.v

*

* %W% %G% -- для управления версиями

*/

// Описания параметров

parameter CYCLE = 10 ; // Время такта

parameter WIDTH = 32 ; //Размер шины данных

parameter ADDRSIZE = 12 ; // Размер полей адреса

parameter MEMSIZE = (1<<ADDRSIZE);//Максимальный

//размер памяти

parameter MAXREGS = 16 ; //Максимальное количество

//регистров в регистровом

//файле

parameter SBITS = 5 ; //Разряды регистра состояния

// Описание регистров и памяти

reg [WIDTH-1:0] MEM[0:MEMSIZE-1], // Память

RFILE[0:MAXREGS-1], // Регистровый файл

ir, //Регистр команд

src1, src2 ; //Регистры операндов АЛУ

reg [WIDTH:0] result ; //Регистр результата АЛУ

reg [SBITS-1:0] psr ; //Регистр состояния процессора

reg [ADDRSIZE-1:0] pc ; //Счетчик команд

reg dir ; //направление циклического сдвига

reg reset ; //Сброс системы

integer i ; // для диалоговой отладки

// Основные определения

`define TRUE 1

`define FALSE 0

`define DEBUG_ON debug=1

`define DEBUG_OFF debug=0

//Определения полей команды

`define OPCODE ir[31:28]

`define SRC ir[23:12]

`define DST ir[11:0]

`define SRCTYPE ir[27] //тип источника,0=регистр (память

// для load),1=непосредственный

//операнд (imm)

`define DSTTYPE ir[26] //тип назначения, 0=регистр, 1=imm

`define CCODE ir[27:24]

`define SRCNT ir[23:12] //Величина сдвига для команд shift/

//rotate -=влево, +=вправо

// Типы операндов

`define REGTYPE 0

`define IMMTYPE 1

//Определения кодов операций для каждой команды

`define NOP 4'b0000

`define BRA 4'b0001

`define LD 4'b0010

`define STR 4'b0011

`define ADD 4'b0100

`define MUL 4'b0101

`define CMP 4'b0110

`define SHF 4'b0111

`define ROT 4'b1000

`define HLT 4'b1001

//Определение полей кодов условий

`define CARRY psr[0]

`define EVEN psr[1]

`define PARITY psr[2]

`define ZERO psr[3]

`define NEG psr[4]

//Определение кодов условий

//Код условия устанавливается, когда ...

`define CCС 0 //При вычислении результата был перенос

`define CCE 1 //Результат - четное число

`define CCP 2 //Cвертка результата по четности равна

// единице

`define CCZ 3 //Результат равен нулю

`define CCN 4 //Результат меньше нуля

`define CCA 5 //Всегда

`define RIGHT 0 // Сдвиг вправо в случае команд

// Rotate/Shift

`define LEFT 1 // Сдвиг влево в случае команд

// Rotate/Shift

// Функции для операндов и результата АЛУ

function [WIDTH-1:0] getsrc;

input [WIDTH-1:0] in ;

begin

if (`SRCTYPE === `REGTYPE) begin

getsrc = RFILE[`SRC] ;

end

else begin // непосредственный тип

getsrc = `SRC ;

end

end

endfunction

function [WIDTH-1:0] getdst;

input [WIDTH-1:0] in ;

begin

if (`DSTTYPE === `REGTYPE) begin

getdst = RFILE[`DST] ;

end

else begin //непосредственный тип

$display("Error:Immediate data cant be destination.") ;

end

end

endfunction

// Функции/задачи для кодов условий

function checkcond; // выдает 1, если код условия установлен

input [4:0] ccode ;

begin

case (ccode)

`CCA : checkcond = 1 ;

`CCC : checkcond = `CARRY ;

`CCE : checkcond = `EVEN ;

`CCP : checkcond = `PARITY ;

`CCZ : checkcond = `ZERO ;

`CCN : checkcond = `NEG ;

endcase

end

endfunction

task clearcondcode ; // Сбрасывает коды условий в PSR

begin

psr = 0 ;

end

endtask

task setcondcode ; // Вычисляет и устанавливает

// коды условий

input [WIDTH:0] res ;

begin

`CARRY = res[WIDTH] ;

`EVEN = ~res[0] ;

`PARITY = ^res ;

`ZERO = ~(|res) ;

`NEG = res[WIDTH-1] ;

end

endtask

/*=================================================

*

* SISC модель без конвейера

*

* sisc_instruction_set_model.v

* Полезна для моделирования системы команд

* Три главных задачи - fetch,execute,write.

*

* %W% %G% -- Для управления версией

*/

module instruction_set_model;

// Здесь необходимо включить файл sisc_declarations.v

// В этом модуле справедливы все общие описания,

// работают все общие функции и задачи.

// Остальная часть модели представлена ниже.

// Главные задачи - fetch, execute, write_result

task fetch ; //Выбрать команду и увеличить на 1

// счетчик команд

begin

ir = MEM[pc] ;

pc = pc + 1 ;

end

endtask

task execute ;// Дешифрировать и выполнить команду

begin

case (`OPCODE)

`NOP : ;

`BRA : begin

if (checkcond(`CCODE) == 1) pc = `DST ;

end

`LD : begin

clearcondcode ;

if (`SRC) RFILE[`DST] = `SRC ;

else RFILE[`DST] = MEM[`SRC] ;

setcondcode({1'b0,RFILE[`DST]}) ;

end

`STR : begin

clearcondcode ;

if (`SRC) MEM[`DST] = `SRC ;

else MEM[`DST] = RFILE[`SRC] ;

end

`ADD : begin

clearcondcode ;

src1 = getsrc(ir) ;

src2 = getdst(ir) ;

result = src1 + src2 ;

setcondcode(result) ;

end

`MUL : begin

clearcondcode ;

src1 = getsrc(ir) ;

src2 = getdst(ir) ;

result = src1 * src2 ;

setcondcode(result) ;

end

`CMP : begin

clearcondcode ;

src1 = getsrc(ir) ;

result = ~src1 ;

setcondcode(result) ;

end

`SHF : begin

clearcondcode ;

src1 = getsrc(ir) ;

src2 = getdst(ir) ;

i = src1[ADDRSIZE-1:0] ;

result = (i>=0) ? (src2 >> i) : (src2 << -i) ;

setcondcode(result) ;

end

`ROT : begin

clearcondcode ;

src1 = getsrc(ir) ;

src2 = getdst(ir) ;

dir = (src1[ADDRSIZE-1]==0) ? `RIGHT : `LEFT ;

i = (src1[ADDRSIZE-1]==0) ?

src1 : -src1[ADDRSIZE-1:0];

while (i > 0) begin

if (dir == `RIGHT) begin

result = src2 >> 1 ;

result[WIDTH-1] = src2[0] ;

end

else begin

result = src2 << 1 ;

result[0] = src2[WIDTH-1] ;

end

i = i - 1 ;

src2 = result ;

end

setcondcode(result) ;

end

`HLT : begin

$display("Halt ...") ;

$stop ;

end

default : $display("Error : Illegal Opcode .") ;

endcase

end

endtask

// Запись результата в регистровый файл или в память

task write_result ;

begin

if ((`OPCODE >= `ADD) && (`OPCODE < `HLT)) begin

if (`DSTTYPE == `REGTYPE) RFILE[`DST] = result ;

else MEM[`DST] = result ;

end

end

endtask

// Помощь при отладке ....

task apply_reset ;

begin

reset = 1 ;

#CYCLE

reset = 0 ;

pc = 0 ;

end

endtask

task disprm ;

input rm ;

input [ADDRSIZE-1:0] adr1, adr2 ;

begin

if (rm == `REGTYPE) begin

while (adr2 >= adr1) begin

$display("REGFILE[%d]=%d\n",adr1,RFILE[adr1]) ;

adr1 = adr1 + 1 ;

end

end

else begin

while (adr2 >= adr1) begin

$display("MEM[%d]=%d\n",adr1,MEM[adr1]) ;

adr1 = adr1 + 1 ;

end

end

end

endtask

// Блоки initial и always

initial begin : prog_load

$readmemb("sisc.prog",MEM) ;

$monitor("%d %d %h %h %h",

$time,pc,RFILE[0],RFILE[1],RFILE[2]) ;

apply_reset ;

end

always begin : main_process

if (!reset) begin

#CYCLE fetch ;

#CYCLE execute ;

#CYCLE write_result ;

end

else #CYCLE ;

end

endmodule

/*===========================================

*

*

* Модель SISC процессора с конвейером

*

*sisc_pipeline_model.v

*

* %W% %G% -- Для управления версией

*/

module pipeline_control ;

// Здесь должны быть включены все описания, функции и

//задачи, определенные ранее. Кроме того, следующие

//описания и объявления используются только для

//моделирования управления конвейера.

// Объявление параметров

parameter HALFCYCLE = (CYCLE/2) ; //Половина времени такта

parameter QDEPTH = 3 ; //Длина очереди команд

// Объявление дополнительных регистров для

// управления конвейером

reg [WIDTH-1:0] IR_Queue[0:QDEPTH-1], //Очередь команд

wir, //Регистр команды для уровня

//записи результата

reg [2:0] eptr, fptr, qsize ;// Указатели

reg clock ; //Системный генератор

//синхроимпульсов

reg [WIDTH:0] wresult; //Регистр результата АЛУ

//для уровня записи результата

// Различные признаки - линии управления

reg mem_access, branch_taken, halt_found ;

reg result_ready ;

reg executed, fetched ;

reg debug ;

wire queue_full ;

event do_fetch, do_execute, do_write_results ;

// Описание полей команды

`define WOPCODE wir[31:28]

`define WDST wir[11:0]

`define WDSTTYPE wir[26] // тип назначения, 0=регистр,

//1=imm

// Непрерывное присваивание для заполненной

// очереди (queue_full)

assign queue_full = (qsize == QDEPTH) ;

// Задачи и функции

task fetch ;

begin

IR_Queue[fptr] = MEM[pc] ;

fetched = 1 ;

end

endtask

task execute ;

begin

if (!mem_access) ir = IR_Queue[eptr] ;//Нужно новое

//содержимое

//регистра команд

//(IR)

case (`OPCODE)

`NOP : begin

if (debug) $display("Nop ...") ;

end

`BRA : begin

if (debug) $write("Branch ...") ;

if (checkcond(`CCODE) == 1) begin

pc = `DST ;

branch_taken = 1 ;

end

end

`LD : begin

if (mem_access == 0) begin

mem_access = 1 ; //Резервирование

//следующего такта

end

else begin // Доступ к памяти

if (debug) $display("Load ...") ;

clearcondcode ;

if (`SRCTYPE) begin

RFILE[`DST] = `SRC ;

end

else RFILE[`DST] = MEM[`SRC] ;

setcondcode({1'b0,RFILE[`DST]}) ;

mem_access = 0 ;

end

end

`STR : begin

if (mem_access == 0) begin

mem_access = 1 ; //Резервирование

//следующего такта

end

else begin //Доступ к памяти

if (debug) $display("Store ...") ;

clearcondcode ;

if (`SRCTYPE) begin

MEM[`DST] = `SRC ;

end

else MEM[`DST] = RFILE[`SRC] ;

mem_access = 0 ;

end

end

// Команды ADD, MUL, CMP, SHF и ROT

// моделируются так же, как и в первой модели.

`HLT : begin

$display("Halt ...") ;

halt_found = 1 ;

end

default : $display("Error : Wrong Opcode in instruction.") ;

endcase

if (!mem_access) executed = 1 ; // Команда выполнена?

end

endtask

task write_result ;

begin

if ((`WOPCODE >= `ADD) && (`WOPCODE < `HLT)) begin

if (`WDSTTYPE == `REGTYPE) RFILE[`WDST] = wresult ;

else MEM[`WDST] = wresult ;

result_ready = 0 ;

end

end

endtask

task flush_queue ;

begin

// Счетчик команд pc уже изменен при выполнении

//команды branch

fptr = 0 ;

Наши рекомендации