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

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

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

image-loader.svg

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

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

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

  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 
#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

Проверка:  

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

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

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

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

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

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

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

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

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

Проверка:  

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

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

image-loader.svg

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

image-loader.svg

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

image-loader.svg

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

Тестовый кейс №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 импульсов с каждой ножки соответственно. Необходимо удерживать кнопку до окончания счёта и не стоит забывать, что тут у нас отсутствует модуль антизвона и возможны артефакты при счете =)

Запускаем программу и нажимаем аппаратную кнопку для генерации указанного количества символов и видим…

Результат:  

До нажатия:

image-loader.svg

После нажатия:

image-loader.svg

Если переведем указанные нами значения из шестнадцатеричного значения в десятичный — получим ровно то, что и требовалось. Успех!

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

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

Программа:  

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

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

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

Проверка:

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

Результат:  

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

image-loader.svg

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

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

Программа:

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

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

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

Программа:  

  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

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 к данной плате. В будущем не хотелось бы привязываться к сетевому кабелю и и можно было бы работать по воздуху. Следите за моими публикациями.  

© Habrahabr.ru