- PVSM.RU - https://www.pvsm.ru -
Спроектируем Little Man Computer [3] на языке Verilog.
Статья [4] про LMC была на Хабре.
Online симулятор этого компьютера здесь [5].
Напишем модуль оперативной памяти (ОЗУ), состоящий из четырех (N=2) четырёхбитных (M=4) слов. Данные загружаются в ОЗУ из data_in по адресу adr при поступлении тактового сигнала clk.
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. Флаг «Ноль» — это лог. элемент 4ИЛИ-НЕ. Флаг поднимается, если содержимое Асс равно нулю.
2. Флаг «Ноль или Положительное число» — это лог. элемент НЕ на старшем разряде четырёхразрядного аккумулятора. Флаг поднимается, если содержимое Асс больше или равно нулю.
//флаг "Ноль"
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
Поместим команды и адреса в одно ОЗУ, а данные — в другое.
Схему можно скачать отсюда [6].
В первых восьми разрядах хранятся команды, в последних четырех разрядах хранится адрес, загружаемый в счётчик.
Отмечу, что загрузка числа в аккумулятор Асс должна производиться после переключения мультиплексора 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/ [5]
Алгоритм работает так: сохраняем в память данных два числа из data_in. Вычитаем из второго числа первое:
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 можно скачать с официального сайта [7].
При регистрации в разделе My Primary Job Function is* необходимо выбрать пункт Student.
Далее необходимо скачать драйвер для программатора (драйвер для usb-blaster'a можно установить из C:altera...quartusdriversusb-blaster).
Logisim [8] можно скачать здесь [9].
Автор: demser
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/diy-ili-sdelaj-sam/279916
Ссылки в тексте:
[1] Часть I: https://geektimes.com/post/292289/
[2] Часть II: https://geektimes.com/post/300769/
[3] Little Man Computer: https://en.wikipedia.org/wiki/Little_man_computer
[4] Статья: https://habr.com/post/257331/
[5] здесь: http://peterhigginson.co.uk/LMC/
[6] отсюда: https://yadi.sk/d/MfVCrSWB3VP5UF
[7] сайта: https://www.altera.com/products/design-software/fpga-design/quartus-prime/download.html
[8] Logisim: https://ru.wikipedia.org/wiki/Logisim
[9] здесь: https://sourceforge.net/projects/circuit/
[10] Источник: https://geektimes.com/post/300769/?utm_campaign=300769
Нажмите здесь для печати.