Все потоки
Поиск
Написать публикацию
Обновить
22.7

FPGA *

Программируемые логические интегральные схемы

Сначала показывать
Порог рейтинга
Уровень сложности

Системы в корпусе или Что на самом деле находится под крышкой корпуса микропроцессора

Время на прочтение8 мин
Количество просмотров59K
Размеры транзисторов в современных микросхемах неумолимо уменьшаются — несмотря на то, что о смерти закона Мура говорят уже несколько лет, а физический предел миниатюризации уже близок (точнее, в некоторых местах его уже успешно обошли). Тем не менее, это уменьшение не приходит даром, а аппетиты пользователей растут быстрее, чем возможности разработчиков микросхем. Поэтому, кроме миниатюризации транзисторов, для создания современных микроэлектронных продуктов используются и другие, зачастую не менее продвинутые технологии.


Читать дальше →

Российские и украинские команды взяли верх над европейцами на европейском финале интеловского конкурса InnovateFPGA

Время на прочтение5 мин
Количество просмотров14K
Золото досталось России, серебро разделила Россия и Италия, бронза досталась Украине. Таковы результаты европейского финала престижного соревнования InnovateFPGA под эгидой Интела. Победители поедут в Калифорнию, где встретятся с финалистами из Америки и Азии. Надеюсь, теперь не нужно будет объяснять на Хабре, почему Verilog и ПЛИС/FPGA стратегически важны, несмотря на то, что «вакансий на джаву больше».

Студенты, которые сейчас делают проекты на ПЛИСах, через несколько лет будут делать массовые микросхемы для самоуправляемых автомобилей, ускорителей нейронных сетей, дополненной реальности и других приложений, в который обычный процессор не справляется. Именно поэтому Intel потратил 16.7 миллиардов долларов на покупку Altera и вход в рынок ПЛИС. А на днях Интел купил еще и компанию eASIC для дешевой конверсии дизайнов из ПЛИС в ASIC (в eASIC есть достаточно многочисленная российская команда).

Победа российских и украинских команд в интеловском конкурсе InnovateFPGA возникла не на пустом месте, а в результате работы конкретных людей в ЛЭТИ, ИТМО, КПИ и других местах, о которых уже были статьи на Хабре. Если продолжить эти начинания и расширить преподавание ПЛИС и языков описания аппаратуры во все технические вузы от Калининграда до Якутии, от Львова до Тбилиси и Астаны — то через пару десятилетий это может изменить расстановку сил в мировой электронной промышленности примерно так же, как работы Курчатова и Королева изменили расстановку сил в мировой атомной промышленности и освоении космоса.


Заметки дилетанта, или Сказ о том, как Scala-разработчик ПЛИС конфигурировал

Время на прочтение18 мин
Количество просмотров8.8K

Долгое время я мечтал научиться работать с FPGA, присматривался. Потом купил отладочную плату, написал пару hello world-ов и положил плату в ящик, поскольку было непонятно, что с ней делать. Потом пришла идея: а давайте напишем генератор композитного видеосигнала для древнего ЭЛТ-телевизора. Идея, конечно, забавная, но я же Verilog толком не знаю, а так его ещё и вспоминать придётся, да и не настолько этот генератор мне нужен… И вот недавно захотелось посмотреть в сторону RISC-V софт-процессоров. Нужно с чего-то начать, а код Rocket Chip (это одна из реализаций) написан на Chisel — это такой DSL для Scala. Тут я внезапно вспомнил, что два года профессионально разрабатываю на Scala и понял: время пришло...


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

Читать дальше →

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате

Время на прочтение14 мин
Количество просмотров15K


В этой статье мы поделимся опытом разработки интерфейсных плат блока сопряжения на базе SoC ARM+FPGA Xilinx Zynq 7000. Платы предназначались для записи речевых сигналов в аналоговом и цифровом формате PRI/BRI (ISDN, E1/T1). Само конечное устройство будет использоваться для фиксации переговоров в гражданской авиации.
Читать дальше →

Прямой репортаж с рождения крупного игрока в аппаратном AI, который ускоряет TensorFlow и конкурирует с NVidia

Время на прочтение5 мин
Количество просмотров9.7K


Завтра будут официальные пресс-релизы о слиянии старожила Silicon Valley, компании MIPS, с молодой AI компанией Wave Computing. Информация об этом событии просочилась в СМИ вчера, и вскоре CNet, Forbes, EE Times и куча хайтек-сайтов вышла со статьями об этом событии. Поэтому сегодня Derek Meyer, президент объединенной компании (на фото снизу справа), сказал «ладно, распостраняйте инфо среди друзей» и я решил написать пару слов о технологиях и людях, связанных с этим событием.

Главный инвестор в MIPS и Wave — миллиардер Dado Banatao (на фото снизу в центре слева), который еще в 1980-х основал компанию Chips & Technoilogies, которая делала чипсеты для ранних персоналок. В Wave+MIPS есть и другие знаменитости, например Стивен Джонсон (на фото справа вверху), автор самого популярного C-компилятора начала 1980-х годов. MIPS хорошо известен и в России. В руках дизайнерши Смрити (на фото слева) плата из Зеленограда, где находятся лицензиаты MIPS Элвис-НеоТек и Байкал Электроникс.

Wave уже выпустила чип, который состоит из тысяч вычислительных блоков, по сути упрощенных процессоров. Эта конструкция оптимизирована для очень быстрых вычислений нейронных сетей. У Wave есть компилятор, который превращает dataflow граф в файл конфигурации для этой структуры.

Объединенная компания создаст чип, который состоит из смеси таких вычислительных блоков и многопоточных ядер MIPS. Сейчас Wave продает свою технологию в виде ящика для дата-центров, для вычислений нейронных сетей в облаке. Следующие чипы будут использоваться во встроенных устройствах.
Читать дальше →

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

Время на прочтение17 мин
Количество просмотров17K

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


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


Спроектируем 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
```<cut/>
В тестбенче загрузим 0001 по адресу 00, 0010 по адресу 01, 0100 по адресу 10, 1000 по адресу 11:
<!--<spoiler title="Создание тестбенча">-->

Создать новый проект, создать файлы R0.v и tR0.v (эти файлы автоматически добавятся к проекту). 
Скомпилировать оба файла.
Запустить моделирование скомпилированного файла tR0.v

<!--</spoiler>-->```verilog
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х_входовым```verilog
always @*
MUX4 = MUX_switch[1]? (MUX_switch[0]? RAM_out: subtract)
: (MUX_switch[0]? sum: data_in);


Подключим к аккумулятору устройство вывода (4bit'ный регистр), также подключим к аккумулятору  2 флага:

1. Флаг "Ноль" - это лог. элемент 4ИЛИ-НЕ.  Флаг поднимается, если содержимое Асс равно нулю. 

2. Флаг "Ноль или Положительное число" - это лог. элемент НЕ на старшем разряде четырёхразрядного аккумулятора. Флаг поднимается, если содержимое Асс больше или равно нулю.
```verilog
//флаг "Ноль" 
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


  4. Загружаем числа в ОЗУ


  5. Обнуляем Асс


  6. Переключаем MUX2


  7. Вычитаем первое число (записанное в ОЗУ) из Асс


  8. Вычитаем второе число (записанное в ОЗУ) из Асс


  9. Загружаем сумму в ОЗУ и в 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


<img src="https://habrastorage.org/webt/jb/fq/c4/jbfqc45piovx4besdmw0pkxz_oa.gif" />

Проверим, что когда в Асс лежит положительное число, перехода Z_JMP не происходит:
```verilog
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) здесь
Инструкция по установке здесь.

Странности синтеза при работе с FPGA

Время на прочтение5 мин
Количество просмотров10K
На сегодняшний день существует два наиболее распространённых языка описания аппаратуры: Verilog/SystemVerilog и VHDL. Сами языки описания аппаратуры являются достаточно универсальными средствами, но всегда ли это так? И от чего может зависеть «не универсальность» языка описания аппаратуры?

Идея написания данной статьи возникла при синтезе одного проекта в разных средах разработки, в результате чего были получены отличные друг от друга результаты. Так как исходный модуль является достаточно объёмным, то для демонстрации полученных результатов был написан тестовый модуль меньшего объёма, но синтез которого вызывал те же предупреждения/ошибки. В качестве тестового модуля был использован 4-х битный регистр с асинхронным сбросом, а в качестве сред разработки были выбраны Libero SoC 18.1, Quartus Prime 17.1, Vivado 2017.4.1.
Читать дальше →

Как мы изобретали оптический рефлектометр

Время на прочтение9 мин
Количество просмотров21K


История про высокий порог входа, забеги по граблям и уверенность в завтрашем дне, а также про оптику, схемотехнику и немного про FPGA. На КДПВ — то, что получилось, работает и используется в production, а ниже — рассказ про процесс создания этого "чуда враждебной техники".


В одно хмурое зимнее утро декабря 2007 года маркетологи небольшой компании, занимающейся разработкой электроники, решили, что пора таки сделать свой OTDR.

Читать дальше →

FPP через FPL: Ускоряем загрузку FPGA

Время на прочтение8 мин
Количество просмотров12K

Всем привет!


Недавно возникла задача — ускорить загрузку FPGA. От появления питания до рабочего состояния у нас есть не более 100 мс. Поскольку чип не самый новый (Altera Cyclone IV GX), просто подключить к нему быструю флешку типа EPCQ не получается. И мы решили задействовать режим FPP (Fast Passive Parallel), поставив снаружи CPLD Intel MAXV с FPL (Flash Parallel Loader). При старте CPLD загружает данные из флешки и формирует сигналы FPP на своих выходах.


Однако, перед тем, как совершить задуманное, собрали DIY-макет из того, что было под рукой, и взялись поэкспериментировать "на кошках". К сожалению, из-за соплей на плате пришлось снизить рабочие частоты, но суть работы FPP от этого не изменилась, зато отладка упростилась. О том, что получилось, и о том, как конфигурируется FPGA, я и решил написать в этой статье. Кому интересно, добро пожаловать под кат.


Читать дальше →

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

Время на прочтение6 мин
Количество просмотров23K

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


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


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


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


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


module R0 #(parameter N = 2, M = 4)
(
input RAM_button, //кнопка
input [N-1:0] adr, //адрес
input [M-1:0] data_in, //порт ввода данных
output [M-1:0] RAM_out //порт вывода данных
);
reg [M-1:0] mem [2**N-1:0]; //объявляем массив mem
always @(posedge RAM_button) //при нажатии на кнопку
mem [adr] <= data_in; //загружаем данные в ОЗУ из data_in 
assign RAM_out = mem[adr]; //назначаем RAM_out портом вывода данных
endmodule
```<cut/>
В качестве внешнего генератора подключим КМОП <b>таймер 555</b> (работающий от 3.3V).
Подключим <b>таймер 555</b> к счётчику, подключим счётчик к адресному входу <b>ОЗУ</b>:
```verilog
module R1 #(parameter N = 2, M = 4)
(
input timer555, RAM_button,
//input [N-1:0] adr,
input [M-1:0] data_in,
output [M-1:0] RAM_out
);
reg [1:0]counter; //объявляем счётчик
always @(posedge timer555) //при поступлении тактового сигнала
 counter <= counter + 1;  // счетчик увеличивается на 1
 wire [N-1:0] adr;
 assign adr = counter; // подключаем счётчик на адресный вход ОЗУ
reg [M-1:0] mem [2**N-1:0];
always @(posedge RAM_button)
 mem [adr] <= data_in;
assign RAM_out = mem[adr];
endmodule

Здесь при описании счетчика counter и памяти mem используются неблокирующие присвоения <= Операторы присвоения рассматриваются на сайте marsohod.org здесь
Описание работы счетчика есть на сайте marsohod.org здесь


Добавим в счетчик функцию загрузки.
Загрузка осуществляется командой Counter_load:


//input Counter_load; 
wire [3:0] branch_adr; // адрес перехода
assign branch_adr = data_in; 
always @(posedge timer555)
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_button,
  output reg [3:0] q  
);
always @(posedge reg_button)
         q <= reg_data;
endmodule

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


module R2 #(parameter ADDR_WIDTH = 2, DATA_WIDTH = 4)
(
input timer555, Counter_load, RAM_button,
input MUX_switch,
input Acc_button, 
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 timer555)
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_button)
mem [adr] <= Acc;
assign RAM = mem[adr];
//sum
wire [3:0] sum;
assign sum =  Acc + RAM;
//MUX
reg [3:0] MUX2; 
always @*  // Always @* — значит «всегда» 
MUX2 = MUX_switch ? sum : data_in;
//Accumulator
register4 Acc_reg(
.reg_data(MUX2),
.reg_button(Acc_button),
.q(Acc)
);
endmodule

Always @ — значит «всегда». Некоторые синтезаторы не понимают эту конструкцию. Мультиплексор можно написать и без Always @ (тут используется просто для примера).



Вычитание


Для того, чтобы произвести вычитание, надо представить вычитаемое число в дополнительном коде. Про сложение и вычитание двоичных чисел можно прочитать в учебнике "Цифорвая схемотехника и архитектура компьютера" (Дэвид М. Харрис и Сара Л. Харрис) в главе 1.4.6 Знак двоичных чисел


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


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

Заменим 2-входовой мультиплексор 4-входовым:


always @*
MUX4 = MUX_switch[1] ? (MUX_switch[0] ? RAM : subtract)
: (MUX_switch[0] ? sum : data_in);

Подключим к аккумулятору устройство вывода (4bit'ный регистр), также подключим к аккумулятору 2 флага:


  1. Флаг "Ноль" — это лог. элемент 4ИЛИ-НЕ. Флаг поднимается, если содержимое Асс равно нулю.


  2. Флаг "Ноль или Положительное число" — это лог. элемент НЕ на старшем разряде 4-разрядного аккумулятора. Флаг поднимается, если содержимое Асс больше или равно нулю.



//флаг "Ноль" 
output Z_flag;
assign Z_flag =  ~(|Acc); // 4-входовой вентиль ИЛИ-НЕ
//флаг "Ноль или Положительное число"
output PZ_flag;
assign PZ_flag =  ~Acc[3]; 

4ИЛИ-НЕ

Здесь мы описали многовходовой вентиль ИЛИ-НЕ как ~(|Acc)
Также в языке Verilog поддерживается набор типов логических вентилей (Gate Types).


Для логических вентилей определены ключевые слова: and (И), nand (И-НЕ), or (ИЛИ), nor (ИЛИ-НЕ), xor (Исключающее ИЛИ), xnor (Исключающее ИЛИ-НЕ), buf (Буферный элемент), not (Отрицание, НЕ).


В Verilog при использовании вентилей необходимо задать входы и выходы элемента, а также (не обязательно) имя вентиля. Например, вентили and и or должны иметь один выход и два и более входов. Так, для вентиля nor имеем
nor name listof arguments
nor mynor(out, in0, in1, in2, in3);



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


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

module R3 #(parameter ADDR_WIDTH = 2, DATA_WIDTH = 4)
(
input timer555, RAM_button,
input JMP, Z_JMP, PZ_JMP,
input [1:0] MUX_switch,
input Acc_button, 
input Output_button,
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 timer555)
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_button)
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_button),
.q(Acc)
);
register4 Output_reg(
.reg_data(Acc),
.reg_clk(Output_button),
.q(data_out)
);
assign Z_flag =  ~(|Acc);
assign PZ_flag =  ~Acc[3]; 
endmodule


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



Схему можно скачать отсюда.


В первых восьми разрядах хранятся команды, в последних четырех разрядах хранится адрес, загружаемый в счётчик.


Вообще, загрузка числа в аккумулятор Асс должна производиться после переключения мультиплексора 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/


Алгоритм работает так: сохраняем в память данных два числа из data_in. Вычитаем из второго числа первое:


  • если результат отрицательный, записываем первое число в Асс, записываем в data_out число из Асс;
  • если результат положительный, записываем второе число в Асс, записываем в data_out число из Асс.

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



Элемент НЕ на управляющем входе счётчика, необходимый для загрузки данных в счётчик — это такая особенность программы Logisim, в реальных схемах элемент НЕ на управляющем входе не требуется (по крайней мере я таких счётчиков не знаю).


Quartus II можно скачать с официального сайта.


При регистрации в разделе My Primary Job Function is* необходимо выбрать пункт Student.
Далее необходимо скачать драйвер для программатора (драйвер для usb-blaster'a можно установить из C:\altera...\quartus\drivers\usb-blaster).


Logisim можно скачать здесь.

Организация шинной инфраструктуры, соединяющей агенты системного интерфейса STI версии 1.0

Время на прочтение13 мин
Количество просмотров3K
В статье изложены принципы построения инфраструктуры локальной системной шины, соединяющей агенты одного сегмента стыка простого исполнителя STI версии 1.0 в объёме кристалла СБИС или ПЛИС. Рассмотрена организация дешифратора адреса, коммутаторов шин чтения данных и выборки исполнителя. Приведен пример описания шинной инфраструктуры сегмента STI на языке Verilog. Предложены варианты подключения исполнителей к сегментам шины с меньшей разрядностью данных.
Читать дальше →

Синхронный системный интерфейс взаимодействия с периферийными блоками в объёме кристалла СБИС или ПЛИС. STI 1.0

Время на прочтение21 мин
Количество просмотров6.3K
В статье предложена организация взаимодействия функциональных блоков в объёме кристалла СБИС, а именно: процессорных ядер, контроллеров DMA и мостов системных шин с периферийными блоками, такими как: контроллеры GPIO, SPI, I2C, UART, таймеры и широтно-импульсные модуляторы – ШИМ. Рассмотрен набор сигналов и протокол обмена стыка простого исполнителя – локального системного интерфейса, реализующего взаимодействие перечисленных блоков кристалла. Приведены примеры синтезируемых моделей контроллера GPIO и регистрового файла, поддерживающие описанный интерфейс.
Читать дальше →

Настройка Sublime Text 3 для работы с VHDL файлами

Время на прочтение7 мин
Количество просмотров11K

Работа с VHDL в Sublime Text 3


Редактор Sublime Text существенно экономит время при работе с vhdl и verilog файлами. Для тех, кто не работал с редакторами типа Sublime Text, Notepad++ и т.п. опишу основные полезные функции данных редакторов:

  • множественное выделение/редактирование строк кода (нажатие средней кнопки мыши или при зажатой клавише Ctrl)
  • установка меток (закладок) в коде, помогает ориентироваться в больших файлах. (Ctrl + F2 или через пункт меню Goto→ Bookmarks)
  • возможность разделения рабочей области на несколько окон (Alt + Shift + 2 или из меню View→ Layout)
  • открытие одного файла несколько раз (File→ New View into File)
  • комментирование выделенных строк кода (Ctrl + /)
  • поиск и замена (Ctrl + h)
  • поиск по всем открытым файлам (Ctrl+Shift+f)
  • вставка сниппетов (шаблонов кода) (написать ключевое слово + клавиша Tab)
  • написание и использование функций на языке python
  • возможность установки различных дополнений
  • гибкая настройка
Читать дальше →

Ближайшие события

Распределённая система управления на базе SoC FPGA

Время на прочтение10 мин
Количество просмотров8.7K
Реализация связки прошивки ПЛИС, ПО микроконтроллера NIOS и управляющего ПО под Linux на базе Altera Cyclone V SoC с использованием Avalon Mailbox для создания на их основе распределенной системы управления.
Читать дальше →

Генерация и тестирование ядра RISC-V

Время на прочтение5 мин
Количество просмотров12K
Как я писал в прошлом посте, название моего проекта на GSoC-2016 — «порт RISC-V на Parallella», и первая вещь, которую я должен был сделать, это познакомиться с экосистемой RISC-V. Один из лучших способов это сделать, — посмотреть видео с презентации первого воркшопа RISC-V на Youtube. Для того, чтобы понять самые важные концепции, я рекомендую следующие презентации:


Ещё одна ссылка, если вы интересуетесь Chisel, языком, основанным на Scala, который используется для описания текущей аппаратной реализации ядра RISC-V (ядро Rocket имеет in-order конвейер, BOOM — out-of-order), и любых будущих реализаций.

Краткое руководство по Chisel (Jonathan Bachrach) видео слайды

Работа с Rocket Chip, добавление расширений, инфраструктура ASIC и FPGA (Colin Schmidt) видео слайды

Если вы глубоко заинтересованы в RISC-V и развитии сообщества, я предлагаю вам принять участие в воркшопах.


Читать дальше →

ECO Flow в Vivado или работа в режиме редактирования нетлиста

Время на прочтение37 мин
Количество просмотров25K

Аннотация


В статье рассмотрен режим работы Vivado, позволяющий вносить изменения в проект на уровне редактирования списка соединений (в дальнейшем – нетлиста). Описаны как сам режим ECO, так и некоторые нюансы, которые появляются во время работы в нём. Приведён демонстрационный пример и описана полная последовательность действий для получения результата, в работоспособности которой может убедиться каждый желающий. Статья будет полезна для «общего развития» FPGA-разработчикам, а особенно — тем, кто часто отлаживает проекты в Logic Analyzer. Надеюсь, работа в этом режиме вызовет интерес у разработчиков, работающих с большими кристаллами, время компиляции в которых может достигать часов (а то и десятков часов), поскольку в этом режиме время, затрачиваемое на имплементацию, при внесении изменений в нетлист может сократиться до буквально пары минут.




Оглавление



В статье очень много картинок не в спойлерах (140 штук). Пожалуйста, будьте внимательны, если заходите с телефона


Читать дальше →

Использование произвольных DataFlash 25-й серии вместо дорогих конфигураторов FPGA Altera без дополнительной аппаратуры

Время на прочтение7 мин
Количество просмотров12K
Из любого описания на популярные FPGA известно, что для хранения их конфигураций используются специальные микросхемы – конфигураторы. В комментариях меня поправили, что потихоньку набирают популярность и FPGA, где это не требуется, но пока многие делают разработки именно на классике, где нужен конфигуратор, и с этим надо считаться. Например, для Altera Cyclone используются микросхемы EPCS. Ниже схема от типичной макетной платы с кристаллом семейства Cyclone IV. На ней мы видим конфигуратор EPCS16. Всё бы ничего, но традиционно эти конфигураторы достаточно дороги, поэтому хотелось бы использовать что-нибудь подешевле.

Подробности в длиннющем тексте ниже. Если же очень интересно, но лень читать так много букв, то можно упростить задачу, посмотрев подробное 20-минутное видео.

Читать дальше →

Преобразователи Time-To-Digital (TDC): что это такое и как они реализованы в FPGA

Время на прочтение8 мин
Количество просмотров19K

На рисунке — первый в мире спутник квантовой связи «Мо-Цзы», который запустили из Китая в 2016 году, в нем летает TDC, реализованная в FPGA.

Объяснить своей девушке (или парню), что такое ADC и DAC, и в каких домашних приборах они используются, может каждый человек, называющий себя инженером. А вот что такое TDC, и почему у нас дома их нет, зачастую можно узнать только после свадьбы.

TDC — это time-to-digital converter. По-русски говоря: времяизмерительная система.

Основные потребители быстродействующих TDC — научные группы. Как правило, под определенный исследовательский проект требуется что-то очень специфическое. То каналов надо много, то разрешение очень высокое, то исполнение компактное. А уровень развития современных FPGA и их доступность как раз дают исследователям возможность экспериментировать с реализациями и подстраивать их под собственные нужды.

В этой хабрастатье приводится детальное описание простенькой времяизмерительной системы на FPGA Cyclone IV. Статья будет полезна не только для расширения кругозора, но и с методической точки зрения, поскольку реализация системы нетривиальная.
Читать дальше →

FPGA-ускорители уходят в облака

Время на прочтение11 мин
Количество просмотров16K
FPGA-ускорители уходят в облака, статья от Selectel

Появление на рынке FPGA-ускорителей, которые можно перепрограммировать сколь угодное число раз, причем на языке высокого уровня типа "С", стало настоящим прорывом в нише высокопроизводительных вычислений. Но не меньшим прорывом стала возможность использовать технологию FPGA, не покупая эти весьма дорогостоящие адаптеры (цена в России от 250 тыс.руб.), — а просто арендуя выделенный сервер с ускорителем в облаке провайдера.

Читать дальше →

О портировании проекта MIPSfpga

Время на прочтение10 мин
Количество просмотров3.3K


Перенос программного обеспечения с одной компьютерной архитектуры на другую в принципе, с некоторыми оговорками, дело относительно простое. Здесь на помощь приходят такие общеизвестные инструменты, как autoconf / automake/ libtool / gnulib. Собрать программу из исходников на каком нибудь Raspberry/ARM бывает так же просто, как и на ПК с Ubuntu/x86-64.

А вот как заставить проект ПЛИС разработанный для одной платы работать на другой плате? Там и сама ПЛИС может быть другой и на плате совершенно другие компоненты могут стоять. Простой перекомпиляцией проекта не обойтись.

Расскажу о своем опыте портирования проекта MIPSfpga для платы Марсоход3 с ПЛИС MAX10 Intel. Статьи о проекте MIPSfpga неоднократно появлялись на хабре. Они были так интересны, что мне захотелось и самому попробовать этот проект в имеющейся у меня плате. В своей работе я опирался на хабровские статьи


И многие другие…

Итак, что нужно сделать, чтобы портировать проект ПЛИС на другую плату?

Вклад авторов