Добавление testbench'ей на языке Verilog в проект Vivado
|
Пусть у нас есть дизайн для Vivado, проект которого разворачивается в соответствии со статьей Vivado и Git. Возможно, это конечный дизайн, возможно - сабмодуль для другого дизайна. Процедура добавления test bench'ей (далее TB) отличаться не будет, поэтому дальнейшее рассмотрение продолжим на примере сабмодуля imitator.
Задача - добавить TB'и для модулей imitator'а, причем
- они должны храниться в СКВ и быть доступны всем разработчикам,
- имеются в виду TB'и на языке Verilog для симуляторов типа Vivado Simulator, ModelSim и т.д., а не тесты на языках Си или Matlab для Verilator'а.
Для конкретики, будем добавлять в дизайн imitator TB'и для двух модулей:
- imichnl_synthesizer, отвечающий за фазу несущей,
- imitator_channel, являющийся топ-модулем для одного канала имитатора и включающий в себя первый модуль.
Наша конечная цель - правильно написанный скрипт регенерации проекта, включающий раскладывание TB'ей по полочкам, и сами файлы TB'а. Будем считать, что пользователь по-максимуму хочет использовать GUI и по-минимуму консоль и TCL. Тогда вырисовывается следующий workflow:
- средствами GUI создать новый набор для моделирования (файлсет, включающий код TB'а, тестируемые модули и прочие файлы),
- через GUI настроить этот набор,
- через GUI выгрузить код регенерации,
- подправить наш скрипт регенерации, чтобы TB разворачивался и настраивался вместе с проектом.
Добавление TB через GUI
Создание нового набора для симуляции
Накидаем через GUI новый TB, а потом перенесем его в tcl-скрипт! Начнем с TB для модуля imichnl_synthesizer.
В Flow Navigator (это панель слева в Vivado) в разделе Simulation выбираем Simulation Settings
В открывшемся окне в разделе Simulation в графе Simulation top modulw name создаем новый файлсет, выбирая Create Simulation Set
ВНИМАНИЕ Не занимайте и не удаляйте файлсет sim_1. Vivado его очень любит и будет создавать заново, делая при этим активным. Лучше оставить sim_1 пустым.
Новому файлсету даем осмысленное название, например, sim_imichnl_synthesizer
Очищаем графу Simulation top module name, т.к. файл с кодом TB'а у нас ещё не создан.
На вкладке Advanced запрещаем включать в TB все файлы проекта, снимая галку с Include all design sources for simulation. Иначе он добавит все наши файлы в файлсет этого TB'а, что нам не нужно.
Закрываем окно, нажимая Ok. Vivado задает вопрос, сделать ли данный TB активным. Можно соглашаться. В итоге в Source проекта появился новый пустой файлсет для симуляции sim_imichnl_synthesizer
Добавляем файлы в набор для симуляции
В контекстном меню файлсета sim_imichnl_synthesizer, выпадающем при нажатии правой кнопкой мыши, выбираем добавление новых файлов Add Sources
Далее Add or create simulation sources
Добавляем новый файл TB'а, нажимая кнопку Create File в открывшемся окне. Даем файлу осмысленное имя с суффиксом _tb, например, imichnl_synthesizer_tb.v и обязательно указываем в качестве пути каталог tb дизайна imitator. Иначе он будет создан в дебрях песочницы (в prj_imitator) и не будет виден системе контроля версий.
С помощью кнопки Add Files добавляем уже существующие файлы, которые потребуются для работы тестируемого модуля. В данном случае это сам модуль imichnl_synthesizer из каталога verilog
После добавления требуемых файлов нажимаем кнопку Finish. Открывается окно Define module для нашего TB'а imichnl_synthesizer_tb.v. Порты нам добавлять не нужно, просто нажимаем Ok. Теперь у нас в файлсете sim_imichnl_synthesizer два файла - код исследуемого модуля и код TB'а.
Возвращаемся в настройки симуляции (Flow Navigator -> Simulation -> Simulation Settings) и указываем в качестве топового модуль imichnl_synthesizer_tb
Код TB'а
Пришло время наполнить imichnl_synthesizer_tb смысловым содержанием. Общий сброс, после чего каждую эпоху PHASE_RATE увеличивается на 2000000:
module imichnl_synthesizer_tb();
reg pclk; // [in]
reg reset_n;
reg [32-1:0] phase_rate;
reg doinit;
reg fix_pulse;
reg dly_epoch;
wire [32-1:0] phase_rate_int; // [out]
wire [32-1:0] phase_int;
wire [32-1:0] phase_cycles_int;
wire [5-1:0] phase_addr;
// Генератор фазы несущей,
// отображает phase_rate в phase_addr
// по задержанной эпохе dly_epoch защелкивает счетчики циклов и фазы, а также регистр phase_rate
// по задержанной эпохе dly_epoch применяет записанное значение phase_rate
//
imichnl_synthesizer CHSYN (
.clk (pclk), // [in]
.reset_n (reset_n),
.phase_rate (phase_rate),
.doinit (doinit),
.fix_pulse (fix_pulse),
.epoch_pulse (dly_epoch),
.phase_rate_int (phase_rate_int), // [out]
.phase_int (phase_int),
.phase_cycles_int (phase_cycles_int),
.phase_addr (phase_addr)
);
initial begin
pclk = 0;
reset_n = 1;
phase_rate = 0;
doinit = 0;
fix_pulse = 0;
dly_epoch = 0;
end
always
#5 pclk = !pclk;
event reset;
event epoch;
event fix;
initial begin
forever begin
@ (reset)
@ (negedge pclk)
reset_n = 0;
@ (negedge pclk)
reset_n = 1;
end
end
initial begin
forever begin
@ (epoch)
@ (negedge pclk)
dly_epoch = 1;
@ (negedge pclk)
dly_epoch = 0;
end
end
initial begin
forever begin
@ (fix)
@ (negedge pclk)
fix_pulse = 1;
@ (negedge pclk)
fix_pulse = 0;
end
end
initial begin: TEST_CASE
#10 -> reset;
fork // Распараллеливание блоков
forever begin
#101 -> epoch;
phase_rate = phase_rate + 20000000;
end
#30
forever begin
#100 -> fix;
end
join
end
endmodule
Моделирование
После того как TB написан, запускаем симуляцию через контекстное меню файлсета:
Моделируем, настраиваем wave-форму
Cохраняем настройки wave-формы в каталог tb через меню File->Save Waveform Configuration, автоматом получая имя файла типа imichnl_synthesizer_tb_behav.wcfg
Добавление TB'а через скрипт регенерации проекта
Сейчас все настройки TB'а, т.е. файлсета sim_imichnl_synthesizer, хранятся в песочнице, которая у нас не находится под системой контроля версий. Нужно добавить соответствующий код в скрипт регенерации проекта (традиционно его место до объявления настроек синтеза synth_1)
# 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 ====================
Как я получил этот код? Я просто выгрузил через File -> Write Project Tcl новый скрипт регенерации проекта и вычленил из него блок, отвечающий за наш новый файлсет.
Ниже мы подробнее рассмотрим команды, используемые для регенерации TB'а.
Добавляем второй TB
Проделываем аналогичные действия для второго TB'а, получаем набор для моделирования sim_imitator_channel, включающий, помимо прочего, imitator_channel_tb.v.
`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 // Распаралеливание блоков
forever begin
#6000000 -> irq;
end
forever begin
#10000000 -> fix;
end
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
endmodule
У другого пользователя
Теперь дизайн, включая TB'и, будет храниться в системе контроля версий. При запуске скрипта регенерации будет разворачиваться у нового пользователя и будет готовым для моделирования.