После небольшого (нет) перерыва в изучении 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 <stdio.h> #include <unistd.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #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 |
0x1 |
0 |
1 |
0 |
0x2 |
0 |
1 |
1 |
0x3 |
1 |
0 |
0 |
0x4 |
1 |
0 |
1 |
0x5 |
1 |
1 |
0 |
0x6 |
За раз скомпилируем и отправим на плату столько же вариантов программ сколько экспериментов нужно будет провести.
Проверка:
Значение content |
Результат |
0x1 |
Провал. Счётчик №1 не инкрементируется. При старте программы предыдущие значения счетчика не сбрасываются. |
0x2 |
Успешно. Счётчик №2 инкрементируется независимо. При старте программы предыдущие значения счетчика сбрасываются. |
0x3 |
Успешно. Счётчик №1 и №2 инкрементируются независимо. При старте программы предыдущие значения счетчика сбрасываются. |
0x4 |
Успешно. Счётчик №3 инкрементируется независимо. При старте программы предыдущие значения счетчика сбрасываются. |
0x5 |
Успешно. Счётчик №1 и №3 инкрементируются независимо. При старте программы предыдущие значения счетчика сбрасываются. |
0x6 |
Успешно. Счётчик №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 |
0x1 |
0 |
1 |
0 |
0x2 |
0 |
1 |
1 |
0x3 |
1 |
0 |
0 |
0x4 |
1 |
0 |
1 |
0x5 |
1 |
1 |
0 |
0x6 |
1 |
1 |
1 |
0x7 |
За раз скомпилируем и отправим на плату столько же вариантов программ сколько экспериментов нужно будет провести.
Проверка:
Значение content |
Результат |
0x1 |
Успешно. После нескольких запусков генератора импульсов — значение счётчика №1 осталось тем же. |
0x2 |
Успешно. После нескольких запусков генератора импульсов — значение счётчика №2 осталось тем же. |
0x3 |
Успешно. После нескольких запусков генератора импульсов — значение счётчика №1 и №2 осталось тем же. |
0x4 |
Успешно. После нескольких запусков генератора импульсов — значение счётчика №3 осталось тем же. |
0x5 |
Успешно. После нескольких запусков генератора импульсов — значение счётчика №1 и №3 осталось тем же. |
0x6 |
Успешно. После нескольких запусков генератора импульсов — значение счётчика №2 и №3 осталось тем же. |
0x7 |
Успешно. После нескольких запусков генератора импульсов — значение счётчика №1, №2 и №3 осталось тем же. |
Результат:
Все тесты в первом приближении работают так как положено. Но замечено то, что не всегда на старте сбрасываются значения в ноль у тех счётчиков, которые должны были быть сброшенными.
Заключение
Что ж, в ходе проверки выявлено несколько недостатков и не совсем корректного поведения при подаче соответствующих управляющих сигналов. Необходимо доработать и отдебажить эти моменты прежде чем пускать такую железку в “прод” 😀
Но цель этой статьи — протестировать, что я собственно и описал. Доработки останутся за кадром. Наверняка найдется ещё не один десяток различных кейсов, которые можно было бы проверить и посмотреть как ведет себя данная железка при разных воздействиях. Пишите в комментарии ваши варианты — если найдутся такие, что меня заинтересуют, можно будет сделать статью part.2 по тестированию.
Спасибо за внимание!
P.S. Для тех, кто жаждет нового материала от меня — на данный момент есть в планах приделать WI-Fi модуль с чипом Realtek RTL8822CS через шину SDIO к данной плате. В будущем не хотелось бы привязываться к сетевому кабелю и и можно было бы работать по воздуху. Следите за моими публикациями.
ссылка на оригинал статьи https://habr.com/ru/articles/593993/
Добавить комментарий