Pull to refresh

Проектирование процессора ModelSim

Reading time 17 min
Views 15K

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

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

Спроектируем 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:
Создание тестбенча
Создать новый проект, создать файлы R0.v и tR0.v (эти файлы автоматически добавятся к проекту).
Скомпилировать оба файла.
Запустить моделирование скомпилированного файла tR0.v

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 



Подключим счётчик к адресному входу ОЗУ. На вход счётчика необходимо подключить тактовый генератор.

Вот пример программы, использующей внутренний генератор 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) или микросхему К155ЛА3 (К155ЛА3 представляет 4 логических элемента 2И-НЕ).

Подключим таймер 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



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

Далее, добавим в счетчик функцию загрузки. Загрузка из 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_dff;
always @(posedge Acc_button or negedge timer555)
        if (!timer555)
            Acc_dff <= 1'b0;
        else
            Acc_dff <= timer555;  */
//Acc
register4 Acc_reg(
	.reg_data(MUX2),
        //.reg_button(Acc_dff),
	.reg_button(Acc_button),
	.q(Acc)
);
endmodule

Для программного подавления дребезга можно применить простую схему
/* reg Acc_dff;
always @(posedge Acc_button or negedge timer555)
if (!timer555)
Acc_dff <= 1'b0;
else
Acc_dff <= timer555; */

А можно использовать одновибратор (ждущий мультивибратор), собранный на таймере 555 или микросхеме К155ЛА3

Далее, будем складывать числа, например, 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    



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

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


Заменим двухвходовой мультиплексор 4х_входовым
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]; 



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

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



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    



Проверим, что когда в Асс лежит положительное число, перехода 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 лог. элементом И на тактовом входе аккумулятора не всегда будет работать корректно (зависит от платы). Заменим лог. элемент И на триггер Acc_dff, загрузку в триггер будем производить по отрицательному фронту (по спаду) тактового сигнала timer555, загрузку в аккумулятор будем производить по положительному фронту

// Acc_dff
reg Acc_dff;
always @(negedge timer555)
        Acc_dff <= 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_dff);
  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_dff;
 
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_dff
reg Acc_dff;
always @(negedge timer555)
   Acc_dff <= Acc_button;  
//Acc
register4 Acc_reg(
	.reg_data(mux_out),
     //.reg_button(Acc_button & timer555),
	.reg_button(Acc_dff),
	.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_dff;
  
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_dff);
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 под Windows можно скачать с сайта www.model.com.
Далее необходимо (заполнив форму) скачать файл student_license.dat и поместить этот файл в основную директорию программы ModelSim.

Ссылка на файл ModelSim под Linux (Ubuntu) здесь
Инструкция по установке здесь.
Tags:
Hubs:
+43
Comments 1
Comments Comments 1

Articles