Главная » Хабрахабр » Little Man Computer на языке Verilog

Little Man Computer на языке Verilog

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

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

Данные загружаются в ОЗУ из data_in по адресу adr при поступлении тактового сигнала clk. Напишем модуль оперативной памяти (ОЗУ), состоящий из четырех (N=2) четырёхбитных (M=4) слов.

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]; //объявляем массив mem
always @(posedge clk) //при поступлении тактового сигнала clk
mem [adr] <= data_in; //загружаем данные в ОЗУ из data_in assign RAM_out = mem[adr]; //назначаем RAM_out портом вывода данных
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; // счетчик увеличивается на 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.

//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

Добавим в общую схему аккумулятор Acc, мультиплексор MUX2 и сумматор sum.
Сумматор прибавляет к числу в аккумуляторе Acc числа из памяти.
На сигнальные входы мультиплексора подаются числа data_in и sum.
Далее число из мультиплексора MUX2 загружается в аккумулятор Acc.

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];


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

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 у нас не будет.

Возьмём для примера алгоритм поиска максимального из двух чисел с сайта http://peterhigginson.co.uk/LMC/

Вычитаем из второго числа первое: Алгоритм работает так: сохраняем в память данных два числа из 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

Quartus II можно скачать с официального сайта.

При регистрации в разделе My Primary Job Function is* необходимо выбрать пункт Student.
Далее необходимо скачать драйвер для программатора (драйвер для usb-blaster'a можно установить из C:\altera\...\quartus\drivers\usb-blaster).

Logisim можно скачать здесь.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Микросервисный фронтенд — современный подход к разделению фронта

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

Security Week 31: Пятьдесят оттенков небезопасности в Android

Давно мы что-то не писали про безопасность Android. В целом ситуация там вроде бы неплохая: таких серьезных проблем, как трехлетней давности баг Stagefright, пока не находили. С 2016 года развивается программа Android One, в которой устройства среднего уровня получают единую ...