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

в 13:10, , рубрики: cpu, diy или сделай сам, fpga, LMC, logisim, Verilog, Программирование, процессор, Электроника для начинающих

Проектирование процессора (CPU Design) LMC - 1
Часть I
Часть II
Часть III

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

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

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

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

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

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 

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

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

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

Также можно использовать внешний генератор, например КМОП таймер 555 (работающий от 3.3V).
Подключим таймер 555 к счётчику, подключим счётчик к адресному входу ОЗУ.
Т.о. при поступлении тактового сигнала на счётчик мы будем переходить на следующую ячейку в памяти. На тактовый вход ОЗУ подключим кнопку 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
Проектирование процессора (CPU Design) LMC - 3

В симуляторе 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'е
Проектирование процессора (CPU Design) LMC - 4

Загрузим 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    

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

В отдельном модуле создаем 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.
Проектирование процессора (CPU Design) LMC - 6

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

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 и 3.
1. Загружаем числа в ОЗУ
2. Обнуляем Асс
3. Переключаем MUX2
4. Загружаем первое число из ОЗУ в Асс
5. Прибавляем к числу в Асс второе число из ОЗУ
6. Загружаем сумму в ОЗУ

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    

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

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

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

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

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

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

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

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

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

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

Проверим, что когда в Асс лежит положительное число, перехода 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    

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

Поместим команду безусловного перехода в ОЗУ
Проектирование процессора (CPU Design) LMC - 13

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

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

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

Добавим в схему 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];

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

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    

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

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

assign RAM2_button = RAM1_out[4];

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

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    

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

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

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

Итак, добавив остальные команды, создадим модуль R52 (LMC)
Проектирование процессора (CPU Design) LMC - 19

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.

Автор: Дмитрий

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js