Zynq 7000. Тестирование счётчика импульсов

от автора

После небольшого (нет) перерыва в изучении Zynq и очередного прочтения своей предыдущей статьи, я отметил для себя очень важный момент — практически не отражено никаких результатов тестирования полученного поделия, кроме базовой проверки работоспособности. Во время подготовки материала для предыдущей статьи — я конечно же все проверял перед публикацией, что всё работает как надо (на первый взгляд), но из-за получившегося объема статьи вся информация о процессе проверки осталась за кадром. Именно поэтому я решил сделать отдельную статью, в которой я расскажу о том, как я проверил, то, что результат, достигнутый в предыдущей статье, соответствует (полностью или частично) заявленным требованиям. Много интересного выяснилось по ходу тестирования, я вам скажу…

Всем интересующимся — добро пожаловать под кат! 

Набор требований

Перед началом любого тестирования, в самую первую очередь, ВСЕГДА формируется набор требований к тестируемой системе. С подробного описание этих самых требований я и начну свой рассказ. 

Итак, напомню, что за задача стояла перед нами: 

  1. Нужно организовать внутри ПЛИС набор счетчиков, которые будут считать импульсы с частотой до 2МГц;

  2. Счетчики должны быть включаемыми\выключаемыми;

  3. Счетчики должны быть сбрасываемыми;

  4. Для включения, выключения и сброса счетчиков должны быть отдельные регистры управления;

  5. Счетчики должны работать независимо друг от друга и синхронно;

  6. Управление и считывание данных из счетчиков должно быть доступно из PS или Linux;

  7. Счетчики должны считать точное количество импульсов (допустимое отклонение 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

Проверка: 

  1. Запускаем программу ./counter_mgmt

  2. Будет пройдена инициализация управления счётчиками внутри ПЛИС и счётчики будут сброшены.

  3. Запускаем программу и с помощью кнопки подаем единичное нажатие.

  4. Каждый из трех счётчиков независимо прибавляет свое значение с 0 на 1. 

  5. Для уверенности можно повторить несколько раз, перезапуская программу.

Результат: работает! =)

Тестовый кейс №2

Суть: счетчики не должны воспринимать импульсы частотой выше чем ~ 2МГц.

Программа: будет использоваться из предыдущего кейса

Проверка: 

В этом кейсе нам понадобится генератор сигналов, о котором я говорил в начале статьи. Мы подключи к первому счетчику генератор импульсов и выставим частоту генерации меандра на 1.9МГц и постепенно будем повышать частоту.  

Перед подключением конечно же надо проверить сигнал по амплитуде и частоте, чтобы не подать что-нибудь не то. Выставляем 3В амплитуду и частоту 1.9МГц.

Запускаем программу и смотрим, что наши счётчики показывают нули. После подключим генератор и увидим, что счётчик нарастает с бешеной скоростью 🙂

Повышаем частоту до тех пор, пока счет не прекратится. В моем случае, даже на максимальной частоте 2.5МГц счёт не прекратился. Значит нужно повысить разрядность в модуле debouncer.v

Результат: ограничение частоты в МГц не работает из-за слишком малой разрядности модуля антизвона. Повышение разрядности снизит верхний порог проходной частоты. Есть еще что доработать. 

Тестовый кейс №3 и №8

Суть: Счетчики должны вести независимый счет. С помощью подготовленной платы с FPGA подадим три параллельных потока установленного числа импульсов и заодно проверим точность счета по кейсу номер 9. 

Программа: 

  1. Составим программу для платы A-C4E6E10 с чипом Altera EP4CE6E22C8N. Которая будет генерировать указанное количество каждой ножкой в параллельном режиме.

  2. Для просмотра значений на плате с 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

Суть: Счетчики не должен реагировать на импульсы когда выключен. Проверить 

Программа: 

  1. Для FPGA воспользуемся программой которая будет генерировать указанное количество каждой ножкой в параллельном режиме.

  2. Для просмотра значений на плате с Zynq воспользуемся сделанной программой в предыдущих шагах с небольшим изменением — нужно подать на Enable счётчиков значение в 0 (переменная content). А именно внести эти изменения в программу и перекомпилировать: 

/* Записываем включение всех счётчиков: 0b000 в регистр EN */ addr = (unsigned long)(map_base + 1); content = 0x0;

Проверка:

Заливаем вновь откомпилированную программу и запускаем ее, следом за этим запускаем генератор импульсов с FPGA-платы. Наблюдаем, что значения счетчиков не изменилось. 

Результат: 

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

Тестовый кейс №5

Суть: Управление счетчиками должно быть независимым

Программа:

  1. Для FPGA воспользуемся программой которая будет генерировать указанное количество каждой ножкой в параллельном режиме.

  2. Для просмотра значений на плате с 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

Суть: Счетчик должен быть сбрасываемым и независимо друг от друга

Программа: 

  1. Для FPGA воспользуемся программой которая будет генерировать указанное количество каждой ножкой в параллельном режиме.

  2. Для просмотра значений на плате с 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/