Хабрахабр

Проектирование процессора на языке Verilog

Спроектируем Little Man Computer на языке Verilog.

Статья про LMC была на Хабре.
Online симулятор этого компьютера здесь.

Сперва создадим устройство, позволяющее производить загрузку данных в ОЗУ.

Напишем модуль ОЗУ на языке Verilog.

module R0 #(parameter N = 2, M = 4)
(
input clk,
input [N-1:0] adr,
input [M-1:0] data_in,
output [M-1:0] RAM_out
);
reg [M-1:0] mem [2**N-1:0];
always @(posedge clk)
mem [adr] <= data_in;
assign RAM_out = mem[adr];
endmodule

Подключим счётчик к адресному входу ОЗУ

module R1 #(parameter N = 2, M = 4)
(
input Counter_clk, RAM_clk,
//input [N-1:0] adr,
input [M-1:0] data_in,
output [M-1:0] RAM_out
);
reg [1:0]counter;
always @(posedge Counter_clk) counter <= counter + 1; wire [N-1:0] adr; assign adr = counter; reg [M-1:0] mem [2**N-1:0];
always @(posedge RAM_clk) mem [adr] <= data_in;
assign RAM_out = mem[adr];
endmodule

Вот так выглядит схема в RTL Viewer

Теперь мы можем командой Counter_load переходить на адрес data_in.
Добавим в счетчик функцию загрузки.

//input Counter_load; wire [3:0] branch_adr; // адрес перехода
assign branch_adr = data_in; always @(posedge Counter_clk)
begin if(Counter_load) //по команде "Counter_load" переходим по адресу "branch_adr" counter <= branch_adr; else counter <= counter + 1; end

В отдельном модуле создаем 4bit'ный регистр (аккумулятор)

module register4
( input [3:0] reg_data, input reg_clk, output reg [3:0] q );
always @(posedge reg_clk) q <= reg_data;
endmodule

Добавим в общую схему аккумулятор, мультиплексор и сумматор

module R2 #(parameter ADDR_WIDTH = 2, DATA_WIDTH = 4)
(
input Counter_clk, Counter_load, RAM_clk,
input MUX_switch,
input Acc_clk, input [3:0] data_in, output [3:0] Acc,
output [DATA_WIDTH-1:0] RAM,
output reg [1:0] counter
);
wire [1:0] branch_adr;
assign branch_adr = data_in[1:0]; //Counter
always @(posedge Counter_clk)
begin if(Counter_load) counter <= branch_adr; else counter <= counter + 1; end wire [ADDR_WIDTH-1:0] adr;
assign adr = counter; //RAM
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0];
always @(posedge RAM_clk)
mem [adr] <= Acc;
assign RAM = mem[adr];
//sum
wire [3:0] sum;
assign sum = Acc + RAM;
//MUX
reg [3:0] MUX2; always @*
MUX2 = MUX_switch ? sum : data_in;
//Accumulator
register4 Acc_reg(
.reg_data(MUX2),
.reg_clk(Acc_clk),
.q(Acc)
);
endmodule

Добавим в основной модуль элемент «вычитатель»

wire [3:0] subtract;
assign subract = Acc - RAM ;

Замним двухвходовой мультиплексор четырёхвходовым

always @*
MUX4 = MUX_switch[1] ? (MUX_switch[0] ? RAM : subtract)
: (MUX_switch[0] ? sum : data_in);

Подключим к аккумулятору устройство вывода (4bit'ный регистр), также подключим к аккумулятору 2 флага:

Флаг «Ноль» — это лог. 1. Флаг поднимается, если содержимое Асс равно нулю.
2. элемент 4ИЛИ-НЕ. элемент НЕ на старшем разряде четырёхразрядного аккумулятора. Флаг «Ноль или Положительное число» — это лог. Флаг поднимается, если содержимое Асс больше или равно нулю.

//флаг "Ноль" output Z_flag;
assign Z_flag = ~(|Acc); // многовходовой вентиль ИЛИ
//флаг "Ноль или Положительное число"
output PZ_flag;
assign PZ_flag = ~Acc[3];

загрузка содержимого аккумулятора в устройство вывода data_out
2. Добавим три команды
1. загрузка адреса в счётчик, если поднят флаг «ноль или положительное число» (JMP if Acc>=0) загрузка адреса в счётчик, если поднят флаг «ноль» (JMP if Acc=0)
3.

module R3 #(parameter ADDR_WIDTH = 2, DATA_WIDTH = 4)
(
input Counter_clk, RAM_clk,
input JMP, Z_JMP, PZ_JMP,
input [1:0] MUX_switch,
input Acc_clk, input Output_clk,
input [3:0] data_in, output [3:0] Acc,
output [3:0] data_out,
output [DATA_WIDTH-1:0] RAM,
output Z_flag, PZ_flag,
output reg [1:0] counter
);
wire [1:0] branch_adr;
assign branch_adr = data_in[1:0]; wire Z,PZ;
assign Z = Z_flag & Z_JMP;
assign PZ = PZ_flag & PZ_JMP;
//Counter
always @(posedge Counter_clk)
begin if(JMP|Z|PZ) counter <= branch_adr; else counter <= counter + 1; end wire [ADDR_WIDTH-1:0] adr;
assign adr = counter; //RAM
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0];
always @(posedge RAM_clk)
mem [adr] <= Acc;
assign RAM = mem[adr];
//sum
wire [3:0] sum;
assign sum = Acc + RAM;
//subtract
wire [3:0] subtract;
assign subtract = Acc - RAM;
//MUX
reg [3:0] MUX4; always @*
MUX4 = MUX_switch[1] ? (MUX_switch[0] ? RAM : subtract)
: (MUX_switch[0] ? sum : data_in); register4 Acc_reg(
.reg_data(MUX4),
.reg_clk(Acc_clk),
.q(Acc)
);
register4 Output_reg(
.reg_data(Acc),
.reg_clk(Output_clk),
.q(data_out)
);
assign Z_flag = ~(|Acc);
assign PZ_flag = ~Acc[3]; endmodule

Поместим команды и адреса в одно ОЗУ, а данные — в другое.

Схему можно скачать отсюда.

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

Отмечу, что загрузка числа в аккумулятор Асс должна производиться после переключения мультиплексора MUX (для команд ADD, SUB, LDA), по спаду тактового сигнала.

в нашем компьютере следующая система команд Т.о.

48х — ADD добавить число из ОЗУ к Асс
50х — SUB вычесть число, хранящееся в ОЗУ из Асс
80x — STA сохранить число из аккумулятора Асс в ОЗУ по адресу х
58х — LDA загрузить число из адреса х в Асс
04х — BRA безусловный переход в ячейку с адресом x
02х — BRZ переход в ячейку с адресом x, если Асс=0 (условный переход)
01x — BRP переход в ячейку с адресом x, если Асс>=0 (условный переход)
40х — INP загрузить число из data_input в Асс
20х — OUT загрузить число из Асс в data_out

Команды HLT у нас не будет.

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

Вычитаем из второго числа первое: Алгоритм работает так: сохраняем в память данных два числа из data_in.

  • если результат отрицательный, записываем первое число в Асс, записываем в data_out число из Асс;
  • если результат положительный, записываем второе число в Асс, записываем в data_out число из Асс.

00 INP
01 STA 11
02 INP
03 STA 12
04 SUB 11
05 BRP 08
06 LDA 11
07 BRA 09
08 LDA 12
09 OUT

В нашей системе команд этот алгоритм будет выглядеть так
400
80b
400
80c
50b
018
58b
049
58c
200

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть