Zynq 7000. Тестирование счётчика импульсов
После небольшого (нет) перерыва в изучении Zynq и очередного прочтения своей предыдущей статьи, я отметил для себя очень важный момент — практически не отражено никаких результатов тестирования полученного поделия, кроме базовой проверки работоспособности. Во время подготовки материала для предыдущей статьи — я конечно же все проверял перед публикацией, что всё работает как надо (на первый взгляд), но из-за получившегося объема статьи вся информация о процессе проверки осталась за кадром. Именно поэтому я решил сделать отдельную статью, в которой я расскажу о том, как я проверил, то, что результат, достигнутый в предыдущей статье, соответствует (полностью или частично) заявленным требованиям. Много интересного выяснилось по ходу тестирования, я вам скажу…
Всем интересующимся — добро пожаловать под кат!
Набор требований
Перед началом любого тестирования, в самую первую очередь, ВСЕГДА формируется набор требований к тестируемой системе. С подробного описание этих самых требований я и начну свой рассказ.
Итак, напомню, что за задача стояла перед нами:
Нужно организовать внутри ПЛИС набор счетчиков, которые будут считать импульсы с частотой до 2МГц;
Счетчики должны быть включаемыми\выключаемыми;
Счетчики должны быть сбрасываемыми;
Для включения, выключения и сброса счетчиков должны быть отдельные регистры управления;
Счетчики должны работать независимо друг от друга и синхронно;
Управление и считывание данных из счетчиков должно быть доступно из PS или Linux;
Счетчики должны считать точное количество импульсов (допустимое отклонение 1–2 импульса).
Исходя из постановки задачи можно составить целый набор тестов для проверки:
Требование | Как проверяется наличие реализованной функции? | Ожидаемый результат | |
1. | Счетчик должен увеличиваться на 1 при наступлении единичного импульса; | Подаем один импульс на каждый из счетчиков с кнопки или с FPGA-отладки | Значение счетчика увеличилось на единицу; |
2. | Счетчик должен считать импульсы частотой до 2 МГц | С помощью генератора сигналов подаем на входы счетчиков прямоугольные импульсы с частотой от 1.9М МГц до 2.1МГц | Счетчик нарастает при частоте импульсов < 2 МГц и перестает считать при превышении частоты в 2 МГц. |
3. | Счетчики должны вести независимый счет | С помощью подготовленной платы с FPGA подается три параллельных потока установленного числа импульсов. | Каждый из счетчиков посчитает все импульсы и выдаст результат с максимальным отклонением в 1 импульс. |
4. | Счетчики не должны реагировать на импульсы когда выключены | Подается набор импульсов во время того, когда счетчики включены и когда выключены. | Во время того, когда счетчики выключены — выходное значение количества посчитанных импульсов не должны прирастать. И если включены — соответственно наоборот. |
5. | Управление счетчиками должно быть независимым | Проверяется наличие приращения счетчика когда другие счетчики включены\выключены. Например, если в системе три счетчика проверяем по схеме: 0 0 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 | В каждой из стадий проверки считать должны только те счетчики, которые включены. Значения выключенных счетчиков не должны изменяться. Значения включенных счетчиков должны соответствовать количеству поданных импульсов. |
6. | Счетчики должны быть сбрасываемым | С помощью генератора сигналов подаем на вход ограниченный поток импульсов, выключаем генератор и подаем команду сброс счётчиков. | После подачи команды сброса счетчики должны быть сброшены, т.е. их значения должны быть равны нулю. |
7. | Управление сбросом счетчика должно быть независимым | Проверяется корректность сброса счетчика когда другие счетчики не сбрасываются. Например, если в системе три счетчика проверяем по схеме: 0 0 0 0 0 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1 1 1 | После подачи команды сброса счетчик должен быть сброшен, т.е. его значение должно быть равным нулю. Другие счетчики не должны изменять своего значения |
8. | Точность счета импульсов должна соответствовать ±1 импульсу. | С помощью подготовленной платы с FPGA подается три параллельных потока установленного числа импульсов. | Полученное значение импульсов должно быть без отклонений или равно величине ± от 1 от того, что было сгенерировано. |
Подготовка
После формирования данного набора тестовых кейсов нам необходимо подготовить тестовое оборудование для стенда и определиться с тем, в каких условиях будет работать наша отладочная плата
В первую очередь позаботимся о тестовых приборах. Для всех тестовых кейсов нам будет достаточно иметь простой генератор сигналов UNI-T UTG9002C, в моем личном арсенале есть такой. Вторым прибором для тестов будет отладочная плата с FPGA на базе Altera Cyclone IV EP4CE6E22C8N.
Тестирование будем проводить из загруженной операционной системы Linux. На ней мы будем запускать программы, которые будет отправлять команды в PL и забирать текущие значения из счётчиков.
Что ж, теперь можно перейти непосредственно к тестированию.
Тестовый кейс №1
Суть: проверить, что если подать один импульс, то будет прибавлена единица в счётчике.
Самый простой вариант, который кажется наиболее разумным — это использовать простую кнопку с антидребезгом и подключить ее к входным ножкам. После этого включить программу которая будет показывать значения счётчиков.
Программа:
#include
#include
#include
#include
#include
#include
#include
#define BRAM_CTRL_0 0x40000000
#define DATA_LEN 6
int fd;
unsigned int *map_base;
int sigintHandler(int sig_num)
{
printf("\n Terminating using Ctrl+C \n");
fflush(stdout);
close(fd);
munmap(map_base, DATA_LEN);
return 0;
}
int main(int argc, char *argv)
{
signal(SIGINT, sigintHandler);
fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0)
{
printf("can not open /dev/mem \n");
return (-1);
}
printf("/dev/mem is open \n");
map_base = mmap(NULL, DATA_LEN * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, BRAM_CTRL_0);
if (map_base == 0)
{
printf("NULL pointer\n");
}
else
{
printf("mmap successful\n");
}
unsigned long addr;
unsigned int content;
int i = 0;
/* Записываем включение всех счётчиков: 0b111 в регистр EN */
addr = (unsigned long)(map_base + 1);
content = 0x7;
map_base[1] = content;
printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content);
/* Отправляем команду на запись в ENA */
addr = (unsigned long)(map_base + 0);
content = 0x1;
map_base[0] = content;
printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content);
/* Записываем отключение сброса всех счётчиков: 0b111 в регистр RST */
addr = (unsigned long)(map_base + 2);
content = 0x0;
map_base[2] = content;
printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content);
/* Отправляем команду на запись в RST */
addr = (unsigned long)(map_base + 0);
content = 0x2;
map_base[0] = content;
printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content);
/* Записываем отключение сброса всех счётчиков: 0b111 в регистр RST */
addr = (unsigned long)(map_base + 2);
content = 0x7;
map_base[2] = content;
printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content);
/* Отправляем команду на запись в RST */
addr = (unsigned long)(map_base + 0);
content = 0x2;
map_base[0] = content;
printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content);
while(1)
{
addr = (unsigned long)(map_base + 0);
content = 0x3;
map_base[0] = content;
printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content);
usleep(115210);
system("clear");
printf("\nread data from bram\n");
for (i = 0; i < DATA_LEN; i++)
{
addr = (unsigned long)(map_base + i);
content = map_base[i];
printf("%2dth data, address: 0x%lx data_read: 0x%x\t\t\n", i, addr, content);
}
}
}
Компилировать программу будем через make, это можно достаточно просто — нужно сделать Makefile со следующим содержимым:
CC=arm-linux-gnueabihf-gcc
CFLAGS ?= -O2 -static
objects = counter_mgmt.o
CHECKFLAGS = -Wall -Wuninitialized -Wundef
override CFLAGS := $(CHECKFLAGS) $(CFLAGS)
progs = counter_mgmt
counter_mgmt: $(objects)
$(CC) $(CFLAGS) -o $@ $(objects)
clean:
rm -f $(progs) $(objects)
$(MAKE) -C clean
.PHONY: clean
После сохраним файлы программы и Makefile в одну папку и скомпилируем исполняемый файл.
# make clean
# make
После можно программу спокойно скинуть по SSH сразу на устройство. Узнаем IP-адрес нашей отладки, предварительно подключив Ethernet-кабель из роутера в плату. И с использованием команды scp передаем файл на заранее смонтированную флешку, чтобы после возможной перезагрузки ничего не потерялось:
# mount /dev/mmcblk0p1 /media
# cd /media
# # ifconfig
eth0 Link encap:Ethernet HWaddr 9E:CF:62:72:A1:72
inet addr:192.168.1.62 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::b53b:e1d2:3dec:10cb/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1891 errors:0 dropped:0 overruns:0 frame:0
TX packets:1046 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2263311 (2.1 MiB) TX bytes:87430 (85.3 KiB)
Interrupt:33 Base address:0xb000
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
# scp megalloid@192.168.1.121:/home/megalloid//Zynq/Projects/10.LinuxMulticounte
r/Application/counter_mgmt .
megalloid@192.168.1.121's password:
counter_mgmt
100% 418KB 418.0KB/s 00:00
# ls -lsa
total 22792
4 drwxr-xr-x 2 root root 4096 Jan 1 00:00 .
0 drwxr-xr-x 17 root root 400 Jul 12 2021 ..
4972 -rwxr-xr-x 1 root root 5088700 Aug 15 2021 BOOT.bin
420 -rwxr-xr-x 1 root root 428072 Jan 1 1980 counter_mgmt
12 -rwxr-xr-x 1 root root 9730 Jan 1 1980 devicetree.dtb
8932 -rwxr-xr-x 1 root root 9143989 Aug 15 2021 sdcard
4376 -rwxr-xr-x 1 root root 4478280 Jul 13 2021 uImage
128 -rwxr-xr-x 1 root root 131072 Jan 1 1980 uboot.env
3948 -rwxr-xr-x 1 root root 4040441 Jul 12 2021 uramdisk.image.gz
Видим, что у нас рядом с загрузочными файлами появился файл counter_mgmt. На всякий случай дадим ему права на исполнение:
# chmod +x ./counter_mgmt
Проверка:
Запускаем программу ./counter_mgmt
Будет пройдена инициализация управления счётчиками внутри ПЛИС и счётчики будут сброшены.
Запускаем программу и с помощью кнопки подаем единичное нажатие.
Каждый из трех счётчиков независимо прибавляет свое значение с 0 на 1.
Для уверенности можно повторить несколько раз, перезапуская программу.
Результат: работает! =)
Тестовый кейс №2
Суть: счетчики не должны воспринимать импульсы частотой выше чем ~ 2МГц.
Программа: будет использоваться из предыдущего кейса
Проверка:
В этом кейсе нам понадобится генератор сигналов, о котором я говорил в начале статьи. Мы подключи к первому счетчику генератор импульсов и выставим частоту генерации меандра на 1.9МГц и постепенно будем повышать частоту.
Перед подключением конечно же надо проверить сигнал по амплитуде и частоте, чтобы не подать что-нибудь не то. Выставляем 3В амплитуду и частоту 1.9МГц.
Запускаем программу и смотрим, что наши счётчики показывают нули. После подключим генератор и увидим, что счётчик нарастает с бешеной скоростью :)
Повышаем частоту до тех пор, пока счет не прекратится. В моем случае, даже на максимальной частоте 2.5МГц счёт не прекратился. Значит нужно повысить разрядность в модуле debouncer.v
Результат: ограничение частоты в МГц не работает из-за слишком малой разрядности модуля антизвона. Повышение разрядности снизит верхний порог проходной частоты. Есть еще что доработать.
Тестовый кейс №3 и №8
Суть: Счетчики должны вести независимый счет. С помощью подготовленной платы с FPGA подадим три параллельных потока установленного числа импульсов и заодно проверим точность счета по кейсу номер 9.
Программа:
Составим программу для платы A-C4E6E10 с чипом Altera EP4CE6E22C8N. Которая будет генерировать указанное количество каждой ножкой в параллельном режиме.
Для просмотра значений на плате с Zynq воспользуемся сделанной программой в прошлом шаге.
Проверка:
В первую очередь нужно подготовить код, который позволит сгенерировать другой FPGA-отладкой нужное нам количество импульсов:
module gen_pulse(
(* chip_pin = "23" *) input clk_i,
(* chip_pin = "91" *) input rst_i,
(* chip_pin = "87" *) input cnt_bnt,
(* chip_pin = "30" *) output pulse1,
(* chip_pin = "28" *) output pulse2,
(* chip_pin = "31" *) output pulse3
);
wire div_clock;
reg [31:0] counter_1;
reg [31:0] counter_2;
reg [31:0] counter_3;
reg out_pulse1;
reg out_pulse2;
reg out_pulse3;
localparam IDLE = 4'd1;
localparam GEN = 4'd2;
localparam RST = 4'd3;
localparam CH1_COUNT = 10240;
localparam CH2_COUNT = 20480;
localparam CH3_COUNT = 40960;
reg [3:0] state = IDLE;
divider div_10(.clk_i(clk_i), .clk_out(div_clock));
always @(negedge rst_i or posedge clk_i)
begin
if(~rst_i)
begin
state = IDLE;
end
else
begin
if(cnt_bnt == 0)
begin
state = GEN;
end
if(cnt_bnt == 1)
begin
state = RST;
end
end
end
always @(posedge div_clock)
begin
case(state)
IDLE: begin
counter_1 = 0;
counter_2 = 0;
counter_3 = 0;
end
GEN: begin
if(counter_1 >= (CH1_COUNT * 2) - 1)
begin
out_pulse1 <= 0;
end
else
begin
counter_1 <= counter_1 + 1;
out_pulse1 <= ~out_pulse1;
end
if(counter_2 >= (CH2_COUNT * 2) - 1)
begin
out_pulse2 <= 0;
end
else
begin
counter_2 <= counter_2 + 1;
out_pulse2 <= ~out_pulse2;
end
if(counter_3 >= (CH3_COUNT * 2) - 1)
begin
out_pulse3 <= 0;
end
else
begin
counter_3 <= counter_3 + 1;
out_pulse3 <= ~out_pulse3;
end
end
RST: begin
counter_1 = 0;
counter_2 = 0;
counter_3 = 0;
end
endcase
end
assign pulse1 = out_pulse1;
assign pulse2 = out_pulse2;
assign pulse3 = out_pulse3;
endmodule
module divider(
input clk_i,
output clk_out
);
localparam WIDTH = 64;
localparam N = 10;
reg [WIDTH - 1:0] r_reg;
wire [WIDTH - 1:0] r_nxt;
reg clk_track;
always @(posedge clk_i)
begin
if (r_nxt == N)
begin
r_reg <= 0;
clk_track <= ~clk_track;
end
else
r_reg <= r_nxt;
end
assign r_nxt = r_reg+1;
assign clk_out = clk_track;
endmodule
Суть данного кода такова, что нажимая кнопку K4, которая подключена к пину P87 дополнительной FPGA-отладки будет запущена генерация 10240, 20480 и 40960 импульсов с каждой ножки соответственно. Необходимо удерживать кнопку до окончания счёта и не стоит забывать, что тут у нас отсутствует модуль антизвона и возможны артефакты при счете =)
Запускаем программу и нажимаем аппаратную кнопку для генерации указанного количества символов и видим…
Результат:
До нажатия:
После нажатия:
Если переведем указанные нами значения из шестнадцатеричного значения в десятичный — получим ровно то, что и требовалось. Успех!
Тестовый кейс №4
Суть: Счетчики не должен реагировать на импульсы когда выключен. Проверить
Программа:
Для FPGA воспользуемся программой которая будет генерировать указанное количество каждой ножкой в параллельном режиме.
Для просмотра значений на плате с Zynq воспользуемся сделанной программой в предыдущих шагах с небольшим изменением — нужно подать на Enable счётчиков значение в 0 (переменная content). А именно внести эти изменения в программу и перекомпилировать:
/* Записываем включение всех счётчиков: 0b000 в регистр EN */
addr = (unsigned long)(map_base + 1);
content = 0x0;
Проверка:
Заливаем вновь откомпилированную программу и запускаем ее, следом за этим запускаем генератор импульсов с FPGA-платы. Наблюдаем, что значения счетчиков не изменилось.
Результат:
После запуска программы и подачи импульсов видно, что все счётчики находятся в состоянии disabled и счёт не производится. Проверка пройдена.
Тестовый кейс №5
Суть: Управление счетчиками должно быть независимым
Программа:
Для FPGA воспользуемся программой которая будет генерировать указанное количество каждой ножкой в параллельном режиме.
Для просмотра значений на плате с Zynq воспользуемся сделанной программой в предыдущих шагах с небольшим изменением — нужно подать на Enable счётчиков значения из таблицы. А именно внести эти изменения в программу и перекомпилировать:
/* Записываем включение всех счётчиков: <значение> в регистр EN */
addr = (unsigned long)(map_base + 1);
content = <значение>;
Таблица значений, с которыми предстоит проверка:
Канал №3 | Канал №2 | Канал №1 | Значение в переменную content |
0 | 0 | 1 | 0×1 |
0 | 1 | 0 | 0×2 |
0 | 1 | 1 | 0×3 |
1 | 0 | 0 | 0×4 |
1 | 0 | 1 | 0×5 |
1 | 1 | 0 | 0×6 |
За раз скомпилируем и отправим на плату столько же вариантов программ сколько экспериментов нужно будет провести.
Проверка:
Значение content | Результат |
0×1 | Провал. Счётчик №1 не инкрементируется. При старте программы предыдущие значения счетчика не сбрасываются. |
0×2 | Успешно. Счётчик №2 инкрементируется независимо. При старте программы предыдущие значения счетчика сбрасываются. |
0×3 | Успешно. Счётчик №1 и №2 инкрементируются независимо. При старте программы предыдущие значения счетчика сбрасываются. |
0×4 | Успешно. Счётчик №3 инкрементируется независимо. При старте программы предыдущие значения счетчика сбрасываются. |
0×5 | Успешно. Счётчик №1 и №3 инкрементируются независимо. При старте программы предыдущие значения счетчика сбрасываются. |
0×6 | Успешно. Счётчик №2 и №3 инкрементируются независимо. При старте программы предыдущие значения счетчика сбрасываются. |
Результат:
По непонятным причинам не работает история когда включен только первый счетчик. Надо разобраться. Остальные сценарии отрабатывают корректно.
Тестовый кейс №6 и №7
Суть: Счетчик должен быть сбрасываемым и независимо друг от друга
Программа:
Для FPGA воспользуемся программой которая будет генерировать указанное количество каждой ножкой в параллельном режиме.
Для просмотра значений на плате с Zynq воспользуемся сделанной программой в предыдущих шагах с небольшим изменением — нужно подать на Reset счётчиков значения из таблицы. А именно внести эти изменения в программу и перекомпилировать:
/* Записываем включение всех счётчиков: 0b111 в регистр EN */
addr = (unsigned long)(map_base + 1);
content = 0x7;
/* Записываем отключение сброса всех счётчиков: <значение> в регистр RST */
addr = (unsigned long)(map_base + 2);
content = <значение>;
По итогу получится, что в зависимости от того, где будет установлена 1 — там сброс не будет производиться. Таблица значений, с которыми предстоит проверка:
Канал №3 | Канал №2 | Канал №1 | Значение в переменную content |
0 | 0 | 1 | 0×1 |
0 | 1 | 0 | 0×2 |
0 | 1 | 1 | 0×3 |
1 | 0 | 0 | 0×4 |
1 | 0 | 1 | 0×5 |
1 | 1 | 0 | 0×6 |
1 | 1 | 1 | 0×7 |
За раз скомпилируем и отправим на плату столько же вариантов программ сколько экспериментов нужно будет провести.
Проверка:
Значение content | Результат |
0×1 | Успешно. После нескольких запусков генератора импульсов — значение счётчика №1 осталось тем же. |
0×2 | Успешно. После нескольких запусков генератора импульсов — значение счётчика №2 осталось тем же. |
0×3 | Успешно. После нескольких запусков генератора импульсов — значение счётчика №1 и №2 осталось тем же. |
0×4 | Успешно. После нескольких запусков генератора импульсов — значение счётчика №3 осталось тем же. |
0×5 | Успешно. После нескольких запусков генератора импульсов — значение счётчика №1 и №3 осталось тем же. |
0×6 | Успешно. После нескольких запусков генератора импульсов — значение счётчика №2 и №3 осталось тем же. |
0×7 | Успешно. После нескольких запусков генератора импульсов — значение счётчика №1, №2 и №3 осталось тем же. |
Результат:
Все тесты в первом приближении работают так как положено. Но замечено то, что не всегда на старте сбрасываются значения в ноль у тех счётчиков, которые должны были быть сброшенными.
Заключение
Что ж, в ходе проверки выявлено несколько недостатков и не совсем корректного поведения при подаче соответствующих управляющих сигналов. Необходимо доработать и отдебажить эти моменты прежде чем пускать такую железку в «прод» : D
Но цель этой статьи — протестировать, что я собственно и описал. Доработки останутся за кадром. Наверняка найдется ещё не один десяток различных кейсов, которые можно было бы проверить и посмотреть как ведет себя данная железка при разных воздействиях. Пишите в комментарии ваши варианты — если найдутся такие, что меня заинтересуют, можно будет сделать статью part.2 по тестированию.
Спасибо за внимание!
P.S. Для тех, кто жаждет нового материала от меня — на данный момент есть в планах приделать WI-Fi модуль с чипом Realtek RTL8822CS через шину SDIO к данной плате. В будущем не хотелось бы привязываться к сетевому кабелю и и можно было бы работать по воздуху. Следите за моими публикациями.