- PVSM.RU - https://www.pvsm.ru -
Спроектируем Little Man Computer [1] на языке Verilog.
Статья [2] про LMC была на Хабре.
Online симулятор этого компьютера здесь [3].
Напишем модуль ОЗУ на языке Verilog.
Данные загружаются в ОЗУ из data_in по тактовому сигналу 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];
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.
//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 числа из памяти RAM.
На сигнальные входы мультиплексора подаются числа 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

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

Схему можно скачать отсюда [4].
В первых восьми разрядах хранятся команды, в последних четырех разрядах хранится адрес, загружаемый в счётчик.
Отмечу, что загрузка числа в аккумулятор Асс должна производиться после переключения мультиплексора 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/ [3]
Алгоритм работает так: сохраняем в память данных два числа из 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 можно скачать с официального сайта [5].
При регистрации в разделе My Primary Job Function is* необходимо выбрать пункт Student.
Далее необходимо скачать драйвер для программатора (драйвер для usb-blaster'a можно установить из C:altera...quartusdriversusb-blaster).
Logisim [6] можно скачать здесь [7].
Автор: demser
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/279660
Ссылки в тексте:
[1] Little Man Computer: https://en.wikipedia.org/wiki/Little_man_computer
[2] Статья: https://habr.com/post/257331/
[3] здесь: http://peterhigginson.co.uk/LMC/
[4] отсюда: https://yadi.sk/d/MfVCrSWB3VP5UF
[5] сайта: https://www.altera.com/products/design-software/fpga-design/quartus-prime/download.html
[6] Logisim: https://ru.wikipedia.org/wiki/Logisim
[7] здесь: https://sourceforge.net/projects/circuit/
[8] Источник: https://habr.com/post/358066/?utm_campaign=358066
Нажмите здесь для печати.