Добавление testbench'ей на языке Verilog в проект Vivado — различия между версиями

Материал из SRNS
Перейти к: навигация, поиск
(Запуск симуляции)
(Ссылки)
 
(не показаны 35 промежуточных версий 3 участников)
Строка 1: Строка 1:
 
{{TOCright}}
 
{{TOCright}}
  
Пусть у нас есть дизайн для Vivado, проект которого разворачивается в соответствии со статьей [[Vivado и Git]]. Возможно, это конечный дизайн, возможно - сабмодуль для другого дизайна. Процедура добавления test bench'ей (далее TB) отличаться не будет, поэтому дальнейшее рассмотрение продолжим на примере сабмодуля '''imitator'''.  
+
Пусть у нас есть дизайн для Vivado, проект которого разворачивается в соответствии со статьей [[Vivado и Git]]. Это может быть как конечный дизайн, так и чей-то сабмодуль. Процедура добавления test bench'ей (далее TB) отличаться не будет, поэтому рассмотрим всё на примере сабмодуля '''imitator'''.  
  
  
'''Задача''' - добавить TB'и для модулей imitator'а, причем  
+
'''Задача''' - добавить TB'и для модулей дизайна, причем  
 
* они должны храниться в СКВ и быть доступны всем разработчикам,
 
* они должны храниться в СКВ и быть доступны всем разработчикам,
 
* имеются в виду TB'и на языке Verilog для симуляторов типа Vivado Simulator, ModelSim и т.д., а не тесты на языках Си или Matlab для Verilator'а.  
 
* имеются в виду TB'и на языке Verilog для симуляторов типа Vivado Simulator, ModelSim и т.д., а не тесты на языках Си или Matlab для Verilator'а.  
Строка 16: Строка 16:
  
  
Наша задача разбивается на несколько этапов:
+
[[file:20160404_KDPV.gif|thumb|right|400px]]
* написать код самих TB'ей, запустить, получить результаты,
+
* понять, какие файлы охватывает TB,
+
* внести изменения в скрипт регенерации проекта так, чтобы сохранились все настройки и файлы TB.
+
  
Приведу один из вариантов решения подзадач, не требующий знания tcl скриптов и опирающийся по-максимуму на GUI.
+
Наша конечная цель - файлы TB'ей и правильно написанный скрипт регенерации проекта, включающий раскладывание TB'ей по полочкам. Будем считать, что пользователь по-максимуму хочет использовать GUI и по-минимуму консоль и TCL. Тогда вырисовывается следующий workflow:
 +
* средствами GUI создать новый набор файлов для моделирования (включающий код TB'а, тестируемые модули и т.п.),
 +
* через GUI настроить этот набор,
 +
* через GUI выгрузить код регенерации,
 +
* подправить существующий скрипт регенерации проекта так, чтобы TB'и разворачивались и настраивался вместе с проектом.
  
== Создание нового набора для симуляции в GUI ==
 
  
Накидаем через GUI новый TB, а потом перенесем его в tcl-скрипт! Начнем с TB для модуля '''imichnl_synthesizer'''.
+
== Добавление TB через GUI ==
 +
 
 +
=== Создание нового набора для симуляции ===
 +
 
 +
Создадим через GUI новый TB, а потом перенесем его в tcl-скрипт! Начнем с TB для модуля '''imichnl_synthesizer'''.
  
 
В ''Flow Navigator'' (это панель слева в Vivado) в разделе ''Simulation'' выбираем ''Simulation Settings''
 
В ''Flow Navigator'' (это панель слева в Vivado) в разделе ''Simulation'' выбираем ''Simulation Settings''
Строка 46: Строка 50:
 
[[file:20160404_vivado_revolution12.png|center]]
 
[[file:20160404_vivado_revolution12.png|center]]
  
== Добавляем файлы в набор для симуляции ==
+
=== Добавляем файлы в набор для симуляции ===
  
 
В контекстном меню файлсета sim_imichnl_synthesizer, выпадающем при нажатии правой кнопкой мыши, выбираем добавление новых файлов ''Add Sources''
 
В контекстном меню файлсета sim_imichnl_synthesizer, выпадающем при нажатии правой кнопкой мыши, выбираем добавление новых файлов ''Add Sources''
Строка 66: Строка 70:
 
[[file:20160404_vivado_revolution15.png|center]]
 
[[file:20160404_vivado_revolution15.png|center]]
  
== Код TB'а ==
+
=== Код TB'а ===
  
Пришло время наполнить imichnl_synthesizer_tb смысловым содержанием. Общий сброс, после чего каждую эпоху PHASE_RATE увеличивается на 2000000:
+
Пришло время наполнить imichnl_synthesizer_tb смысловым содержанием. Общий сброс, после чего каждую эпоху PHASE_RATE увеличивается на 500:
 
{{Hider|title = imichnl_synthesizer_tb.v
 
{{Hider|title = imichnl_synthesizer_tb.v
 
  |content = <source lang="verilog">
 
  |content = <source lang="verilog">
`timescale 1ns / 1ps
+
`timescale 100ps / 1ps
  
 
module imichnl_synthesizer_tb();
 
module imichnl_synthesizer_tb();
Строка 114: Строка 118:
 
     end
 
     end
 
      
 
      
     always  
+
     always // 105.6 MHz
         #5 pclk = !pclk;
+
         #47 pclk = !pclk;
 
        
 
        
 
     event reset;
 
     event reset;
Строка 150: Строка 154:
 
         end
 
         end
 
     end     
 
     end     
 +
 +
    initial begin
 +
        forever begin
 +
            #10000500 -> epoch;
 +
        end
 +
    end
 +
   
 +
    initial begin
 +
        #2000000
 +
        forever begin
 +
            #10000000 -> fix;
 +
        end
 +
    end
  
 
     initial begin: TEST_CASE
 
     initial begin: TEST_CASE
 
         #10 -> reset;
 
         #10 -> reset;
 +
        forever begin
 +
            #3000000
 +
            phase_rate = phase_rate + 500;
 +
            @ (epoch);
 +
        end
 +
    end
 +
 +
endmodule
 +
</source>
 +
|hidden = 1
 +
}}
 +
 +
=== Моделирование ===
 +
 +
После того как TB написан, запускаем симуляцию через контекстное меню файлсета:
 +
[[file:20160404_vivado_revolution16.png|center]]
 +
 +
Моделируем, настраиваем wave-форму
 +
[[file:20160404_vivado_revolution20.png|center]]
 +
 +
Cохраняем настройки wave-формы в '''каталог tb''' через меню ''File->Save Waveform Configuration'', автоматом получая имя файла типа '''imichnl_synthesizer_tb_behav.wcfg'''
 +
 +
== Добавление TB'а через скрипт регенерации проекта ==
 +
 +
Сейчас все настройки TB'а, т.е. файлсета sim_imichnl_synthesizer, хранятся в песочнице, которая у нас не находится под системой контроля версий. Нужно добавить соответствующий код в скрипт регенерации проекта (традиционно его место до объявления настроек синтеза synth_1)
 +
{{Hider|title = Вставка в prj_imitator.tcl для регенерации sim_imichnl_synthesizer
 +
|content = <source lang="tcl">
 +
# =============== imichnl_synthesizer module test bench ====================
 +
# Create 'sim_imichnl_synthesizer' fileset (if not found)
 +
if {[string equal [get_filesets -quiet sim_imichnl_synthesizer] ""]} {
 +
  create_fileset -simset sim_imichnl_synthesizer
 +
}
 +
 +
set obj [get_filesets sim_imichnl_synthesizer]
 +
set files [list \
 +
              "[file normalize "$origin_dir/tb/imichnl_synthesizer_tb.v"]"\
 +
              "[file normalize "$origin_dir/tb/imichnl_synthesizer_tb_behav.wcfg"]"\
 +
              "[file normalize "$origin_dir/verilog/imichnl_synthesizer.v"]"\
 +
              ]
 +
add_files -norecurse -fileset $obj $files
 +
 +
# Set 'sim_imichnl_synthesizer' fileset properties
 +
set obj [get_filesets sim_imichnl_synthesizer]
 +
set_property "source_set" "" $obj
 +
set_property "top" "imichnl_synthesizer_tb" $obj
 +
set_property "xelab.nosort" "1" $obj
 +
set_property "xelab.unifast" "" $obj
 +
set_property "xsim.view" "$origin_dir/tb/imichnl_synthesizer_tb_behav.wcfg" $obj
 +
# ============ End of imichnl_synthesizer module test bench ====================
 +
 +
</source>
 +
|hidden = 1
 +
}}
 +
 +
Как я получил этот код? Я просто выгрузил через ''File -> Write Project Tcl'' новый скрипт регенерации проекта и вычленил из него блок, отвечающий за наш новый файлсет.
 +
 +
Ниже мы подробнее рассмотрим команды, используемые для регенерации TB'а. Сделаем мы это на примере второго TB'а, т.к. он немного сложнее.
 +
 +
== Добавляем второй TB ==
 +
 +
Проделываем аналогичные действия для второго TB'а, получаем набор для моделирования sim_imitator_channel, включающий, помимо прочего, imitator_channel_tb.v.
 +
{{Hider|title = imitator_channel_tb.v
 +
|content = <source lang="tcl">
 +
`timescale 100ps / 1ps
 +
 +
`include "global_param.v"
 +
`include "imichnl_param.v"
 +
 +
module imitator_channel_tb();
 +
 +
    parameter BASE_ADDR = `ADDR_WIDTH'h8000;
 +
   
 +
    // [in]
 +
    reg            clk;               
 +
    reg            pclk;             
 +
    reg            reset_n;
 +
    reg            wr_en;             
 +
    reg            rd_en;
 +
    reg [`ADDR_WIDTH - 1 : 0] reg_addr;
 +
    reg [31 : 0]    wdata;
 +
    reg            intr_pulse;
 +
    reg            fix_pulse;
 +
 +
    // [out]
 +
    wire [31 : 0] rdata;
 +
    wire [`IMI_CHNLOUTWIDTH - 1 : 0] i_ch; // Синфазная компонента i-го канала
 +
    wire [`IMI_CHNLOUTWIDTH - 1 : 0] q_ch; // Квадратурная компонента i-го канала
 +
   
 +
`define chNum 0   
 +
    imitator_channel
 +
    #(BASE_ADDR + (`chNum << 6)) IMI_CH (
 +
        .clk          (clk),            // In
 +
        .pclk        (pclk),
 +
        .reset_n      (reset_n),
 +
        .wr_en        (wr_en),
 +
        .rd_en        (rd_en),
 +
        .reg_addr    (reg_addr),
 +
        .wdata        (wdata),
 +
        .intr_pulse  (intr_pulse),
 +
        .fix_pulse    (fix_pulse),
 +
        .rdata        (rdata),          // Out
 +
        .i            (i_ch),
 +
        .q            (q_ch)         
 +
    );
 +
 +
    initial begin
 +
        clk = 0;
 +
        pclk = 0;
 +
        reset_n = 1;
 +
        wr_en = 0;
 +
        rd_en = 0;
 +
        reg_addr = 0;
 +
        wdata = 0;       
 +
        intr_pulse = 0;
 +
        fix_pulse = 0;
 +
    end
 +
   
 +
    always  // 105.6 MHz
 +
        #47 pclk = !pclk;
 +
       
 +
    always // 60 MHz
 +
        #83 clk = !clk;
 +
       
 +
    event reset;
 +
    event irq;
 +
    event fix;
 +
   
 +
    event write;
 +
    event writeDone;
 +
    event read;
 +
    event readDone;
 +
   
 +
    initial begin
 +
        forever begin
 +
            @ (reset)
 +
            @ (negedge pclk)
 +
            reset_n = 0;
 +
            @ (negedge pclk)
 +
            reset_n = 1;
 +
        end
 +
    end
 +
   
 +
    initial begin
 +
        forever begin
 +
            @ (irq)
 +
            @ (negedge pclk)
 +
            intr_pulse = 1;
 +
            @ (negedge pclk)
 +
            intr_pulse = 0;
 +
        end
 +
    end
 +
   
 +
    initial begin
 +
        forever begin
 +
            @ (fix)
 +
            @ (negedge pclk)
 +
            fix_pulse = 1;
 +
            @ (negedge pclk)
 +
            fix_pulse = 0;
 +
        end
 +
    end   
 +
   
 +
    initial begin
 
         fork // Распараллеливание блоков
 
         fork // Распараллеливание блоков
 
             forever begin
 
             forever begin
                 #101 -> epoch;
+
                 #6000000 -> irq;
                phase_rate = phase_rate + 20000000;
+
 
             end
 
             end
            #30
 
 
             forever begin
 
             forever begin
                 #100 -> fix;
+
                 #10000000 -> fix;
 
             end
 
             end
 
         join
 
         join
 +
    end
 +
   
 +
    initial begin
 +
        forever begin
 +
            @ (write)
 +
            @ (negedge clk)
 +
            wr_en = 1;
 +
            @ (negedge clk)
 +
            @ (negedge clk)
 +
            @ (negedge clk)
 +
            wr_en = 0;
 +
            -> writeDone;
 +
        end
 +
    end
 +
   
 +
    initial begin
 +
        forever begin
 +
            @ (read)
 +
            @ (negedge clk)
 +
            rd_en = 1;
 +
            @ (negedge clk)
 +
            @ (negedge clk)
 +
            @ (negedge clk)
 +
            rd_en = 0;
 +
            -> readDone;
 +
        end
 +
    end 
 +
     
 +
    initial begin: TEST_CASE
 +
        #10 -> reset;
 +
 +
        #500
 +
     
 +
        // Write PHASE_RATE
 +
        reg_addr = (`chNum << 6) + `PHASE_RATE_OFFSET;
 +
        wdata = 32'd1000000;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        // Configure GLONASS ST
 +
        reg_addr = (`chNum << 6) + `CODE_STATE1_OFFSET;
 +
        wdata = 32'hFFFFFFFF;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        reg_addr = (`chNum << 6) + `CODE_BITMASK1_OFFSET;
 +
        wdata = 32'h08800000;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        reg_addr = (`chNum << 6) + `CODE_OUT_BITMASK1_OFFSET;
 +
        wdata = 32'h0x02000000;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        reg_addr = (`chNum << 6) + `CODE_STATE2_OFFSET;
 +
        wdata = 32'h00000000;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        reg_addr = (`chNum << 6) + `CODE_BITMASK2_OFFSET;
 +
        wdata = 32'h00000000;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        reg_addr = (`chNum << 6) + `CODE_OUT_BITMASK2_OFFSET;
 +
        wdata = 32'h0x00000000;
 +
        -> write;
 +
        @ (writeDone);           
 +
       
 +
        reg_addr = (`chNum << 6) + `PRN_LENGTH_OFFSET;
 +
        wdata = 511-1;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        reg_addr = (`chNum << 6) + `PRN_LENGTH1_OFFSET;
 +
        wdata = 511-1;
 +
        -> write;
 +
        @ (writeDone);     
 +
       
 +
        reg_addr = (`chNum << 6) + `CHIP_MAX_OFFSET;
 +
        wdata = 511-1;
 +
        -> write;
 +
        @ (writeDone); 
 +
       
 +
        reg_addr = (`chNum << 6) + `PRN_INIT_OFFSET;
 +
        wdata = 0;
 +
        -> write;
 +
        @ (writeDone);   
 +
       
 +
        reg_addr = (`chNum << 6) + `PRN_INIT1_OFFSET;
 +
        wdata = 0;
 +
        -> write;
 +
        @ (writeDone);     
 +
       
 +
        reg_addr = (`chNum << 6) + `BOC_REGS_OFFSET;
 +
        wdata = 0;
 +
        -> write;
 +
        @ (writeDone);   
 +
       
 +
        reg_addr = (`chNum << 6) + `CODE_RATE_OFFSET;
 +
        wdata = 20783411;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        reg_addr = (`chNum << 6) + `CODE_PHASE_OFFSET;
 +
        wdata = 0;
 +
        -> write;
 +
        @ (writeDone); 
 +
       
 +
        reg_addr = (`chNum << 6) + `EPOCH_AND_TOW_OFFSET;
 +
        wdata = 0;
 +
        -> write;
 +
        @ (writeDone);
 +
       
 +
        reg_addr = (`chNum << 6) + `EPOCH_AND_SYMB_MAX_OFFSET;
 +
        wdata = 1000-1;
 +
        -> write;
 +
        @ (writeDone);   
 +
       
 +
        reg_addr = (`chNum << 6) + `CODE_DOINIT_OFFSET;
 +
        wdata = {16'h12AB, 16'b0};
 +
        -> write;
 +
        @ (writeDone);           
 
     end
 
     end
  
Строка 170: Строка 472:
 
}}
 
}}
  
== Моделирование ==
+
TB задает два типа тактовых сигналов - один процессора, другой от РЧБ. В бесконечном цикле генерируются импульс эпохи и импульс снятия измерений. В TEST CASE дается сигнал сброса, затем канал имитатора конфигурируется под сигнал ГЛОНАСС СТ.
 +
[[File:20160404_vivado_revolution19.png|center]]
  
После того как TB написан, запускаем симуляцию через контекстное меню файлсета:
 
[[file:20160404_vivado_revolution16.png|center]]
 
  
Моделируем, настраиваем wave-форму
+
А вот что мы добавляем в prj_imitator.tcl, чтобы этот набор для моделирования разворачивался вместе с проектом:
[[file:20160404_vivado_revolution17.png|center]]
+
{{Hider|title = Вставка в prj_imitator.tcl для регенерации sim_imitator_channel
 +
|content = <source lang="tcl">
 +
# =============== imitator_channel module test bench ====================
 +
# Create 'sim_imitator_channel' fileset (if not found)
 +
if {[string equal [get_filesets -quiet sim_imitator_channel] ""]} {
 +
  create_fileset -simset sim_imitator_channel
 +
}
  
Cохраняем настройки wave-формы в '''каталог tb''' через меню ''File->Save Waveform Configuration'', автоматом получая имя файла типа '''imichnl_synthesizer_tb_behav.wcfg'''
+
set obj [get_filesets sim_imitator_channel]
 +
set files [list \
 +
"[file normalize "$origin_dir/tb/imitator_channel_tb.v"]"\
 +
"[file normalize "$origin_dir/verilog/top/global_param.v"]"\
 +
"[file normalize "$origin_dir/verilog/imitator_channel.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_synthesizer.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_sin_table.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_param.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_regfile.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_cos_table.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_delay_reg.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync_n.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/time_generator.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_shift_reg.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/sync/verilog/level_sync.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/sync/verilog/signal_sync.v"]"\
 +
"[file normalize "$origin_dir/tb/imitator_channel_tb_behav.wcfg"]"\
 +
]
 +
add_files -norecurse -fileset $obj $files
 +
 
 +
# Set 'sim_imitator_channel' fileset properties
 +
set obj [get_filesets sim_imitator_channel]
 +
set_property "include_dirs" "$origin_dir/verilog $origin_dir/verilog/top" $obj
 +
set_property "runtime" "3000000ns" $obj
 +
set_property "source_set" "" $obj
 +
set_property "top" "imitator_channel_tb" $obj
 +
set_property "xelab.nosort" "1" $obj
 +
set_property "xelab.unifast" "" $obj
 +
set_property "xsim.simulate.runtime" "3000000ns" $obj
 +
set_property "xsim.view" "$origin_dir/tb/imitator_channel_tb_behav.wcfg" $obj
 +
# ============ End of imitator_channel module test bench ====================
 +
</source>
 +
|hidden = 1
 +
}}
 +
 
 +
Обсудим команды, которые добавляются в скрипт. В первую очередь создается новый набор для симуляции, ему присваивается название sim_imitator_channel
 +
<source lang="tcl">
 +
if {[string equal [get_filesets -quiet sim_imitator_channel] ""]} {
 +
  create_fileset -simset sim_imitator_channel
 +
}
 +
</source>
 +
 
 +
 
 +
Далее в этот набор добавляются файлы:
 +
<source lang="tcl">
 +
set obj [get_filesets sim_imitator_channel]
 +
set files [list \
 +
"[file normalize "$origin_dir/tb/imitator_channel_tb.v"]"\
 +
"[file normalize "$origin_dir/verilog/top/global_param.v"]"\
 +
"[file normalize "$origin_dir/verilog/imitator_channel.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_synthesizer.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_sin_table.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_param.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_regfile.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_cos_table.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_delay_reg.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync_n.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/time_generator.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_shift_reg.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/sync/verilog/level_sync.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/sync/verilog/signal_sync.v"]"\
 +
"[file normalize "$origin_dir/tb/imitator_channel_tb_behav.wcfg"]"\
 +
]
 +
add_files -norecurse -fileset $obj $files
 +
</source>
 +
 
 +
 
 +
Далее указываются директории, в которых будет производиться поиск файл, подключенных с помощью директивы <code>`include</code>:
 +
<source lang="tcl">
 +
set obj [get_filesets sim_imitator_channel]
 +
set_property "include_dirs" "$origin_dir/verilog $origin_dir/verilog/top" $obj
 +
</source>
 +
 
 +
 
 +
Указывается топовый модуль для моделирования:
 +
<source lang="tcl">
 +
set_property "top" "imitator_channel_tb" $obj
 +
</source>
 +
 
 +
 
 +
Указывается топовый модуль для моделирования:
 +
<source lang="tcl">
 +
set_property "runtime" "3000000ns" $obj
 +
set_property "xsim.simulate.runtime" "3000000ns" $obj
 +
</source>
 +
 
 +
 
 +
Указывается wave-форма:
 +
<source lang="tcl">
 +
set_property "xsim.view" "$origin_dir/tb/imitator_channel_tb_behav.wcfg" $obj
 +
</source>
 +
 
 +
 
 +
Также в скрипте регенерации мы можем указать, какой набор симуляции считать активным по-умолчанию:
 +
<source lang="tcl">
 +
current_fileset -simset [ get_filesets sim_imitator_channel ]
 +
</source>
 +
 
 +
== Итоговый скрипт регенерации проекта ==
 +
 
 +
 
 +
Итоговый скрипт регенерации проекта imitator будет выглядеть так:
 +
 
 +
{{Hider|title = Скрипт регенерации проекта prj_imitator.tcl
 +
|content = <source lang="tcl">
 +
#!/usr/bin/tclsh
 +
#
 +
# Vivado (TM) v2015.3 (64-bit)
 +
#
 +
# prj_imitator.tcl: Tcl script for re-creating project 'imitator'
 +
#
 +
# Generated by Vivado on Tue Mar 22 10:11:05 +0300 2016
 +
# IP Build 1367837 on Mon Sep 28 08:56:14 MDT 2015
 +
#
 +
# This file contains the Vivado Tcl commands for re-creating the project to the state*
 +
# when this script was generated. In order to re-create the project, please source this
 +
# file in the Vivado Tcl Shell.
 +
#
 +
# * Note that the runs in the created project will be configured the same way as the
 +
#  original project, however they will not be launched automatically. To regenerate the
 +
#  run results please launch the synthesis/implementation runs as needed.
 +
#
 +
 
 +
# Set the reference directory for source file relative paths (by default the value is script directory path)
 +
set origin_dir "."
 +
set sub_dir "sub"
 +
set prj_name "imitator"
 +
set prj_dir_name "prj_imitator"
 +
set topmodule_name "imitator_top"
 +
 
 +
 
 +
# Use origin directory path location variable, if specified in the tcl shell
 +
if { [info exists ::origin_dir_loc] } {
 +
  set origin_dir $::origin_dir_loc
 +
}
 +
 
 +
variable script_file
 +
set script_file "prj_$prj_name.tcl"
 +
 
 +
# Help information for this script
 +
proc help {} {
 +
  variable script_file
 +
  puts "\nDescription:"
 +
  puts "Recreate a Vivado project from this script. The created project will be"
 +
  puts "functionally equivalent to the original project for which this script was"
 +
  puts "generated. The script contains commands for creating a project, filesets,"
 +
  puts "runs, adding/importing sources and setting properties on various objects.\n"
 +
  puts "Syntax:"
 +
  puts "$script_file"
 +
  puts "$script_file -tclargs \[--origin_dir <path>\]"
 +
  puts "$script_file -tclargs \[--help\]\n"
 +
  puts "Usage:"
 +
  puts "Name                  Description"
 +
  puts "-------------------------------------------------------------------------"
 +
  puts "\[--origin_dir <path>\]  Determine source file paths wrt this path. Default"
 +
  puts "                      origin_dir path value is \".\", otherwise, the value"
 +
  puts "                      that was set with the \"-paths_relative_to\" switch"
 +
  puts "                      when this script was generated.\n"
 +
  puts "\[--help\]              Print help information for this script"
 +
  puts "-------------------------------------------------------------------------\n"
 +
  exit 0
 +
}
 +
 
 +
if { $::argc > 0 } {
 +
  for {set i 0} {$i < [llength $::argc]} {incr i} {
 +
    set option [string trim [lindex $::argv $i]]
 +
    switch -regexp -- $option {
 +
      "--origin_dir" { incr i; set origin_dir [lindex $::argv $i] }
 +
      "--help"      { help }
 +
      default {
 +
        if { [regexp {^-} $option] } {
 +
          puts "ERROR: Unknown option '$option' specified, please type '$script_file -tclargs --help' for usage info.\n"
 +
          return 1
 +
        }
 +
      }
 +
    }
 +
  }
 +
}
 +
 
 +
# Create project
 +
create_project $prj_name ./$prj_dir_name
 +
 
 +
# Set the directory path for the new project
 +
set proj_dir [get_property directory [current_project]]
 +
 
 +
# Set project properties
 +
set obj [get_projects $prj_name]
 +
set_property "default_lib" "xil_defaultlib" $obj
 +
set_property "part" "xc7z045fbg676-2" $obj
 +
set_property "sim.ip.auto_export_scripts" "1" $obj
 +
set_property "simulator_language" "Mixed" $obj
 +
set_property "source_mgmt_mode" "DisplayOnly" $obj
 +
 
 +
# Create 'sources_1' fileset (if not found)
 +
if {[string equal [get_filesets -quiet sources_1] ""]} {
 +
  create_fileset -srcset sources_1
 +
}
 +
 
 +
# Set 'sources_1' fileset object
 +
set obj [get_filesets sources_1]
 +
set files [list \
 +
"[file normalize "$origin_dir/verilog/top/$topmodule_name.v"]"\
 +
"[file normalize "$origin_dir/verilog/imitator.v"]"\
 +
"[file normalize "$origin_dir/verilog/imitator_channel.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_regfile.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_param.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_delay_reg.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_sin_table.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_cos_table.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_synthesizer.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync_n.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_shift_reg.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/time_generator.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/sync/verilog/signal_sync.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/sync/verilog/level_sync.v"]"\
 +
]
 +
add_files -norecurse -fileset $obj $files
 +
 
 +
# Set 'sources_1' fileset file properties for remote files
 +
# None
 +
 
 +
 
 +
# Set 'sources_1' fileset properties
 +
set obj [get_filesets sources_1]
 +
set_property "include_dirs" "$origin_dir/verilog $origin_dir/verilog/top" $obj
 +
set_property "top" "$topmodule_name" $obj
 +
 
 +
# Create 'constrs_1' fileset (if not found)
 +
if {[string equal [get_filesets -quiet constrs_1] ""]} {
 +
  create_fileset -constrset constrs_1
 +
}
 +
 
 +
# Set 'constrs_1' fileset object
 +
set obj [get_filesets constrs_1]
 +
 
 +
# Create 'sim_1' fileset (if not found)
 +
# It should exist! If Vivado doesn't found it, once will be generated
 +
if {[string equal [get_filesets -quiet sim_1] ""]} {
 +
  create_fileset -simset sim_1
 +
}
 +
 
 +
# Set 'sim_sim_1' fileset properties
 +
set obj [get_filesets sim_1]
 +
set_property "source_set" "" $obj
 +
set_property "top" "$topmodule_name" $obj
 +
set_property "xelab.nosort" "1" $obj
 +
set_property "xelab.unifast" "" $obj
 +
 
 +
 
 +
# =============== imichnl_synthesizer module test bench ====================
 +
# Create 'sim_imichnl_synthesizer' fileset (if not found)
 +
if {[string equal [get_filesets -quiet sim_imichnl_synthesizer] ""]} {
 +
  create_fileset -simset sim_imichnl_synthesizer
 +
}
 +
 
 +
set obj [get_filesets sim_imichnl_synthesizer]
 +
set files [list \
 +
      "[file normalize "$origin_dir/tb/imichnl_synthesizer_tb.v"]"\
 +
      "[file normalize "$origin_dir/tb/imichnl_synthesizer_tb_behav.wcfg"]"\
 +
      "[file normalize "$origin_dir/verilog/imichnl_synthesizer.v"]"\
 +
      ]
 +
add_files -norecurse -fileset $obj $files
 +
 
 +
# Set 'sim_imichnl_synthesizer' fileset properties
 +
set obj [get_filesets sim_imichnl_synthesizer]
 +
set_property "source_set" "" $obj
 +
set_property "top" "imichnl_synthesizer_tb" $obj
 +
set_property "xelab.nosort" "1" $obj
 +
set_property "xelab.unifast" "" $obj
 +
set_property "xsim.view" "$origin_dir/tb/imichnl_synthesizer_tb_behav.wcfg" $obj
 +
# ============ End of imichnl_synthesizer module test bench ====================
 +
 
 +
 
 +
 
 +
# =============== imitator_channel module test bench ====================
 +
# Create 'sim_imitator_channel' fileset (if not found)
 +
if {[string equal [get_filesets -quiet sim_imitator_channel] ""]} {
 +
  create_fileset -simset sim_imitator_channel
 +
}
 +
 
 +
set obj [get_filesets sim_imitator_channel]
 +
set files [list \
 +
"[file normalize "$origin_dir/tb/imitator_channel_tb.v"]"\
 +
"[file normalize "$origin_dir/verilog/top/global_param.v"]"\
 +
"[file normalize "$origin_dir/verilog/imitator_channel.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_synthesizer.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_sin_table.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_param.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_regfile.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_cos_table.v"]"\
 +
"[file normalize "$origin_dir/verilog/imichnl_delay_reg.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync_n.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/time_generator.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_shift_reg.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/sync/verilog/level_sync.v"]"\
 +
"[file normalize "$origin_dir/$sub_dir/sync/verilog/signal_sync.v"]"\
 +
"[file normalize "$origin_dir/tb/imitator_channel_tb_behav.wcfg"]"\
 +
]
 +
add_files -norecurse -fileset $obj $files
 +
 
 +
# Set 'sim_imitator_channel' fileset properties
 +
set obj [get_filesets sim_imitator_channel]
 +
set_property "include_dirs" "$origin_dir/verilog $origin_dir/verilog/top" $obj
 +
set_property "runtime" "3000000ns" $obj
 +
set_property "source_set" "" $obj
 +
set_property "top" "imitator_channel_tb" $obj
 +
set_property "xelab.nosort" "1" $obj
 +
set_property "xelab.unifast" "" $obj
 +
set_property "xsim.simulate.runtime" "3000000ns" $obj
 +
set_property "xsim.view" "$origin_dir/tb/imitator_channel_tb_behav.wcfg" $obj
 +
# ============ End of imitator_channel module test bench ====================
 +
 
 +
 
 +
current_fileset -simset [ get_filesets sim_imitator_channel ]
 +
 
 +
 
 +
# Create 'synth_1' run (if not found)
 +
if {[string equal [get_runs -quiet synth_1] ""]} {
 +
  create_run -name synth_1 -part xc7z045fbg676-2 -flow {Vivado Synthesis 2014} -strategy "Vivado Synthesis Defaults" -constrset constrs_1
 +
} else {
 +
  set_property strategy "Vivado Synthesis Defaults" [get_runs synth_1]
 +
  set_property flow "Vivado Synthesis 2014" [get_runs synth_1]
 +
}
 +
set obj [get_runs synth_1]
 +
set_property "part" "xc7z045fbg676-2" $obj
 +
 
 +
# set the current synth run
 +
current_run -synthesis [get_runs synth_1]
 +
 
 +
# Create 'impl_1' run (if not found)
 +
if {[string equal [get_runs -quiet impl_1] ""]} {
 +
  create_run -name impl_1 -part xc7z045fbg676-2 -flow {Vivado Implementation 2015} -strategy "Performance_Explore" -constrset constrs_1 -parent_run synth_1
 +
} else {
 +
  set_property strategy "Performance_Explore" [get_runs impl_1]
 +
  set_property flow "Vivado Implementation 2015" [get_runs impl_1]
 +
}
 +
set obj [get_runs impl_1]
 +
set_property "part" "xc7z045fbg676-2" $obj
 +
set_property "steps.opt_design.args.directive" "Explore" $obj
 +
set_property "steps.place_design.args.directive" "Explore" $obj
 +
set_property "steps.phys_opt_design.is_enabled" "1" $obj
 +
set_property "steps.phys_opt_design.args.directive" "Explore" $obj
 +
set_property "steps.route_design.args.directive" "Explore" $obj
 +
set_property "steps.write_bitstream.args.readback_file" "0" $obj
 +
set_property "steps.write_bitstream.args.verbose" "0" $obj
 +
 
 +
# set the current impl run
 +
current_run -implementation [get_runs impl_1]
 +
 
 +
puts "INFO: Project created:$prj_name"
 +
</source>
 +
|hidden = 1
 +
}}
 +
 
 +
 
 +
Добавляем файл регенерации проекта prj_imitator.tcl и файлы в каталоге tb в коммит.
 +
 
 +
== У другого пользователя ==
 +
 
 +
Теперь оба TB'а будут храниться в системе контроля версий. При запуске скрипта регенерации у нового пользователя они будут разворачиваться и настраиваться. Пользователю останется их только запустить.
 +
 
 +
<source lang="bash">
 +
korogodin@Diod:~/Oryx/src/fpga/sub/imitator$ /opt/Xilinx/Vivado/2015.3/bin/vivado -source prj_imitator.tcl
 +
</source>
 +
 
 +
[[file:20160404_vivado_revolution18.png|center]]
  
 
== Ссылки ==
 
== Ссылки ==
  
[http://we.easyelectronics.ru/plis/testbench-na-verilog-dlya-novichkov.html Easyelectronics:TestBench на Verilog для новичков]
+
*[http://we.easyelectronics.ru/plis/testbench-na-verilog-dlya-novichkov.html Easyelectronics:TestBench на Verilog для новичков]
 +
*[http://www.xilinx.com/support/documentation/sw_manuals/xilinx2014_2/ug900-vivado-logic-simulation.pdf Xilinx UG900: Logic Simulation]
 +
*[http://www.xilinx.com/support/answers/64000.html AR64000: Understanding Waveform Database file (WDB) and Wave Configuration File (WCFG)]
 +
*[http://www.compitech.ru/html.cgi/arhiv/01_02/stat_86.htm Verilog — инструмент разработки цифровых электронных схем: О типах данных]
 +
*[http://www.compitech.ru/html.cgi/arhiv/01_03/stat_134.htm Verilog — инструмент разработки цифровых электронных схем: Операторы]
 +
*[http://www.compitech.ru/html.cgi/arhiv/01_04/stat_76.htm Verilog — инструмент разработки цифровых электронных схем: О моделируемых конструкциях]
 +
*[http://compitech.ru/html.cgi/arhiv/01_05/stat_66.htm Verilog — инструмент разработки цифровых электронных схем: О синтезируемых конструкциях]
 +
*[https://inst.eecs.berkeley.edu/~cs150/fa06/Labs/verilog-ieee.pdf Verilog-2001]
 +
*[http://www.uccs.edu/~gtumbush/published_papers/Tumbush%20DVCon%2005.pdf Signed Arithmetic in Verilog 2001 – Opportunities and Hazards]
  
 
[[Категория:HOWTO]]
 
[[Категория:HOWTO]]
 +
[[Категория:Git]]
 +
[[Категория:Xilinx]]
 +
[[Категория:Vivado]]
 +
[[Category:FPGA]]
 +
[[Category:Verilog]]

Текущая версия на 15:54, 9 ноября 2016

Содержание

Пусть у нас есть дизайн для Vivado, проект которого разворачивается в соответствии со статьей Vivado и Git. Это может быть как конечный дизайн, так и чей-то сабмодуль. Процедура добавления test bench'ей (далее TB) отличаться не будет, поэтому рассмотрим всё на примере сабмодуля imitator.


Задача - добавить TB'и для модулей дизайна, причем

  • они должны храниться в СКВ и быть доступны всем разработчикам,
  • имеются в виду TB'и на языке Verilog для симуляторов типа Vivado Simulator, ModelSim и т.д., а не тесты на языках Си или Matlab для Verilator'а.


Подопечные

Для конкретики, будем добавлять в дизайн imitator TB'и для двух модулей:

  • imichnl_synthesizer, отвечающий за фазу несущей,
  • imitator_channel, являющийся топ-модулем для одного канала имитатора и включающий в себя первый модуль.


20160404 KDPV.gif

Наша конечная цель - файлы TB'ей и правильно написанный скрипт регенерации проекта, включающий раскладывание TB'ей по полочкам. Будем считать, что пользователь по-максимуму хочет использовать GUI и по-минимуму консоль и TCL. Тогда вырисовывается следующий workflow:

  • средствами GUI создать новый набор файлов для моделирования (включающий код TB'а, тестируемые модули и т.п.),
  • через GUI настроить этот набор,
  • через GUI выгрузить код регенерации,
  • подправить существующий скрипт регенерации проекта так, чтобы TB'и разворачивались и настраивался вместе с проектом.


[править] Добавление TB через GUI

[править] Создание нового набора для симуляции

Создадим через GUI новый TB, а потом перенесем его в tcl-скрипт! Начнем с TB для модуля imichnl_synthesizer.

В Flow Navigator (это панель слева в Vivado) в разделе Simulation выбираем Simulation Settings

20160404 vivado revolution2.png

В открывшемся окне в разделе Simulation в графе Simulation top modulw name создаем новый файлсет, выбирая Create Simulation Set

20160404 vivado revolution3.png

ВНИМАНИЕ Не занимайте и не удаляйте файлсет sim_1. Vivado его очень любит и будет создавать заново, делая при этим активным. Лучше оставить sim_1 пустым.

Новому файлсету даем осмысленное название, например, sim_imichnl_synthesizer

20160404 vivado revolution7.png

Очищаем графу Simulation top module name, т.к. файл с кодом TB'а у нас ещё не создан.

На вкладке Advanced запрещаем включать в TB все файлы проекта, снимая галку с Include all design sources for simulation. Иначе он добавит все наши файлы в файлсет этого TB'а, что нам не нужно.

20160404 vivado revolution11.png

Закрываем окно, нажимая Ok. Vivado задает вопрос, сделать ли данный TB активным. Можно соглашаться. В итоге в Source проекта появился новый пустой файлсет для симуляции sim_imichnl_synthesizer

20160404 vivado revolution12.png

[править] Добавляем файлы в набор для симуляции

В контекстном меню файлсета sim_imichnl_synthesizer, выпадающем при нажатии правой кнопкой мыши, выбираем добавление новых файлов Add Sources

20160404 vivado revolution13.png

Далее Add or create simulation sources

20160404 vivado revolution4.png

Добавляем новый файл TB'а, нажимая кнопку Create File в открывшемся окне. Даем файлу осмысленное имя с суффиксом _tb, например, imichnl_synthesizer_tb.v и обязательно указываем в качестве пути каталог tb дизайна imitator. Иначе он будет создан в дебрях песочницы (в prj_imitator) и не будет виден системе контроля версий.

20160404 vivado revolution8.png20160404 vivado revolution9.png

С помощью кнопки Add Files добавляем уже существующие файлы, которые потребуются для работы тестируемого модуля. В данном случае это сам модуль imichnl_synthesizer из каталога verilog

20160404 vivado revolution10.png

После добавления требуемых файлов нажимаем кнопку Finish. Открывается окно Define module для нашего TB'а imichnl_synthesizer_tb.v. Порты нам добавлять не нужно, просто нажимаем Ok. Теперь у нас в файлсете sim_imichnl_synthesizer два файла - код исследуемого модуля и код TB'а.

20160404 vivado revolution14.png

Возвращаемся в настройки симуляции (Flow Navigator -> Simulation -> Simulation Settings) и указываем в качестве топового модуль imichnl_synthesizer_tb

20160404 vivado revolution15.png

[править] Код TB'а

Пришло время наполнить imichnl_synthesizer_tb смысловым содержанием. Общий сброс, после чего каждую эпоху PHASE_RATE увеличивается на 500:


[править] Моделирование

После того как TB написан, запускаем симуляцию через контекстное меню файлсета:

20160404 vivado revolution16.png

Моделируем, настраиваем wave-форму

20160404 vivado revolution20.png

Cохраняем настройки wave-формы в каталог tb через меню File->Save Waveform Configuration, автоматом получая имя файла типа imichnl_synthesizer_tb_behav.wcfg

[править] Добавление TB'а через скрипт регенерации проекта

Сейчас все настройки TB'а, т.е. файлсета sim_imichnl_synthesizer, хранятся в песочнице, которая у нас не находится под системой контроля версий. Нужно добавить соответствующий код в скрипт регенерации проекта (традиционно его место до объявления настроек синтеза synth_1)


Как я получил этот код? Я просто выгрузил через File -> Write Project Tcl новый скрипт регенерации проекта и вычленил из него блок, отвечающий за наш новый файлсет.

Ниже мы подробнее рассмотрим команды, используемые для регенерации TB'а. Сделаем мы это на примере второго TB'а, т.к. он немного сложнее.

[править] Добавляем второй TB

Проделываем аналогичные действия для второго TB'а, получаем набор для моделирования sim_imitator_channel, включающий, помимо прочего, imitator_channel_tb.v.


TB задает два типа тактовых сигналов - один процессора, другой от РЧБ. В бесконечном цикле генерируются импульс эпохи и импульс снятия измерений. В TEST CASE дается сигнал сброса, затем канал имитатора конфигурируется под сигнал ГЛОНАСС СТ.

20160404 vivado revolution19.png


А вот что мы добавляем в prj_imitator.tcl, чтобы этот набор для моделирования разворачивался вместе с проектом:


Обсудим команды, которые добавляются в скрипт. В первую очередь создается новый набор для симуляции, ему присваивается название sim_imitator_channel

if {[string equal [get_filesets -quiet sim_imitator_channel] ""]} {
  create_fileset -simset sim_imitator_channel
}


Далее в этот набор добавляются файлы:

set obj [get_filesets sim_imitator_channel]
set files [list \
 "[file normalize "$origin_dir/tb/imitator_channel_tb.v"]"\
 "[file normalize "$origin_dir/verilog/top/global_param.v"]"\
 "[file normalize "$origin_dir/verilog/imitator_channel.v"]"\
 "[file normalize "$origin_dir/verilog/imichnl_synthesizer.v"]"\
 "[file normalize "$origin_dir/verilog/imichnl_sin_table.v"]"\
 "[file normalize "$origin_dir/verilog/imichnl_param.v"]"\
 "[file normalize "$origin_dir/verilog/imichnl_regfile.v"]"\
 "[file normalize "$origin_dir/verilog/imichnl_cos_table.v"]"\
 "[file normalize "$origin_dir/verilog/imichnl_delay_reg.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync_n.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/time_generator.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_shift_reg.v"]"\
 "[file normalize "$origin_dir/$sub_dir/sync/verilog/level_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/sync/verilog/signal_sync.v"]"\
 "[file normalize "$origin_dir/tb/imitator_channel_tb_behav.wcfg"]"\
]
add_files -norecurse -fileset $obj $files


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

set obj [get_filesets sim_imitator_channel]
set_property "include_dirs" "$origin_dir/verilog $origin_dir/verilog/top" $obj


Указывается топовый модуль для моделирования:

set_property "top" "imitator_channel_tb" $obj


Указывается топовый модуль для моделирования:

set_property "runtime" "3000000ns" $obj
set_property "xsim.simulate.runtime" "3000000ns" $obj


Указывается wave-форма:

set_property "xsim.view" "$origin_dir/tb/imitator_channel_tb_behav.wcfg" $obj


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

current_fileset -simset [ get_filesets sim_imitator_channel ]

[править] Итоговый скрипт регенерации проекта

Итоговый скрипт регенерации проекта imitator будет выглядеть так:


Добавляем файл регенерации проекта prj_imitator.tcl и файлы в каталоге tb в коммит.

[править] У другого пользователя

Теперь оба TB'а будут храниться в системе контроля версий. При запуске скрипта регенерации у нового пользователя они будут разворачиваться и настраиваться. Пользователю останется их только запустить.

korogodin@Diod:~/Oryx/src/fpga/sub/imitator$ /opt/Xilinx/Vivado/2015.3/bin/vivado -source prj_imitator.tcl
20160404 vivado revolution18.png

[править] Ссылки

Персональные инструменты
Пространства имён

Варианты
Действия
SRNS Wiki
Рабочие журналы
Приватный файлсервер
QNAP Сервер
Инструменты