Хабрахабр

Проектирование процессора (CPU Design) LMC


Часть I
Часть II
Часть III

Это полная версия предыдущей статьи.

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

Статья про LMC была на Хабре.

Online симулятор этого компьютера здесь.

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

module R0 #(parameter ADDR_WIDTH = 2, DATA_WIDTH = 4)
( input clk, //тактовый сигнал input [ADDR_WIDTH-1:0] adr, //адрес input [DATA_WIDTH-1:0] data_in, //порт ввода данных output [DATA_WIDTH-1:0] RAM_out //порт вывода данных
); reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0]; //объявляем массив mem always @(posedge clk) //при поступлении тактового сигнала clk mem [adr] <= data_in; //загружаем данные в ОЗУ из data_in assign RAM_out = mem[adr]; //назначаем RAM_out портом вывода данных
endmodule

В тестбенче загрузим 0001 по адресу 00, 0010 по адресу 01, 0100 по адресу 10, 1000 по адресу 11

module tR0; reg clk; reg [1:0] adr; reg [3:0] data_in; wire [3:0] RAM_out;
R0 test_R0 (clk, adr, data_in,RAM_out); initial begin clk = 0; adr[0] = 0; adr[1] = 0; data_in[0] = 0; data_in[1] = 0; data_in[2] = 0; data_in[3] = 0; #5 data_in[0] = 1; #5 clk = 1; #5 adr[0] = 1; data_in[0] = 0; data_in[1] = 1; clk = 0; #5 clk = 1; #5 adr[0] = 0; adr[1] = 1; data_in[1] = 0; data_in[2] = 1; clk = 0; #5 clk = 1; #5 adr[0] = 1; adr[1] = 1; data_in[2] = 0; data_in[3] = 1; clk = 0; #5 clk = 1; #5 adr[0] = 0; adr[1] = 0; data_in[3] = 0; clk = 0; #5 adr[0] = 1; adr[1] = 0; #5 adr[0] = 0; adr[1] = 1; #5 adr[0] = 1; adr[1] = 1; #5 adr[0] = 0; adr[1] = 0; #5 adr[0] = 1; adr[1] = 0; #5 adr[0] = 0; adr[1] = 1; #5 adr[0] = 1; adr[1] = 1; #5 adr[0] = 0; adr[1] = 0; #5 adr[0] = 1; adr[1] = 0; #5 adr[0] = 0; adr[1] = 1; #5 adr[0] = 1; adr[1] = 1;
end
endmodule

5 МГц (MAX II EPM240 CPLD Minimal Development Board).
Подключим счётчик к адресному входу ОЗУ.
На вход счётчика необходимо подключить тактовый генератор.
Вот пример программы, использующей внутренний генератор ALTUFM_OSC.
Частота штатного генератора 5.

module inner_Clock ( output reg LED);
ALTUFM_OSC osc( .oscena(1'b1), .osc(clk)); reg signal; reg [24:0] osc_counter; reg [24:0] const_data = 25'b10110111000110110000000; initial begin signal = 1'b0; osc_counter = 25'b0; end
//досчитываем до 6 000 000 и обнуляем счетчик osc_counter
always @(posedge clk) begin osc_counter <= osc_counter+ 1'b1; if(osc_counter == const_data) begin signal <= ~signal; osc_counter <= 25'b0; end
LED = signal; // LED мигает ~1 раз в сеунду.
end
endmodule

3V).
Подключим таймер 555 к счётчику, подключим счётчик к адресному входу ОЗУ.
Т.о. Также можно использовать внешний генератор, например КМОП таймер 555 (работающий от 3. На тактовый вход ОЗУ подключим кнопку RAM_button — данные в ОЗУ будут загружаться при нажатии на эту кнопку.
при поступлении тактового сигнала на счётчик мы будем переходить на следующую ячейку в памяти.

module R1 (timer555, RAM_button, data_in, RAM_out, counter); parameter ADDR_WIDTH = 2; parameter DATA_WIDTH = 4; input timer555; input RAM_button; //input [ADDR_WIDTH-1:0] adr; input [DATA_WIDTH-1:0] data_in; output [DATA_WIDTH-1:0] RAM_out; output reg [1:0] counter;
// Counter
always @(posedge timer555) counter <= counter + 1; // RAM wire [ADDR_WIDTH-1:0] adr; assign adr = counter; reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0]; always @(posedge RAM_button) mem [adr] <= data_in;
assign RAM_out = mem[adr];
endmodule

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

Работоспособность схемы можно проверить, непосредственно загрузив программу в ПЛИС. В симуляторе ModelSim эта схема работать не будет, потому что не инициализирован счетчик.

Далее, добавим в счетчик функцию загрузки.
Загрузка из data_in [1:0] производится нажатием на кнопку Counter_load

module R2 (counter, timer555, Counter_load, RAM_button, data_in, RAM_out); parameter ADDR_WIDTH = 2; parameter DATA_WIDTH = 4; output [1:0] counter; input timer555, Counter_load; // input [N-1:0] adr; input RAM_button; input [DATA_WIDTH-1:0] data_in; output [DATA_WIDTH-1:0] RAM_out;
// Counter
reg [1:0] counter;
always @ (posedge timer555 or posedge Counter_load) if (Counter_load) counter <= data_in[1:0]; else counter <= counter + 2'b01;
// RAM wire [ADDR_WIDTH-1:0] adr; assign adr = counter;
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0]; always @(posedge RAM_button) mem [adr] <= data_in;
assign RAM_out = mem[adr]; endmodule

Вот так выглядит подключение кнопок и светодиодов в Pin Planner'е

Загрузим 0001 по адресу 00, 0010 по адресу 01, 0100 по адресу 10, 1000 по адресу 11

module tR2;
parameter ADDR_WIDTH = 2;
parameter DATA_WIDTH = 4; reg timer555, Counter_load, RAM_button;
wire [1:0] counter;
reg [DATA_WIDTH-1:0] data_in;
wire [DATA_WIDTH-1:0] RAM_out; R2 test_R2(counter, timer555, Counter_load, RAM_button, data_in, RAM_out);
initial // Clock generator begin timer555 = 0; forever #20 timer555 = ~timer555; end
initial begin data_in[0] = 0; data_in[1] = 0; data_in[2] = 0; data_in[3] = 0; Counter_load = 0; RAM_button = 0; #5 data_in[0]=0; data_in[1]=0; Counter_load=1; RAM_button=0; #5 data_in[0]=1; data_in[1]=0; Counter_load=0; RAM_button=1; #5 data_in[0]=0; data_in[1]=0; Counter_load=0; RAM_button=0; #5 data_in[0]=1; data_in[1]=0; Counter_load=1; RAM_button=0; #5 data_in[0]=0; data_in[1]=1; Counter_load=0; RAM_button=1; #5 data_in[0]=0; data_in[1]=0; Counter_load=0; RAM_button=0; #5 data_in[0]=0; data_in[1]=1; Counter_load=1; RAM_button=0; #5 data_in[2]=1; data_in[0]=0; data_in[1]=0; Counter_load=0; RAM_button=1; #5 data_in[2]=0; data_in[0]=0; data_in[1]=0; Counter_load=0; RAM_button=0; #5 data_in[0]=1; data_in[1]=1; Counter_load=1; RAM_button=0; #5 data_in[3]=1; data_in[0]=0; data_in[1]=0; Counter_load=0; RAM_button=1; #5 data_in[3]=0; data_in[0]=0; data_in[1]=0; Counter_load=0; RAM_button=0; end
endmodule

В отдельном модуле создаем 4bit'ный регистр (аккумулятор).
Данные загружаются в регистр при нажатии на кнопку reg_button

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

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

module R3 (MUX_switch, Acc_button, Acc, counter, timer555, Counter_load, RAM_button, data_in, RAM_out); parameter ADDR_WIDTH = 2; parameter DATA_WIDTH = 4; input MUX_switch; input Acc_button; output [3:0] Acc; input timer555, Counter_load; output [1:0] counter; // input [N-1:0] adr; input RAM_button; input [DATA_WIDTH-1:0] data_in; output [DATA_WIDTH-1:0] RAM_out;
// Counter
reg [1:0] counter;
always @ (posedge timer555 or posedge Counter_load) if (Counter_load) counter <= data_in[1:0]; else counter <= counter + 2'b01;
// RAM wire [ADDR_WIDTH-1:0] adr; assign adr = counter;
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0]; always @(posedge RAM_button) mem [adr] <= Acc;
assign RAM_out = mem[adr];
// sum wire [3:0] sum;
assign sum = Acc + RAM_out;
// MUX2
reg [3:0] MUX2; always @*
MUX2 = MUX_switch ? sum : data_in;
//Схема подавления дребезга контактов кнопки Acc_button
/* reg Acc_latch;
always @(posedge Acc_button or negedge timer555) if (!timer555) Acc_latch <= 1'b0; else Acc_latch <= timer555; */
//Acc
register4 Acc_reg( .reg_data(MUX2), //.reg_button(Acc_latch), .reg_button(Acc_button), .q(Acc)
);
endmodule

Для программного подавления дребезга можно применить простую схему,
приведённую в комментариях.

Загружаем числа в ОЗУ
2. Далее, будем складывать числа, например, 2 и 3.
1. Переключаем MUX2
4. Обнуляем Асс
3. Прибавляем к числу в Асс второе число из ОЗУ
6. Загружаем первое число из ОЗУ в Асс
5. Загружаем сумму в ОЗУ

module tR3; parameter ADDR_WIDTH = 2; parameter DATA_WIDTH = 4; reg MUX_switch; reg Acc_button; wire [3:0] Acc; reg timer555, Counter_load, RAM_button; wire [1:0] counter; reg [DATA_WIDTH-1:0] data_in; wire [DATA_WIDTH-1:0] RAM_out;
R3 test_R3(MUX_switch, Acc_button, Acc, counter, timer555, Counter_load, RAM_button, data_in, RAM_out);
initial begin timer555 = 0; forever #20 timer555 = ~timer555; end
initial begin data_in[0] = 0; data_in[1] = 0; data_in[2] = 0; data_in[3] = 0; Counter_load = 0; Acc_button = 0; RAM_button = 0; MUX_switch = 0; #5 Counter_load = 1; #5 data_in[0]=0; data_in[1]=1; Counter_load = 0; #5 Acc_button = 1; #5 RAM_button = 1; #5 data_in[0]=0; data_in[1] = 0; Acc_button = 0; RAM_button = 0; #5 data_in[0]=1; data_in[1]=1; #15 Acc_button = 1; #5 RAM_button = 1; #5 Acc_button = 0; #5 data_in[0]=0; data_in[1] = 0; RAM_button = 0; #10 Acc_button = 1; #10 Acc_button = 0; #60 MUX_switch = 1; #10 Acc_button = 1; #10 Acc_button = 0; #30 Acc_button = 1; #10 Acc_button = 0; #30 RAM_button = 1; #10 RAM_button = 0; end
endmodule

Добавим в основной модуль элемент, вычитающий из числа в аккумуляторе число, записанное в памяти.

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

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

always @*
MUX4 = MUX_switch[1] ? (MUX_switch[0] ? RAM_out : 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];

Добавим три команды

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

module R4 (JMP,Z_JMP,PZ_JMP,Z_flag,PZ_flag,Output_button,data_out,MUX_switch,Acc_button,Acc,counter,timer555,RAM_button,data_in,RAM_out); parameter ADDR_WIDTH = 2; parameter DATA_WIDTH = 4; input JMP, Z_JMP, PZ_JMP; output Z_flag, PZ_flag; input Output_button; output [3:0] data_out; input [1:0] MUX_switch; input Acc_button; output [3:0] Acc; input timer555; output [1:0] counter; input RAM_button; input [DATA_WIDTH-1:0] data_in; output [DATA_WIDTH-1:0] RAM_out;
// flags
wire Z,PZ;
assign Z = Z_flag & Z_JMP;
assign PZ = PZ_flag & PZ_JMP; // Counter
reg [1:0] counter;
always @ (posedge timer555 or posedge JMP or posedge Z or posedge PZ) if (JMP|Z|PZ) counter <= data_in[1:0]; else counter <= counter + 2'b01;
// RAM wire [ADDR_WIDTH-1:0] adr; assign adr = counter;
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0]; always @(posedge RAM_button) mem [adr] <= Acc;
assign RAM_out = mem[adr];
// sum wire [3:0] sum;
assign sum = Acc + RAM_out;
//subtract
wire [3:0] subtract;
assign subtract = Acc - RAM_out;
// MUX4
reg [3:0] MUX4; always @*
MUX4 = MUX_switch[1] ? (MUX_switch[0] ? RAM_out : subtract)
: (MUX_switch[0] ? sum : data_in);
//Acc
register4 Acc_reg( .reg_data(MUX4), .reg_button(Acc_button), .q(Acc)
);
//data_out
register4 Output_reg(
.reg_data(Acc),
.reg_button(Output_button),
.q(data_out)
); assign Z_flag = ~(|Acc); assign PZ_flag = ~Acc[3];
endmodule

Загружаем числа в ОЗУ
2. 1. Переключаем MUX2
4. Обнуляем Асс
3. Вычитаем второе число (записанное в ОЗУ) из Асс
6. Вычитаем первое число (записанное в ОЗУ) из Асс
5. Загружаем сумму в ОЗУ и в data_out

module tR4;
parameter ADDR_WIDTH = 2;
parameter DATA_WIDTH = 4; reg JMP, Z_JMP, PZ_JMP; wire Z_flag, PZ_flag; reg Output_button; wire [3:0] data_out; reg [1:0] MUX_switch; reg Acc_button; wire [3:0] Acc; reg timer555, RAM_button; wire [1:0] counter; reg [DATA_WIDTH-1:0] data_in; wire [DATA_WIDTH-1:0] RAM_out; R4 test_R4
(JMP,Z_JMP,PZ_JMP,Z_flag,PZ_flag,Output_button,data_out,MUX_switch,Acc_button,Acc,
counter,timer555,RAM_button,data_in,RAM_out);
initial begin timer555 = 0; forever #20 timer555 = ~timer555; end
initial begin data_in[0] = 0; data_in[1] = 0; data_in[2] = 0; data_in[3] = 0; JMP = 0; Z_JMP = 0; PZ_JMP = 0; Acc_button = 0; RAM_button = 0; Output_button = 0; MUX_switch[0] = 0; MUX_switch[1] = 0; #5 JMP = 1; #5 data_in[0]=0; data_in[1]=1; JMP = 0; #5 Acc_button = 1; #5 RAM_button = 1; #5 data_in[0]=0; data_in[1] = 0; Acc_button = 0; RAM_button = 0; #5 data_in[0]=1; data_in[1]=1; #15 Acc_button = 1; #5 RAM_button = 1; #5 Acc_button = 0; #5 data_in[0]=0; data_in[1] = 0; RAM_button = 0; #10 Acc_button = 1; #10 Acc_button = 0; #60 MUX_switch[1] = 1; #10 Acc_button = 1; #10 Acc_button = 0; #30 Acc_button = 1; #10 Acc_button = 0; #30 RAM_button = 1; Output_button = 1; #10 RAM_button = 0; Output_button = 0; end
endmodule

Проверим, что когда в Асс лежит положительное число, перехода Z_JMP не происходит

module tR4_jmp;
parameter ADDR_WIDTH = 2;
parameter DATA_WIDTH = 4; reg JMP, Z_JMP, PZ_JMP; wire Z_flag, PZ_flag; reg Output_button; wire [3:0] data_out; reg [1:0] MUX_switch; reg Acc_button; wire [3:0] Acc; reg timer555, RAM_button; wire [1:0] counter; reg [DATA_WIDTH-1:0] data_in; wire [DATA_WIDTH-1:0] RAM_out;
R4 test_R4
(JMP,Z_JMP,PZ_JMP,Z_flag,PZ_flag,Output_button,data_out,MUX_switch,Acc_button,Acc,
counter,timer555,RAM_button,data_in,RAM_out);
initial begin timer555 = 0; forever #20 timer555 = ~timer555; end
initial begin data_in[0] = 0; data_in[1] = 0; data_in[2] = 0; data_in[3] = 0; JMP = 0; Z_JMP = 0; PZ_JMP = 0; Acc_button = 0; RAM_button = 0; Output_button = 0; MUX_switch[0] = 0; MUX_switch[1] = 0; #5 JMP = 1; #5 data_in[0]=0; data_in[1]=1; JMP = 0; #5 Acc_button = 1; #5 data_in[0]=1; data_in[1]=1; Acc_button = 1; #5 data_in[0]=1; data_in[1]=1; Acc_button = 0; #5 Z_JMP = 1; #5 PZ_JMP = 1; Z_JMP = 0; #5 PZ_JMP = 0; end
endmodule

Поместим команду безусловного перехода в ОЗУ

Конструкция вида

//wire Counter_load;
always @ (posedge timer555) if (Counter_load) counter <= RAM_out[3:0]; else counter <= counter + 2'b01;

в ModelSim работать не будет, поэтому будем использовать дополнительную команду reset_count, которая инициализирует счетчик, обнуляя его, т.е.

module resCount (reset_count, counter, timer555, RAM_button, data_in, RAM_out); parameter ADDR_WIDTH = 4; parameter DATA_WIDTH = 8; input reset_count; output [ADDR_WIDTH-1:0] counter; input timer555; input RAM_button; input [DATA_WIDTH-1:0] data_in; output [DATA_WIDTH-1:0] RAM_out;
wire Counter_load;
assign Counter_load = RAM_out[7];
reg [ADDR_WIDTH-1:0] counter;
always @ (posedge timer555 or posedge reset_count) if (reset_count) counter <= 4'b0000; else if (Counter_load) counter <= RAM_out[3:0]; else counter <= counter + 4'b0001; wire [ADDR_WIDTH-1:0] adr; assign adr = counter;
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0]; always @(posedge RAM_button) mem [adr] <= data_in;
assign RAM_out = mem[adr]; endmodule

test bench

module tresCount; parameter ADDR_WIDTH = 4; parameter DATA_WIDTH = 8; reg reset_count; reg timer555, RAM_button; wire [ADDR_WIDTH-1:0] counter; reg [DATA_WIDTH-1:0] data_in; wire [DATA_WIDTH-1:0] RAM_out;
resCount test_resCount(reset_count, counter, timer555, RAM_button, data_in, RAM_out);
initial // Clock generator begin timer555 = 0; forever #20 timer555 = ~timer555; end
initial begin data_in[0] = 0; data_in[1] = 0; data_in[2] = 0; data_in[3] = 0; data_in[4] = 0; data_in[5] = 0; data_in[6] = 0; data_in[7] = 0; RAM_button = 0; reset_count =1; #5 reset_count =0; #1500 data_in[7] =1; #5 RAM_button = 1; #5 data_in[7] =0; RAM_button = 0; end
endmodule

Добавим в схему MUX2 и Асс
Будем производить запись в Асс командой RAM_out[6]

assign Acc_button = RAM_out[6];

элемент И
К тактовому входу Асс подключим лог.

//в модуле regiser4 заменим (posedge reg_button) на (negedge reg_button)
.reg_button(Acc_button & timer555),

Смысл подключения лог. элемента И к тактовому входу в том, что теперь по фронту timer555 можно переключать мультиплексор, а по спаду производить запись в аккумулятор. Т.о. мы поместили две команды в один такт.

Будем производить переключение MUX2 командой RAM_out[5]

assign MUX_switch = RAM_out[5];

module register4
( input [3:0] reg_data, input reg_button, output reg [3:0] q );
always @(negedge reg_button) // заменим "posedge" на "negedge" q <= reg_data;
endmodule module R50 (reset_count, counter, timer555, RAM_button, data_in, RAM_out, mux_switch_out, mux_out,Acc_out); parameter ADDR_WIDTH = 2; parameter DATA_WIDTH = 8; input reset_count; output [ADDR_WIDTH-1:0] counter; input timer555; input RAM_button; input [DATA_WIDTH-1:0] data_in; output [DATA_WIDTH-1:0] RAM_out; output [3:0] Acc_out; output mux_switch_out; output [3:0] mux_out;
wire Counter_load;
assign Counter_load = RAM_out[7];
//Counter
reg [ADDR_WIDTH-1:0] counter;
always @ (posedge timer555 or posedge reset_count) if (reset_count) counter <= 2'b00; else if (Counter_load) counter <= RAM_out[1:0]; else counter <= counter + 2'b01; wire [ADDR_WIDTH-1:0] adr; assign adr = counter;
//RAM
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0]; always @(posedge RAM_button) mem [adr] <= data_in;
assign RAM_out = mem[adr]; // MUX2
wire MUX_switch;
assign MUX_switch = RAM_out[5];
reg [3:0] MUX2; always @*
MUX2 = MUX_switch ? RAM_out : data_in[3:0]; // возьмём 4 разряда из data_in
assign mux_out = MUX2;
assign mux_switch_out = MUX_switch; wire Acc_button;
assign Acc_button = RAM_out[6];
//Acc
register4 Acc_reg( .reg_data(mux_out), .reg_button(Acc_button & timer555), .q(Acc_out)
);
endmodule

В тестбенче запишем в ячейку 00 число 0101, а в ячейку 01 число 1010; загрузим эти числа в аккумулятор

module tR50; parameter ADDR_WIDTH = 2; parameter DATA_WIDTH = 8; reg reset_count; reg timer555, RAM_button; wire [ADDR_WIDTH-1:0] counter; reg [DATA_WIDTH-1:0] data_in; wire [DATA_WIDTH-1:0] RAM_out; wire mux_switch_out; wire [3:0] mux_out; wire [3:0] Acc_out;
R50 test_R50(reset_count, counter, timer555, RAM_button, data_in, RAM_out, mux_switch_out, mux_out,Acc_out);
initial // Clock generator begin timer555 = 0; forever #20 timer555 = ~timer555; end
initial begin data_in[0] = 1; data_in[1] = 0; data_in[2] = 1; data_in[3] = 0; data_in[4] = 0; data_in[5] = 1; data_in[6] = 1; data_in[7] = 0; RAM_button = 0; reset_count =1; #5 RAM_button = 1; reset_count = 0; #5 data_in[0]=0; data_in[2]=0; data_in[5]=0; data_in[6]=0; RAM_button=0; #15 data_in[1]=1; data_in[3]=1; data_in[5]=1;data_in[6]=1; #5 RAM_button=1; #5 data_in[1]=0; data_in[3]=0; data_in[5]=0; data_in[6]=0; RAM_button=0; end
endmodule

Поместим второе ОЗУ в общую схему и будем производить запись в ОЗУ командой RAM1_out[4].

assign RAM2_button = RAM1_out[4];

module register4
( input [3:0] reg_data, input reg_button, output reg [3:0] q );
always @(negedge reg_button) q <= reg_data;
endmodule module R51 (reset_count, counter, timer555, RAM1_button, data_in, RAM1_out, RAM2_out, mux_switch_out, mux_out,Acc_out); parameter ADDR_WIDTH = 3; parameter DATA_WIDTH = 8; input reset_count; output [ADDR_WIDTH-1:0] counter; input timer555; input RAM1_button; input [DATA_WIDTH-1:0] data_in; output [DATA_WIDTH-1:0] RAM1_out; output [3:0] RAM2_out; output [3:0] Acc_out; output mux_switch_out; output [3:0] mux_out;
wire Counter_load;
assign Counter_load = RAM1_out[7];
//Counter
reg [ADDR_WIDTH-1:0] counter;
always @ (posedge timer555 or posedge reset_count) if (reset_count) counter <= 2'b00; else if (Counter_load) counter <= RAM1_out[1:0]; else counter <= counter + 2'b01; wire [ADDR_WIDTH-1:0] adr1; assign adr1 = counter;
//RAM1
reg [DATA_WIDTH-1:0] mem1 [2**ADDR_WIDTH-1:0]; always @(posedge RAM1_button ) mem1 [adr1] <= data_in;
assign RAM1_out = mem1[adr1]; wire [ADDR_WIDTH-1:0] adr2; assign adr2 = RAM1_out[3:0];
wire RAM2_button; assign RAM2_button = RAM1_out[4];
//RAM2
reg [3:0] mem2 [2**ADDR_WIDTH-1:0]; always @(posedge RAM2_button) mem2 [adr2] <= Acc_out;
assign RAM2_out = mem2[adr2]; // MUX2
wire MUX_switch; assign MUX_switch = RAM1_out[5];
reg [3:0] MUX2; always @*
MUX2 = MUX_switch ? RAM2_out : data_in[3:0];
assign mux_out = MUX2;
assign mux_switch_out = MUX_switch; wire Acc_button;
assign Acc_button = RAM1_out[6];
//Acc
register4 Acc_reg( .reg_data(mux_out), .reg_button(Acc_button & timer555), .q(Acc_out)
);
endmodule

В тестбенче загрузим числа 0100 и 1000 из Асс в нулевую 0000 и первую 0001 ячейки ОЗУ mem2 (затем загрузим эти числа в Асс из ОЗУ mem2)

module tR51; parameter ADDR_WIDTH = 3; parameter DATA_WIDTH = 8; reg reset_count; reg timer555, RAM1_button; wire [ADDR_WIDTH-1:0] counter; reg [DATA_WIDTH-1:0] data_in; wire [DATA_WIDTH-1:0] RAM1_out; wire [3:0] RAM2_out; wire mux_switch_out; wire [3:0] mux_out; wire [3:0] Acc_out;
R51 test_R51(reset_count, counter, timer555, RAM1_button, data_in, RAM1_out, RAM2_out, mux_switch_out, mux_out,Acc_out);
initial // Clock generator begin timer555 = 0; forever #20 timer555 = ~timer555; end
initial begin data_in[0] = 0; data_in[1] = 0; data_in[2] = 0; data_in[3] = 0; data_in[4] = 0; data_in[5] = 0; data_in[6] = 1; data_in[7] = 0; RAM1_button = 0; reset_count =1; #5 RAM1_button = 1; reset_count = 0; #5 RAM1_button = 0; data_in[6] = 0; #10 data_in[4] = 1; #5 RAM1_button = 1; #5 data_in[4] = 0; RAM1_button = 0; #30 data_in[6] = 1; #5 RAM1_button = 1; #5 data_in[6] = 0; RAM1_button = 0; #30 data_in[4] = 1; data_in[0] = 1; #5 RAM1_button = 1; #5 data_in[4] = 0; data_in[0] = 0; RAM1_button = 0; #30 data_in[6] = 1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[6] = 0; #30 data_in[5] = 1; data_in[6] = 1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[5] = 0; data_in[6] = 0; #30 data_in[5] = 1; data_in[6] = 1; data_in[0] = 1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[0] = 0; data_in[5] = 0; data_in[6] = 0; #70 data_in[2] = 1; #80 data_in[2] = 0; data_in[3] = 1; #40 data_in[3] = 0; end
endmodule

элементом И на тактовом входе аккумулятора не всегда будет работать корректно (зависит от платы). Добавлю, что схема c лог. элемент И на триггер, загрузку в триггер будем производить по отрицательному фронту (по спаду) тактового сигнала timer555, загрузку в аккумулятор будем производить по положительному фронту
Заменим лог.

// Acc_latch
reg Acc_latch;
always @(negedge timer555) Acc_latch <= Acc_button;

Итак, добавив остальные команды, создадим модуль R52 (LMC)

module register4
( input [3:0] reg_data, input reg_button, output reg [3:0] q );
always @(posedge reg_button) //заменим negedge на posedge q <= reg_data;
endmodule module R52 (Z_flag, PZ_flag, reset_count, counter, timer555, RAM1_button, data_in, RAM1_out, RAM2_out, mux_switch_out, mux_out,Acc_out, data_out, Acc_latch); parameter ADDR_WIDTH = 4; parameter DATA_WIDTH = 12; input reset_count; input timer555; input RAM1_button; input [DATA_WIDTH-1:0] data_in; output [ADDR_WIDTH-1:0] counter; output [1:0] mux_switch_out; output [3:0] mux_out; output [3:0] Acc_out; output [3:0] data_out; output [DATA_WIDTH-1:0] RAM1_out; output [3:0] RAM2_out; output Z_flag, PZ_flag; output Acc_latch; wire JMP_button, Z_JMP_button,PZ_JMP_button; assign JMP_button = RAM1_out[6]; assign Z_JMP_button = RAM1_out[5]; assign PZ_JMP_button = RAM1_out[4]; wire Z_JMP,PZ_JMP; assign Z_JMP = Z_flag & Z_JMP_button; assign PZ_JMP = PZ_flag & PZ_JMP_button; //Counter
reg [ADDR_WIDTH-1:0] counter; always @ (posedge timer555 or posedge reset_count) if (reset_count) counter <= 4'b0000; else if (JMP_button|Z_JMP|PZ_JMP) counter <= RAM1_out[3:0]; else counter <= counter + 4'b0001; wire [ADDR_WIDTH-1:0] adr1; assign adr1 = counter;
//RAM1
reg [DATA_WIDTH-1:0] mem1 [2**ADDR_WIDTH-1:0]; always @(posedge RAM1_button ) mem1 [adr1] <= data_in; assign RAM1_out = mem1[adr1]; //RAM2_adr
wire [ADDR_WIDTH-1:0] adr2; assign adr2 = RAM1_out[2:0];
//RAM2_button
wire RAM2_button; assign RAM2_button = RAM1_out[11];
//RAM2 reg [3:0] mem2 [2**ADDR_WIDTH-1:0]; always @(posedge RAM2_button) mem2 [adr2] <= Acc_out; assign RAM2_out = mem2[adr2]; // sum wire [3:0] sum; assign sum = Acc_out + RAM2_out;
//subtract
wire [3:0] subtract; assign subtract = Acc_out - RAM2_out;
// MUX4
wire [1:0] mux_switch; assign mux_switch[0] = RAM1_out[7]; assign mux_switch[1] = RAM1_out[8];
reg [3:0] MUX4; always @*
MUX4 = mux_switch[1] ? (mux_switch[0] ? RAM2_out : subtract)
: (mux_switch[0] ? sum : data_in[3:0]); assign mux_out = MUX4; assign mux_switch_out[0] = mux_switch[0]; assign mux_switch_out[1] = mux_switch[1];
//Acc_button
wire Acc_button; assign Acc_button = RAM1_out[10];
// Acc_latch
reg Acc_latch;
always @(negedge timer555) Acc_latch <= Acc_button; //Acc
register4 Acc_reg( .reg_data(mux_out), //.reg_button(Acc_button & timer555), .reg_button(Acc_latch), .q(Acc_out)
);
//data_out
wire Output_button; assign Output_button = RAM1_out[9];
register4 Output_reg( .reg_data(Acc_out), .reg_button(Output_button), .q(data_out)
);
// flags assign Z_flag = ~(|Acc_out); assign PZ_flag = ~Acc_out[3];
endmodule

В тестбенче проверим, как работает алгоритм поиска максимального числа.
Особенность загрузки команд в ОЗУ заключается в том, что после загрузки всех команд нам приходится возвращаться (340ns) в ячейку 8 и загружать ещё одну команду

module tR52; parameter ADDR_WIDTH = 4; parameter DATA_WIDTH = 12; reg reset_count; reg timer555; reg RAM1_button; reg [DATA_WIDTH-1:0] data_in; wire [ADDR_WIDTH-1:0] counter; wire [1:0]mux_switch_out; wire [3:0] mux_out; wire [3:0] Acc_out; wire [3:0] data_out; wire [DATA_WIDTH-1:0] RAM1_out; wire [3:0] RAM2_out; wire Z_flag, PZ_flag; wire Acc_latch; R52 test_R52(Z_flag, PZ_flag, reset_count, counter, timer555, RAM1_button, data_in, RAM1_out, RAM2_out, mux_switch_out, mux_out,Acc_out, data_out, Acc_latch);
initial // Clock generator begin timer555 = 0; forever #20 timer555 = ~timer555; end initial begin data_in[0] = 0; data_in[1] = 0; data_in[2] = 0; data_in[3] = 0; data_in[4] = 0; data_in[5] = 0; data_in[6] = 0; data_in[7] = 0; data_in[8] = 0; data_in[9] = 0; data_in[10] = 1; data_in[11] = 0; RAM1_button = 0; reset_count =1; // загружаем 1-ое число в Асс #5 RAM1_button = 1; reset_count = 0; #5 RAM1_button = 0; data_in[10] = 0; data_in[0] = 0; // сохраняем 1-ое число в ячейке 0 #10 data_in[11] = 1; #5 RAM1_button = 1; #5 data_in[11] = 0; RAM1_button = 0; // загружаем 2-ое число в Асс #30 data_in[10] = 1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[10] = 0; // сохраняем 2-ое число в ячейке 0 #30 data_in[11] = 1;data_in[0] = 1; #5 RAM1_button = 1; #5 data_in[11] = 0;data_in[0] = 0; RAM1_button = 0; //вычитаем 1-ое число из Асс #30 data_in[8]=1; data_in[10] = 1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[8]=0; data_in[10] = 0; // Если Acc>=0, переходим на ячейку 8 #30 data_in[4]=1; data_in[3]=1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[4]=0; data_in[3]=0; // загружаем 1-ое число #30 data_in[7] = 1; data_in[8] = 1; data_in[10] = 1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[7] = 0; data_in[8] = 0; data_in[10] = 0; // безусловный переход в ячейку 9 #30 data_in[6] = 1; data_in[3]=1; data_in[0]=1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[6] = 0; data_in[3]=0; data_in[0]=0; //выводим число в data_out #30 data_in[9] = 1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[9] = 0; // безусловный переход в ячейку 8 #30 data_in[6] = 1; data_in[3]=1; data_in[0]=0; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[6] = 0; data_in[3]=0; data_in[0]=0; //загружаем 2-ое число #30 data_in[7] = 1; data_in[8] = 1; data_in[10] = 1; data_in[0] = 1; #5 RAM1_button = 1; #5 RAM1_button = 0; data_in[7] = 0; data_in[8] = 0; data_in[10] = 0; data_in[0] = 0; #75 RAM1_button = 1; #5 RAM1_button = 0; #230 data_in[2]=1; data_in[0]=0; //первое число #80 data_in[2]=0; data_in[0]=1; // второе число end
endmodule

Ссылка на github с кодами программ.

Бесплатную студенческую версию ModelSim можно скачать с сайта www.model.com.

Показать больше

Похожие публикации

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

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

Кнопка «Наверх»