Добавить общие блоки Zynq, Ethernet DSView и декодер ШИМ

This commit is contained in:
2026-11-09 00:15:26 +03:00
parent fa2ab3cc65
commit e144c3e86a
80 changed files with 10790 additions and 7 deletions
+12
View File
@@ -24,6 +24,18 @@ templates/
## Что лежит
### FPGA и DSView
| Блок | Назначение |
|---|---|
| [Блоки логического анализатора](hdl/logic-analyzer/README.md) | Аппаратный триггер, BRAM, AXI и кольцевой буфер DDR |
| [Осциллограф](hdl/scope-acquisition/README.md) | Синхронизация каналов ADC, триггер, буфер и выдача записи |
| [Ethernet в FPGA](hdl/logic-udp-ethernet/README.md) | RGMII, MDIO для RTL8211F, UDP-поток и буфер восьми входов |
| [HDMI-отрисовка](hdl/logic-hdmi-preview/README.md) | График восьми цифровых каналов из входов или потока |
| [Трёхфазный ШИМ](hdl/vfd-pwm/README.md) | DDS, рампа, комплементарные выходы и мёртвое время |
| [DSView Ethernet](tools/dsview/zynq-ethernet/README.md) | Нативное подключение TCP/UDP, запуск и остановка |
| [ZNET](c/zynq-net/README.md) | C99-кодек Ethernet-команд ввода-вывода |
### C
| Библиотека | Что делает | Зависимости | Что нужно от платформы |
+15
View File
@@ -0,0 +1,15 @@
# ZNET Ethernet-команды
`include/zynq_net.h` задаёт 64-байтный UDP wire-контракт версии 1.
`src/zynq_net.c` кодирует пакет, проверяет длину и допустимость
команд и применяет операции к состоянию ARM. Транспорт передаёт в
кодек выделенные вызывающим буферы.
`ports/rtl/axi_simple_io.sv` — регистровый AXI4-Lite блок для
физических входов и выходов. Транспортная служба сопоставляет команды
регистрам, таблицу пинов определяет проект платы.
```sh
cc -std=c99 -Wall -Wextra -Iinclude src/zynq_net.c tests/test_zynq_net.c -o znet-check
./znet-check
```
+41
View File
@@ -0,0 +1,41 @@
#ifndef ZYNQ_NET_H /* Защищаем единый переносимый кодек Ethernet-обмена от повторного включения. */
#define ZYNQ_NET_H /* Один и тот же ABI используется ARM-службой и библиотекой настольного клиента. */
#include <stddef.h> /* Размеры пользовательских буферов передаются явно через size_t. */
#include <stdint.h> /* Фиксированные разрядности исключают различия ARMv7 и Windows x64. */
#if defined(_WIN32) /* Windows требует явного экспорта функций из отдельной библиотеки кодека. */
#define ZNET_API __declspec(dllexport) /* Экспортируем только функции, вызываемые тонким ctypes-портом. */
#else /* В ELF видимость по умолчанию подходит статической и динамической сборке. */
#define ZNET_API /* Кодек не зависит от конкретной ОС и сетевого стека. */
#endif /* Завершаем выбор атрибутов публичного ABI. */
#define ZNET_SIZE 64u /* Версия 1 передаёт ровно 64 байта UDP payload без зависимости от padding структур. */
#define ZNET_PORT 37570u /* Выделенный пользовательский UDP-порт проекта, одинаковый для поиска и обмена. */
#define ZNET_DISCOVER 1u /* Запрос сведений об устройстве допускает broadcast и не меняет состояние. */
#define ZNET_READ 2u /* Читаем два поля и восемь дискретов выбранной цели. */
#define ZNET_WRITE 3u /* Устанавливаем абсолютные значения: повтор пакета не переключает выходы заново. */
#define ZNET_WRITE_OUTPUTS 4u /* Записываем только DO, сохраняя счётчики A/B, которые независимо меняются кнопками платы. */
#define ZNET_REPLY 128u /* Старший бит операции отличает ответ от запроса, исключая отражение ответов. */
#define ZNET_ARM 0u /* Цель PS хранит учебные поля в памяти процесса Cortex-A9. */
#define ZNET_FPGA 1u /* Цель PL обслуживается только при явно подключённом UIO-модуле. */
#define ZNET_OK 0u /* Успешное выполнение позволяет применять полученные значения к интерфейсу. */
#define ZNET_UNAVAILABLE 1u /* Загруженный bitstream не предоставляет нужный модуль либо UIO не настроен. */
#define ZNET_BAD_REQUEST 2u /* Некорректные значения отклоняются до изменения ARM или AXI-регистров. */
#define ZNET_CAP_ARM 1u /* Бит capabilities подтверждает наличие учебного ARM-хранилища. */
#define ZNET_CAP_FPGA 2u /* Бит capabilities выставляется только после проверки аппаратных ID и ABI. */
#define ZNET_CAP_VIRTUAL_DI 4u /* Входы ARM являются программным эхом выходов, а не физическими контактами. */
#define ZNET_CAP_BOARD_IO 8u /* ARM управляет четырьмя штатными LED и читает четыре кнопки через драйверы Linux. */
#define ZNET_PL_ID 0x5a494f31u /* ASCII ZIO1 идентифицирует отдельный AXI-модуль простого обмена. */
#define ZNET_PL_VERSION 0x00010000u /* Старшая половина задаёт несовместимую версию аппаратной карты 1.0. */
typedef struct ZnetMessage { /* Host-представление никогда не отправляется в сеть прямым копированием. */
uint32_t operation, target, sequence, status; /* Операция, цель, номер запроса и код результата доступны через стабильный ABI. */
uint32_t field_a, field_b, inputs, outputs; /* Два беззнаковых 32-битных поля и младшие восемь бит входов/выходов. */
uint32_t capabilities, counter; /* Возможности и счётчик принятых корректных запросов облегчают диагностику. */
char name[24]; /* Имя устройства ограничено 23 ASCII-символами и завершающим нулём. */
} ZnetMessage; /* Размер структуры не определяет размер сетевого пакета: сериализация всегда явная. */
typedef struct ZnetArmState { /* Вызывающий владеет независимым состоянием каждого ARM-экземпляра. */
uint32_t field_a, field_b, outputs; /* Поля живут до перезапуска; Linux-порт при наличии BOARD_IO связывает младшие четыре DO со светодиодами. */
} ZnetArmState; /* Переносимое хранилище не использует heap и глобальные переменные. */
ZNET_API int znet_encode(uint8_t *wire, size_t capacity, const ZnetMessage *message); /* Проверяем поля и записываем ровно один пакет в буфер вызывающего. */
ZNET_API int znet_decode(ZnetMessage *message, const uint8_t *wire, size_t length); /* Отвергаем чужие magic/version/размеры до обращения к состоянию. */
ZNET_API uint32_t znet_arm_apply(ZnetArmState *state, const ZnetMessage *request, ZnetMessage *reply); /* Общая логика ARM READ/WRITE тестируется независимо от Linux-сокетов. */
ZNET_API void znet_arm_key(ZnetArmState *state, uint32_t *held, unsigned key, int value); /* Обрабатываем фронт кнопки 0:A+, 1:A−, 2:B+, 3:B−; evdev repeat=2 не меняет счётчик. */
#endif /* Завершаем публичный контракт версии 1. */
+74
View File
@@ -0,0 +1,74 @@
`timescale 1ns/1ps // Единица симуляции 1 нс и точность 1 пс не влияют на синтез регистров.
module axi_simple_io ( // Независимый AXI4-Lite slave добавляет учебный обмен, сохраняя ABI осциллографа.
(* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 clk CLK", X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF s_axi, ASSOCIATED_RESET aresetn" *) input wire clk, // Vivado связывает AXI-интерфейс с единственным processing clock.
(* X_INTERFACE_INFO = "xilinx.com:signal:reset:1.0 aresetn RST", X_INTERFACE_PARAMETER = "POLARITY ACTIVE_LOW" *) input wire aresetn, // Сброс снимается синхронно в clock domain AXI после platform reset controller.
input wire [11:0] s_axi_awaddr, input wire [2:0] s_axi_awprot, // Окно 4 КиБ выбирает GP interconnect; PROT не влияет на учебный банк.
input wire s_axi_awvalid, output wire s_axi_awready, // Адрес записи принимается независимо от данных.
input wire [31:0] s_axi_wdata, input wire [3:0] s_axi_wstrb, // Четыре WSTRB управляют отдельными байтами 32-битного регистра.
input wire s_axi_wvalid, output wire s_axi_wready, // Канал W допускает приход раньше либо позже AW.
output reg [1:0] s_axi_bresp, output reg s_axi_bvalid, input wire s_axi_bready, // Ответ записи удерживается до подтверждения master.
input wire [11:0] s_axi_araddr, input wire [2:0] s_axi_arprot, // Чтение адресуется байтовым смещением внутри того же окна.
input wire s_axi_arvalid, output wire s_axi_arready, // Один read response обслуживается независимо от незавершённой записи.
output reg [31:0] s_axi_rdata, output reg [1:0] s_axi_rresp, // Данные и код ответа фиксируются на AR handshake.
output reg s_axi_rvalid, input wire s_axi_rready, // Backpressure не меняет уже выданный ответ.
input wire [7:0] digital_inputs, output reg [7:0] digital_outputs, // Входы предназначены для медленных независимых дискретов, а не когерентной многобитной шины.
output reg [31:0] field_a, output reg [31:0] field_b // Два поля становятся видимыми прикладной PL-логике одновременно с COMMIT.
); // Физические пины DIO назначаются только после проверки электрического интерфейса конкретной платы.
localparam [1:0] OKAY = 2'b00, SLVERR = 2'b10; // Стандарт AXI кодирует успешный ответ 00, ошибку slave 10.
reg aw_pending, w_pending; // Одноэлементные буферы связывают независимые AW и W в одну транзакцию.
reg [11:0] write_address; reg [31:0] write_data; reg [3:0] write_strobes; // Сохраняем все атрибуты до выполнения записи.
reg [31:0] shadow_a, shadow_b, shadow_outputs; // Shadow-банк позволяет передать несколько параметров до атомарного применения.
(* ASYNC_REG = "TRUE" *) reg [7:0] input_meta, input_sync; // Две ступени снижают риск метастабильности каждого независимого асинхронного входа.
assign s_axi_awready = aresetn && !aw_pending && !s_axi_bvalid; // Новый адрес не перезаписывает ни ожидающую пару, ни удерживаемый ответ.
assign s_axi_wready = aresetn && !w_pending && !s_axi_bvalid; // Новый payload принимается только в свободный буфер W.
assign s_axi_arready = aresetn && !s_axi_rvalid; // До принятия R текущая выборка остаётся неизменной.
function automatic [31:0] merge_bytes(input [31:0] old_value, input [31:0] new_value, input [3:0] strobes); // Объединяем только явно разрешённые byte lanes.
integer i; begin // Счётчик разворачивается синтезатором в четыре независимых мультиплексора.
merge_bytes = old_value; // Невыбранные байты сохраняют прежнее значение.
for (i = 0; i < 4; i = i + 1) if (strobes[i]) merge_bytes[i*8 +: 8] = new_value[i*8 +: 8]; // Номер WSTRB совпадает с номером байта на AXI32.
end // Завершаем вычисление маскированной записи.
endfunction // Нулевая маска strobes даёт no-op.
always @(posedge clk) begin // Вся управляющая логика, включая synchronizer, работает в одной AXI-области.
if (!aresetn) begin // Сброс отменяет незавершённые транзакции и переводит все выходы в ноль.
aw_pending <= 0; w_pending <= 0; write_address <= 0; write_data <= 0; write_strobes <= 0; // Старые половины записи не переживают reset.
s_axi_bvalid <= 0; s_axi_bresp <= OKAY; s_axi_rvalid <= 0; s_axi_rresp <= OKAY; s_axi_rdata <= 0; // Ответы AXI начинают работу в неактивном состоянии.
shadow_a <= 0; shadow_b <= 0; shadow_outputs <= 0; field_a <= 0; field_b <= 0; digital_outputs <= 0; // Нулевые настройки исключают непреднамеренное включение учебных DO после старта.
input_meta <= 0; input_sync <= 0; // Первые две выборки после reset заполняют synchronizer заново.
end else begin // Нормальная работа не зависит от состояния Ethernet-службы.
input_meta <= digital_inputs; input_sync <= input_meta; // Уровень каждого DI достигает read-bank через два фронта clk.
if (s_axi_bvalid && s_axi_bready) s_axi_bvalid <= 0; // Отпускаем ответ записи только после handshake B.
if (s_axi_rvalid && s_axi_rready) s_axi_rvalid <= 0; // Отпускаем read response только после handshake R.
if (s_axi_awvalid && s_axi_awready) begin write_address <= s_axi_awaddr; aw_pending <= 1; end // Сохраняем AW даже при отсутствующем W.
if (s_axi_wvalid && s_axi_wready) begin write_data <= s_axi_wdata; write_strobes <= s_axi_wstrb; w_pending <= 1; end // Аналогично W не зависит от готовности адреса.
if (aw_pending && w_pending && !s_axi_bvalid) begin // Пара выполняется один раз; B backpressure не дублирует COMMIT.
aw_pending <= 0; w_pending <= 0; s_axi_bvalid <= 1; s_axi_bresp <= SLVERR; // По умолчанию неизвестный либо read-only адрес даёт ошибку.
case (write_address) // Полное сравнение также отклоняет невыровненные адреса и alias верхних битов.
12'h008: begin shadow_a <= merge_bytes(shadow_a, write_data, write_strobes); s_axi_bresp <= OKAY; end // Смещение 0x08 хранит первое shadow-поле uint32.
12'h00c: begin shadow_b <= merge_bytes(shadow_b, write_data, write_strobes); s_axi_bresp <= OKAY; end // Смещение 0x0C хранит второе shadow-поле uint32.
12'h010: begin shadow_outputs <= merge_bytes(shadow_outputs, write_data, write_strobes) & 32'hff; s_axi_bresp <= OKAY; end // Только биты 7..0 соответствуют восьми выходам.
12'h018: begin // Регистр команды применяет весь набор настроек за один фронт.
if (!write_strobes[0] || !(write_data & 32'hff)) s_axi_bresp <= OKAY; // Незаписанный младший байт либо нулевая команда являются no-op.
else if ((write_data & 32'hff) == 1) begin field_a <= shadow_a; field_b <= shadow_b; digital_outputs <= shadow_outputs[7:0]; s_axi_bresp <= OKAY; end // Команда 1 одновременно обновляет поля и дискреты.
end // Другие команды отклоняются без изменения активного набора.
default: s_axi_bresp <= SLVERR; // ID, версия, DI, активные копии и reserved-адреса не записываются.
endcase // Каждая запись завершается ровно одним B-ответом.
end // Буферы AW/W освободятся для следующей транзакции после B handshake.
if (s_axi_arvalid && s_axi_arready) begin // Snapshot одного read word удерживается независимо от дальнейших изменений DI.
s_axi_rvalid <= 1; s_axi_rresp <= OKAY; // Подтверждение AR создаёт единственный ожидающий R.
case (s_axi_araddr) // Карта совместима с UIO-портом server.c.
12'h000: s_axi_rdata <= 32'h5a494f31; // ASCII ZIO1 отличает этот модуль от scope_core и заводского HDMI.
12'h004: s_axi_rdata <= 32'h00010000; // Версия ABI 1.0 проверяется ARM до любых аппаратных записей.
12'h008: s_axi_rdata <= shadow_a; // Позволяем проверить подготовленное первое поле до COMMIT.
12'h00c: s_axi_rdata <= shadow_b; // Второе shadow-поле доступно тем же способом.
12'h010: s_axi_rdata <= shadow_outputs; // Возвращаем подготовленную восьмибитную маску DO.
12'h014: s_axi_rdata <= {24'd0, input_sync}; // Уровни DI являются индивидуально синхронизированными медленными сигналами.
12'h018: s_axi_rdata <= 0; // Команда не хранится и не повторяется при чтении.
12'h01c: s_axi_rdata <= field_a; // Активная копия первого поля подтверждает результат COMMIT.
12'h020: s_axi_rdata <= field_b; // Активная копия второго поля читается отдельно от подготовки следующей записи.
12'h024: s_axi_rdata <= {24'd0, digital_outputs}; // Активные DO доступны для подтверждения применения всей группы.
default: begin s_axi_rdata <= 0; s_axi_rresp <= SLVERR; end // Reserved и невыровненные чтения явно сообщают об ошибке адреса.
endcase // Данные и код ответа больше не меняются до RREADY.
end // Независимое чтение допускается и при задержанной записи.
end // Завершаем цикл работы при снятом reset.
end // Одна clocked-процедура владеет каждым регистром блока.
endmodule // Доступ к PS Ethernet идёт через ARM-службу, отдельный PL Ethernet MAC не требуется.
+49
View File
@@ -0,0 +1,49 @@
#include "zynq_net.h" /* Общий заголовок задаёт единственный wire-контракт ARM и клиента. */
#include <string.h> /* memcpy и memset работают с фиксированными буферами без динамического выделения. */
static uint32_t get32(const uint8_t *p) { /* Декодируем сетевой big-endian независимо от endian процессора. */
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]; /* Сдвиги соответствуют весу каждого из четырёх октетов. */
} /* Доступ по байтам не требует выравнивания UDP-буфера. */
static void put32(uint8_t *p, uint32_t v) { /* Записываем сетевое представление без type-punning и unaligned access. */
p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16); p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)v; /* Первый октет содержит старшие восемь бит значения. */
} /* Все 32-битные поля проходят один и тот же путь сериализации. */
static int valid(const ZnetMessage *m) { /* Проверяем структурные ограничения до записи либо принятия пакета. */
uint32_t op = m->operation & ~ZNET_REPLY; /* Ответ сохраняет исходную операцию в младших семи битах. */
return m->operation <= 255u && op >= ZNET_DISCOVER && op <= ZNET_WRITE_OUTPUTS && m->target <= ZNET_FPGA && m->inputs <= 255u && m->outputs <= 255u && memchr(m->name, 0, sizeof(m->name)) != NULL; /* Дополнительная операция DO сохраняет 64-байтный ABI, дискреты ограничены восемью битами. */
} /* Бизнес-ошибки запроса дополнительно проверяются обработчиком цели. */
int znet_encode(uint8_t *wire, size_t capacity, const ZnetMessage *m) { /* Преобразуем модель ABI в фиксированный UDP payload. */
if (!wire || !m || capacity < ZNET_SIZE || !valid(m)) return -1; /* Ошибка не оставляет частично записанного кадра. */
memset(wire, 0, ZNET_SIZE); memcpy(wire, "ZNET", 4); wire[5] = 1; wire[6] = (uint8_t)m->operation; wire[7] = (uint8_t)m->target; /* Magic и 16-битная версия 1 занимают первые шесть байт. */
put32(wire + 8, m->sequence); put32(wire + 12, m->status); /* Номер запроса сопоставляет асинхронный ответ, status передаёт результат. */
put32(wire + 16, m->field_a); put32(wire + 20, m->field_b); /* Поля являются uint32 без масштаба и физических единиц. */
put32(wire + 24, m->inputs); put32(wire + 28, m->outputs); /* Маски дискретов передаются 32-битно для стабильного выравнивания ABI. */
put32(wire + 32, m->capabilities); put32(wire + 36, m->counter); memcpy(wire + 40, m->name, sizeof(m->name)); /* Последние 24 байта отведены идентификатору устройства. */
return (int)ZNET_SIZE; /* Возвращаем точное число байт для sendto. */
} /* Wire-формат не зависит от sizeof структуры. */
int znet_decode(ZnetMessage *m, const uint8_t *wire, size_t length) { /* Разбираем только полный пакет поддерживаемой версии. */
ZnetMessage decoded; /* Временная модель сохраняет прежнее значение выхода при неуспешном разборе. */
if (!m || !wire || length != ZNET_SIZE || memcmp(wire, "ZNET", 4) || wire[4] || wire[5] != 1) return -1; /* Точный размер исключает усечённые пакеты и незаметное принятие будущего ABI. */
memset(&decoded, 0, sizeof(decoded)); decoded.operation = wire[6]; decoded.target = wire[7]; /* Обнуляем padding, затем заполняем стабильные поля модели. */
decoded.sequence = get32(wire + 8); decoded.status = get32(wire + 12); /* Значения читаются без требований к выравниванию. */
decoded.field_a = get32(wire + 16); decoded.field_b = get32(wire + 20); /* Числовые поля сохраняют все 32 бита. */
decoded.inputs = get32(wire + 24); decoded.outputs = get32(wire + 28); /* Некорректные старшие биты масок позже отклоняет valid. */
decoded.capabilities = get32(wire + 32); decoded.counter = get32(wire + 36); memcpy(decoded.name, wire + 40, sizeof(decoded.name)); /* Имя проверяется на наличие завершающего нуля. */
if (!valid(&decoded)) return -1; /* Не публикуем непроверенный пакет вызывающему коду. */
*m = decoded; return 0; /* Целая модель становится доступной только после успешной проверки. */
} /* Клиент и сервер используют один разборщик, исключая расхождение offset/endian. */
uint32_t znet_arm_apply(ZnetArmState *state, const ZnetMessage *request, ZnetMessage *reply) { /* Обрабатываем простое программное хранилище PS. */
if (!state || !request || !reply || request->target != ZNET_ARM || request->status || request->inputs || request->outputs > 255u || (request->operation != ZNET_READ && request->operation != ZNET_WRITE && request->operation != ZNET_DISCOVER && request->operation != ZNET_WRITE_OUTPUTS)) return ZNET_BAD_REQUEST; /* Принимаем только известные ARM-команды с нулевыми полями ответа и допустимой маской DO. */
if (request->operation == ZNET_WRITE_OUTPUTS && (request->field_a || request->field_b)) return ZNET_BAD_REQUEST; /* Ненулевые A/B в команде только DO считаются ошибкой клиента, а не потерянной записью счётчиков. */
if (request->operation == ZNET_WRITE) { state->field_a = request->field_a; state->field_b = request->field_b; state->outputs = request->outputs; } /* Абсолютная запись идемпотентна при потере ответа и повторной отправке. */
if (request->operation == ZNET_WRITE_OUTPUTS) state->outputs = request->outputs; /* Одно присваивание DO сохраняет последние значения счётчиков даже при нажатии кнопки перед запросом. */
reply->field_a = state->field_a; reply->field_b = state->field_b; reply->outputs = state->outputs; reply->inputs = state->outputs; /* DI ARM явно являются виртуальным echo DO для проверки первого обмена. */
return ZNET_OK; /* Метаданные устройства и сетевой ответ добавляет платформенный сервер. */
} /* Аппаратная PL-цель реализуется отдельным Linux UIO-портом. */
void znet_arm_key(ZnetArmState *state, uint32_t *held, unsigned key, int value) { /* Чистая логика кнопок проверяется одинаково на Windows и Cortex-A9. */
if (!state || !held || key >= 4u || (value != 0 && value != 1)) return; /* Игнорируем неизвестные кнопки, повреждённые события и аппаратный autorepeat со значением 2. */
uint32_t mask = 1u << key; /* Порядок четырёх младших DI совпадает с PS_KEY1, PS_KEY2, PL_KEY1 и PL_KEY2. */
if (!value) { *held &= ~mask; return; } /* Отпускание очищает DI и разрешает следующий отдельный шаг счётчика. */
if (*held & mask) return; /* Повторное событие нажатия при удержании не должно повторно менять A/B. */
*held |= mask; uint32_t *field = key < 2u ? &state->field_a : &state->field_b; /* Первая пара кнопок управляет A, вторая B; DI показывает физическое удержание. */
if (!(key & 1u)) { if (*field < UINT32_MAX) ++*field; } /* KEY1 увеличивает поле на единицу с насыщением на максимуме uint32, без переполнения. */
else if (*field) --*field; /* KEY2 уменьшает поле до нуля, не превращая нулевой счётчик в 4294967295. */
} /* Значения сохраняются в ARM-хранилище и публикуются следующим ответом READ или WRITE_OUTPUTS. */
File diff suppressed because one or more lines are too long
+36
View File
@@ -0,0 +1,36 @@
#include "zynq_net.h" /* Проверяем тот же C-кодек, который связывается с ARM-службой и ctypes. */
#include <assert.h> /* Проверки остаются активными благодаря -UNDEBUG в сборочном сценарии. */
#include <stdio.h> /* PASS marker позволяет отличить выполненные проверки от одной успешной компиляции. */
#include <string.h> /* Эталонные байты и состояние сравниваются без повторной реализации кодека. */
int main(void) { /* Проверки не требуют ОС, сокетов либо FPGA. */
uint8_t wire[ZNET_SIZE + 1], saved[ZNET_SIZE]; ZnetMessage input = {0}, output = {0}, reply = {0}; ZnetArmState state = {0}; /* Изолированные буферы позволяют проверить ошибки и неизменность состояния. */
input.operation = ZNET_WRITE; input.target = ZNET_ARM; input.sequence = 0x12345678u; input.field_a = 0xffffffffu; input.field_b = 0x01020304u; input.outputs = 0xa5; /* Граничное поле, несимметричные endian-байты и разноимённые биты DO. */
assert(znet_encode(wire, sizeof(wire), &input) == 64); /* ABI v1 обязан вернуть ровно 64 байта. */
const uint8_t prefix[] = {'Z','N','E','T',0,1,3,0,0x12,0x34,0x56,0x78,0,0,0,0,0xff,0xff,0xff,0xff,1,2,3,4,0,0,0,0,0,0,0,0xa5}; /* Независимый wire-вектор фиксирует magic, version, op, endian и offsets. */
assert(!memcmp(wire, prefix, sizeof(prefix))); memcpy(saved, wire, ZNET_SIZE); /* Roundtrip сам по себе недостаточен: проверяем конкретные эталонные байты. */
assert(!znet_decode(&output, wire, ZNET_SIZE)); assert(output.sequence == input.sequence && output.field_a == 0xffffffffu && output.field_b == 0x01020304u && output.outputs == 0xa5); /* uint32 сохраняет все биты на обеих архитектурах. */
assert(znet_arm_apply(&state, &output, &reply) == ZNET_OK && reply.inputs == 0xa5 && reply.outputs == 0xa5); /* Виртуальные DI документированно повторяют DO. */
assert(znet_arm_apply(&state, &output, &reply) == ZNET_OK && state.outputs == 0xa5); /* Повтор WRITE задаёт состояние, а не переключает его второй раз. */
output.operation = ZNET_READ; output.field_a = 0; output.field_b = 0; output.outputs = 0; assert(znet_arm_apply(&state, &output, &reply) == ZNET_OK && reply.field_a == 0xffffffffu && reply.field_b == 0x01020304u); /* READ не изменяет ранее записанные значения. */
ZnetArmState before = state; output.operation = ZNET_WRITE | ZNET_REPLY; assert(znet_arm_apply(&state, &output, &reply) == ZNET_BAD_REQUEST && !memcmp(&state, &before, sizeof(state))); /* Ответ, ошибочно пришедший как запрос, не меняет хранилище. */
output.operation = ZNET_WRITE; output.status = 1; assert(znet_arm_apply(&state, &output, &reply) == ZNET_BAD_REQUEST); output.status = 0; output.inputs = 1; assert(znet_arm_apply(&state, &output, &reply) == ZNET_BAD_REQUEST); /* Клиенту запрещено задавать status и DI. */
assert(znet_decode(&output, wire, 63) < 0 && znet_decode(&output, wire, 65) < 0); /* Ни усечённый, ни расширенный пакет не принимается как v1. */
wire[0] ^= 1; assert(znet_decode(&output, wire, 64) < 0); memcpy(wire, saved, 64); /* Чужое magic не интерпретируется как команда устройства. */
wire[5] = 2; assert(znet_decode(&output, wire, 64) < 0); memcpy(wire, saved, 64); /* Неизвестная версия отклоняется без предположения о совместимости. */
wire[6] = 5; assert(znet_decode(&output, wire, 64) < 0); memcpy(wire, saved, 64); /* После добавления WRITE_OUTPUTS=4 первой неизвестной операцией является 5. */
wire[7] = 2; assert(znet_decode(&output, wire, 64) < 0); memcpy(wire, saved, 64); /* Разрешены только ARM=0 и FPGA=1. */
wire[28] = 1; assert(znet_decode(&output, wire, 64) < 0); memcpy(wire, saved, 64); /* Старшие биты восьмиканальных DO не могут проникнуть в обработчик. */
memset(wire + 40, 'x', 24); assert(znet_decode(&output, wire, 64) < 0); /* Неограниченное строковое чтение исключается проверкой терминатора имени. */
assert(znet_encode(wire, 63, &input) < 0 && znet_decode(NULL, wire, 64) < 0 && znet_encode(NULL, 64, &input) < 0); /* Ошибки размера и указателей детерминированно возвращают отказ. */
state.field_a = 10; state.field_b = 20; uint32_t held = 0; znet_arm_key(&state, &held, 0, 1); assert(state.field_a == 11 && held == 1); /* PS_KEY1 увеличивает только A и устанавливает соответствующий физический DI. */
znet_arm_key(&state, &held, 0, 2); znet_arm_key(&state, &held, 0, 1); assert(state.field_a == 11); /* Autorepeat и дублированный фронт удержанной кнопки не создают дополнительных шагов. */
znet_arm_key(&state, &held, 0, 0); znet_arm_key(&state, &held, 0, 1); znet_arm_key(&state, &held, 1, 1); assert(state.field_a == 11 && held == 3); /* Отпускание разрешает новый шаг A+, а независимая PS_KEY2 выполняет A−. */
znet_arm_key(&state, &held, 2, 1); znet_arm_key(&state, &held, 3, 1); assert(state.field_b == 20 && held == 15); /* PL_KEY1/2 меняют B на +1/−1 и используют отдельные DI2/3. */
state.field_a = UINT32_MAX; held = 0; znet_arm_key(&state, &held, 0, 1); assert(state.field_a == UINT32_MAX); /* Инкремент насыщается на максимуме uint32 без переполнения в ноль. */
state.field_b = 0; znet_arm_key(&state, &held, 3, 1); assert(state.field_b == 0); /* Декремент нулевого счётчика не превращает его в максимальное значение. */
before = state; uint32_t saved_held = held; znet_arm_key(&state, &held, 4, 1); znet_arm_key(&state, &held, 0, -1); assert(!memcmp(&state, &before, sizeof(state)) && held == saved_held); /* Неизвестные индексы и повреждённые значения события не меняют состояние. */
input.operation = ZNET_WRITE_OUTPUTS; input.field_a = 0; input.field_b = 0; input.outputs = 5; assert(znet_encode(wire, sizeof(wire), &input) == 64 && !znet_decode(&output, wire, 64)); /* Новая операция проходит тот же 64-байтный ABI без изменения версии и смещений. */
assert(znet_arm_apply(&state, &output, &reply) == ZNET_OK && reply.field_a == UINT32_MAX && reply.field_b == 0 && reply.outputs == 5); /* Запись LED не затирает значения A/B, которые могли измениться независимо от черновика GUI. */
output.field_a = 1; before = state; assert(znet_arm_apply(&state, &output, &reply) == ZNET_BAD_REQUEST && !memcmp(&state, &before, sizeof(state))); /* Ненулевой зарезервированный A в команде DO отклоняется до изменения состояния. */
puts("PASS ZNET: golden bytes, uint32, malformed packets, idempotent write, button edges and saturation, output-only preservation"); return 0; /* Итоговый marker печатается после старых проверок ABI и новых сценариев реальных кнопок. */
} /* Тест собирается для Windows host и native ARM Linux из одного файла. */
+46
View File
@@ -21,3 +21,49 @@ Example Quartus source assignment:
```tcl
set_global_assignment -name VERILOG_FILE path/to/templates/hdl/logic-analyzer/rtl/la_capture_buffer.v
```
## Буфер с аппаратным триггером
`rtl/la_trigger_buffer.v` принимает восемь синхронизированных входов.
После `arm` первый отсчёт, удовлетворяющий маскам уровня и фронтов,
попадает в адрес 0. Нулевые маски включают немедленную запись.
`divider` задаёт шаг в тактах (минимум 1), `last_address` — последний адрес
записи включительно. По умолчанию глубина равна 65 536 отсчётам.
Модуль использует `rtl/la_capture_buffer.v`. После заполнения BRAM
он выдаёт отсчёты через `data_valid` и `ready`. Задержки читателя
влияют только на выгрузку. `stop` отменяет ожидание, запись или
выгрузку. Вход `clk` общий для обоих портов BRAM.
При `EXTERNAL_TRIGGER=1` и `use_external=1` вход `pretrigger`
задаёт число отсчётов перед срабатыванием. Блок собирает их в кольце
до разрешения `trigger_tick`. Подключите `external_hit` к
`rtl/la_advanced_trigger.v` и подайте на этот блок тот же отсчёт и
`trigger_tick`. Он выполняет до 16 стадий или сопоставляет биты
Serial Trigger по фронтам Clock между флагами Start и Stop.
`rtl/la_run_encoder.v` запускается по тому же типу триггера и кодирует
непрерывный поток. На каждый непрерывный уровень выдаёт два байта:
значение и число отсчётов минус один. Допустимая длина серии 1–256
отсчётов. Готовность выхода влияет на выдачу. При переполнении
очереди выставляется `overflow`, такт выборки сохраняется.
## Блоки AXI-захвата
- `rtl/axi_logic_analyzer.sv` принимает восемь входов и сохраняет окно в
BRAM. Управляющий AXI4-Lite и входной синхронизатор работают в домене
`clk`. `CLOCK_HZ` передаёт фактическую частоту в регистр метаданных.
`ADDR_WIDTH` задаёт глубину BRAM.
- `rtl/axi_logic_analyzer_bd.v` выводит интерфейсы в Vivado Block Design.
Для другого домена управления подключите AXI Clock Converter.
- `rtl/zynq_la_stream.v` пишет 8-битные отсчёты в кольцо DDR через
64-битный AXI4. Выделите 8 МиБ по параметру `DDR_BASE` и исключите
диапазон из общего распределителя DDR. Читатель подтверждает целые
блоки счётчиком CONSUMED. При отставании читателя блок выставляет ошибку.
- `include/zynq_la.h` и `include/zynq_la_stream.h` описывают регистры
и форматы Ethernet-сообщений ZLA1 и ZLS1.
Для буферного блока добавьте [vfd_pwm.sv](../vfd-pwm/rtl/vfd_pwm.sv)
и `la_capture_buffer.v`. Папка `tests` содержит проверки BRAM, фронтов,
переполнения и выдачи DDR через модель AXI. Привязку пинов, регистровые
адреса AXI и допустимую частоту определяет проект платы.
+24
View File
@@ -0,0 +1,24 @@
#ifndef ZYNQ_LA_H
#define ZYNQ_LA_H
#include <stdint.h>
/* All wire words are little endian. One request per TCP connection, port 37572.
* Request: magic, version, count, divider, level_mask, level_value, edge_mask,
* edge_value, test_enable, test_divider, test_duty (11 uint32 = 44 bytes).
* test_enable: 0 off, 1 PWM duty/256, 2 fast binary divided square waves.
* Reply: magic, version, status, clock_hz, divider, count, channels, flags
* (8 uint32 = 32 bytes), then count uint8 samples on success.
* flags bit 0: bounded snapshot, gaps between requests. No pre-trigger samples.
* Status: 0 success, 1 invalid request, 2 capture timeout, 3 hardware fault.
*/
#define ZLA_MAGIC UINT32_C(0x31414c5a)
#define ZLA_REQUEST UINT32_C(0x52414c5a)
#define ZLA_PORT 37572
#define ZLA_MAX_SAMPLES 65536
enum zla_register {
ZLA_ID=0, ZLA_STATUS=4, ZLA_CLOCK=8, ZLA_DEPTH=12,
ZLA_COUNT=16, ZLA_DIVIDER=20, ZLA_LEVEL_MASK=24, ZLA_LEVEL_VALUE=28,
ZLA_EDGE_MASK=32, ZLA_EDGE_VALUE=36, ZLA_COMMAND=40, ZLA_INDEX=44,
ZLA_DATA=48, ZLA_CAPTURED=52, ZLA_TEST_DIVIDER=56,
ZLA_TEST_DUTY=60, ZLA_TEST_ENABLE=64
};
#endif
@@ -0,0 +1,23 @@
#ifndef ZYNQ_LA_STREAM_H
#define ZYNQ_LA_STREAM_H
#include <stdint.h>
#define ZLS_MAGIC UINT32_C(0x31534c5a)
#define ZLS_PORT 37574
#define ZLS_CLOCK_HZ UINT32_C(100000000)
#define ZLS_BLOCK UINT32_C(16384)
#define ZLS_SLOTS UINT32_C(512)
#define ZLS_DDR_BASE UINT32_C(0x3e000000)
#define ZLS_REG_BASE UINT32_C(0x43c40000)
/* Little endian. Request: magic, version, divider, reserved.
* Header: magic, version, error, clock, divider, block bytes, channels, flags.
* Then frames: sequence, length, error, followed by length sample bytes.
* A zero length denotes a terminal error. Sequence starts at zero.
*/
enum zls_reg {
ZLS_ID=0, ZLS_STATUS=4, ZLS_CLOCK=8, ZLS_BLOCK_SIZE=12,
ZLS_DIVIDER=16, ZLS_COMMAND=20, ZLS_PRODUCED=24, ZLS_CONSUMED=28
};
enum zls_error { ZLS_BAD_REQUEST=1, ZLS_OVERFLOW=2, ZLS_DDR_ERROR=3, ZLS_NOT_READY=4 };
#endif
@@ -0,0 +1,204 @@
`timescale 1ns/1ps
// Eight asynchronous digital inputs, bounded BRAM capture, AXI4-Lite readout.
// AXI and sampling intentionally share clk: no multi-bit CDC in the control path.
module axi_logic_analyzer #(
parameter integer CLOCK_HZ = 100000000,
parameter integer ADDR_WIDTH = 16
)(
(* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 clk CLK", X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF s_axi, ASSOCIATED_RESET aresetn" *) input wire clk,
(* X_INTERFACE_INFO = "xilinx.com:signal:reset:1.0 aresetn RST", X_INTERFACE_PARAMETER = "POLARITY ACTIVE_LOW" *) input wire aresetn,
input wire [11:0] s_axi_awaddr, input wire [2:0] s_axi_awprot,
input wire s_axi_awvalid, output wire s_axi_awready,
input wire [31:0] s_axi_wdata, input wire [3:0] s_axi_wstrb,
input wire s_axi_wvalid, output wire s_axi_wready,
output reg [1:0] s_axi_bresp, output reg s_axi_bvalid, input wire s_axi_bready,
input wire [11:0] s_axi_araddr, input wire [2:0] s_axi_arprot,
input wire s_axi_arvalid, output wire s_axi_arready,
output reg [31:0] s_axi_rdata, output reg [1:0] s_axi_rresp,
output reg s_axi_rvalid, input wire s_axi_rready,
input wire [7:0] digital_inputs,
output wire [7:0] test_outputs
);
localparam integer DEPTH = 1 << ADDR_WIDTH;
(* ASYNC_REG = "TRUE" *) reg [7:0] input_meta, input_sync;
reg [7:0] previous;
reg busy, done, triggered, previous_valid;
reg [2:0] settle;
reg [31:0] count_cfg, captured;
reg [15:0] divider, ticks;
reg [7:0] level_mask, level_value, edge_mask, edge_value;
reg [ADDR_WIDTH-1:0] read_index;
reg [15:0] test_divider, test_ticks, test_phase;
reg [8:0] test_duty;
reg [1:0] test_enable;
reg vf_start, vf_off, vf_stop, vf_apply, vf_heartbeat;
reg signed [31:0] vf_target_cfg;
reg [31:0] vf_up, vf_down;
reg [15:0] vf_top, vf_dead, vf_mod;
wire vf_enabled, vf_fault;
wire signed [31:0] vf_actual, vf_target;
wire [5:0] vf_gates;
localparam integer VF_MAX_STEP=21474836; // 500 Hz at a 100 kHz DDS tick.
wire vf_valid=(CLOCK_HZ%100000==0 && vf_target_cfg>=-VF_MAX_STEP && vf_target_cfg<=VF_MAX_STEP &&
vf_up>=1 && vf_up<=VF_MAX_STEP && vf_down>=1 && vf_down<=VF_MAX_STEP &&
vf_top>=CLOCK_HZ/100000 && vf_top<=CLOCK_HZ/2000 && vf_top>=4 &&
vf_dead>=1 && vf_dead<=vf_top/4 && vf_mod<=29491);
vfd_pwm #(.CLOCK_HZ(CLOCK_HZ)) vfd (
.clk(clk), .aresetn(aresetn), .start(vf_start), .off(vf_off),
.ramp_stop(vf_stop), .apply_target(vf_apply), .heartbeat(vf_heartbeat),
.target_cfg(vf_target_cfg), .up_cfg(vf_up), .down_cfg(vf_down),
.top_cfg(vf_top), .dead_cfg(vf_dead), .mod_cfg(vf_mod),
.enabled(vf_enabled), .fault(vf_fault), .actual(vf_actual),
.target(vf_target), .gates(vf_gates)
);
wire [7:0] legacy_outputs;
assign test_outputs = vf_enabled ? {2'b0,vf_gates} : legacy_outputs;
genvar ch;
generate for (ch = 0; ch < 8; ch = ch + 1) begin: pwm
assign legacy_outputs[ch] = (test_enable == 2) ? test_phase[ch] :
((test_enable == 1) && ({1'b0, test_phase[ch +: 8]} < test_duty));
end endgenerate
wire [7:0] read_sample;
wire match_level = ((input_sync & level_mask) == (level_value & level_mask));
wire match_edge = (edge_mask == 0) || (previous_valid &&
(((input_sync ^ previous) & edge_mask) == edge_mask) &&
((input_sync & edge_mask) == (edge_value & edge_mask)));
wire sample_tick = busy && (settle == 0) && (ticks == 0);
wire store_sample = sample_tick && (triggered || (match_level && match_edge));
// Reuse the portable capture memory from templates. No reset on the RAM.
la_capture_buffer #(.SAMPLE_WIDTH(8), .ADDR_WIDTH(ADDR_WIDTH)) memory (
.wr_clk(clk), .wr_en(aresetn && store_sample),
.wr_addr(captured[ADDR_WIDTH-1:0]), .wr_data(input_sync),
.rd_clk(clk), .rd_addr(read_index), .rd_data(read_sample)
);
reg aw_pending, w_pending;
reg [11:0] write_address;
reg [31:0] write_data;
reg [3:0] write_strobes;
assign s_axi_awready = aresetn && !aw_pending && !s_axi_bvalid;
assign s_axi_wready = aresetn && !w_pending && !s_axi_bvalid;
assign s_axi_arready = aresetn && !s_axi_rvalid;
always @(posedge clk) begin
if (!aresetn) begin
input_meta <= 0; input_sync <= 0; previous <= 0;
busy <= 0; done <= 0; triggered <= 0; previous_valid <= 0;
settle <= 0; ticks <= 0; captured <= 0;
count_cfg <= DEPTH; divider <= 0;
level_mask <= 0; level_value <= 0; edge_mask <= 0; edge_value <= 0;
read_index <= 0; aw_pending <= 0; w_pending <= 0;
test_divider <= 0; test_ticks <= 0; test_phase <= 0;
test_duty <= 128; test_enable <= 0;
vf_start<=0; vf_off<=0; vf_stop<=0; vf_apply<=0; vf_heartbeat<=0;
vf_target_cfg<=0; vf_up<=1; vf_down<=1;
vf_top<=2500; vf_dead<=100; vf_mod<=16384;
write_address <= 0; write_data <= 0; write_strobes <= 0;
s_axi_bvalid <= 0; s_axi_bresp <= 0;
s_axi_rvalid <= 0; s_axi_rresp <= 0; s_axi_rdata <= 0;
end else begin
vf_start<=0; vf_off<=0; vf_stop<=0; vf_apply<=0; vf_heartbeat<=0;
input_meta <= digital_inputs; input_sync <= input_meta;
if (!test_enable) begin test_ticks <= 0; test_phase <= 0; end
else if (test_ticks == 0) begin test_ticks <= test_divider; test_phase <= test_phase + 1'b1; end
else test_ticks <= test_ticks - 1'b1;
if (busy) begin
if (settle != 0) settle <= settle - 1'b1;
else if (ticks != 0) ticks <= ticks - 1'b1;
else begin
ticks <= divider; previous <= input_sync; previous_valid <= 1;
if (store_sample) begin
triggered <= 1; captured <= captured + 1;
if (captured == count_cfg - 1) begin busy <= 0; done <= 1; end
end
end
end
if (s_axi_bvalid && s_axi_bready) s_axi_bvalid <= 0;
if (s_axi_rvalid && s_axi_rready) s_axi_rvalid <= 0;
if (s_axi_awvalid && s_axi_awready) begin
write_address <= s_axi_awaddr; aw_pending <= 1;
end
if (s_axi_wvalid && s_axi_wready) begin
write_data <= s_axi_wdata; write_strobes <= s_axi_wstrb; w_pending <= 1;
end
if (aw_pending && w_pending && !s_axi_bvalid) begin
aw_pending <= 0; w_pending <= 0; s_axi_bvalid <= 1; s_axi_bresp <= 2;
// Explicit full-word register ABI; zero strobes are a no-op.
if (write_strobes == 0) s_axi_bresp <= 0;
else if (write_strobes == 15) begin
if (write_address >= 'h48 && write_address <= 'h60) begin
case(write_address)
'h48: case(write_data)
1: if(!vf_enabled && vf_valid) begin vf_start<=1; test_enable<=0; s_axi_bresp<=0; end
2: begin vf_off<=1; s_axi_bresp<=0; end
3: begin vf_stop<=1; s_axi_bresp<=0; end
4: begin vf_heartbeat<=1; s_axi_bresp<=0; end
5: if(vf_enabled && vf_target_cfg>=-VF_MAX_STEP && vf_target_cfg<=VF_MAX_STEP) begin vf_apply<=1; s_axi_bresp<=0; end
default: ;
endcase
'h4c: if($signed(write_data)>=-VF_MAX_STEP && $signed(write_data)<=VF_MAX_STEP) begin vf_target_cfg<=write_data; s_axi_bresp<=0; end
'h50: if(!vf_enabled && write_data>=1 && write_data<=VF_MAX_STEP) begin vf_up<=write_data; s_axi_bresp<=0; end
'h54: if(!vf_enabled && write_data>=1 && write_data<=VF_MAX_STEP) begin vf_down<=write_data; s_axi_bresp<=0; end
'h58: if(!vf_enabled && write_data>=4 && write_data<=65535) begin vf_top<=write_data[15:0]; s_axi_bresp<=0; end
'h5c: if(!vf_enabled && write_data>=1 && write_data<=65535) begin vf_dead<=write_data[15:0]; s_axi_bresp<=0; end
'h60: if(!vf_enabled && write_data<=29491) begin vf_mod<=write_data[15:0]; s_axi_bresp<=0; end
default: ;
endcase
end else if (write_address == 'h28 && write_data == 2) begin
busy <= 0; done <= 0; triggered <= 0; captured <= 0;
s_axi_bresp <= 0;
end else if (!busy) begin
case (write_address)
'h10: if (write_data >= 1 && write_data <= DEPTH) begin count_cfg <= write_data; s_axi_bresp <= 0; end
'h14: if (write_data <= 65535) begin divider <= write_data[15:0]; s_axi_bresp <= 0; end
'h18: if (write_data <= 255) begin level_mask <= write_data[7:0]; s_axi_bresp <= 0; end
'h1c: if (write_data <= 255) begin level_value <= write_data[7:0]; s_axi_bresp <= 0; end
'h20: if (write_data <= 255) begin edge_mask <= write_data[7:0]; s_axi_bresp <= 0; end
'h24: if (write_data <= 255) begin edge_value <= write_data[7:0]; s_axi_bresp <= 0; end
'h28: if (write_data == 1) begin
busy <= 1; done <= 0; triggered <= 0; captured <= 0;
ticks <= 0; settle <= 3; previous_valid <= 0; s_axi_bresp <= 0;
end
'h2c: if (write_data < DEPTH) begin read_index <= write_data[ADDR_WIDTH-1:0]; s_axi_bresp <= 0; end
'h38: if (write_data <= 65535) begin test_divider <= write_data[15:0]; s_axi_bresp <= 0; end
'h3c: if (write_data <= 256) begin test_duty <= write_data[8:0]; s_axi_bresp <= 0; end
'h40: if (write_data <= 2 && (!vf_enabled || write_data==0)) begin test_enable <= write_data[1:0]; s_axi_bresp <= 0; end
default: ;
endcase
end
end
end
if (s_axi_arvalid && s_axi_arready) begin
s_axi_rvalid <= 1; s_axi_rresp <= 0; s_axi_rdata <= 0;
case (s_axi_araddr)
'h00: s_axi_rdata <= 32'h31414c5a; // little-endian "ZLA1"
'h04: s_axi_rdata <= {29'b0, triggered, done, busy};
'h08: s_axi_rdata <= CLOCK_HZ;
'h0c: s_axi_rdata <= DEPTH;
'h10: s_axi_rdata <= count_cfg;
'h14: s_axi_rdata <= {16'b0, divider};
'h18: s_axi_rdata <= {24'b0, level_mask};
'h1c: s_axi_rdata <= {24'b0, level_value};
'h20: s_axi_rdata <= {24'b0, edge_mask};
'h24: s_axi_rdata <= {24'b0, edge_value};
'h2c: s_axi_rdata <= read_index;
'h30: if (done && read_index < captured) s_axi_rdata <= {24'b0, read_sample}; else s_axi_rresp <= 2;
'h34: s_axi_rdata <= captured;
'h38: s_axi_rdata <= {16'b0, test_divider};
'h3c: s_axi_rdata <= {23'b0, test_duty};
'h40: s_axi_rdata <= {30'b0, test_enable};
'h44: s_axi_rdata <= 32'h3146565a; // ZVF1 capability
'h4c: s_axi_rdata <= vf_target_cfg;
'h50: s_axi_rdata <= vf_up;
'h54: s_axi_rdata <= vf_down;
'h58: s_axi_rdata <= {16'b0,vf_top};
'h5c: s_axi_rdata <= {16'b0,vf_dead};
'h60: s_axi_rdata <= {16'b0,vf_mod};
'h64: s_axi_rdata <= {29'b0,(vf_actual!=vf_target),vf_fault,vf_enabled};
'h68: s_axi_rdata <= vf_actual;
'h6c: s_axi_rdata <= 100000;
'h70: s_axi_rdata <= vf_target;
default: s_axi_rresp <= 2;
endcase
end
end
end
endmodule
@@ -0,0 +1,47 @@
`timescale 1ns/1ps
// Eight asynchronous digital inputs, bounded BRAM capture, AXI4-Lite readout.
// AXI and sampling intentionally share clk: no multi-bit CDC in the control path.
module axi_logic_analyzer_bd #(
parameter integer CLOCK_HZ = 100000000,
parameter integer ADDR_WIDTH = 16
)(
(* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 clk CLK", X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF s_axi, ASSOCIATED_RESET aresetn" *) input wire clk,
(* X_INTERFACE_INFO = "xilinx.com:signal:reset:1.0 aresetn RST", X_INTERFACE_PARAMETER = "POLARITY ACTIVE_LOW" *) input wire aresetn,
input wire [11:0] s_axi_awaddr, input wire [2:0] s_axi_awprot,
input wire s_axi_awvalid, output wire s_axi_awready,
input wire [31:0] s_axi_wdata, input wire [3:0] s_axi_wstrb,
input wire s_axi_wvalid, output wire s_axi_wready,
output wire [1:0] s_axi_bresp, output wire s_axi_bvalid, input wire s_axi_bready,
input wire [11:0] s_axi_araddr, input wire [2:0] s_axi_arprot,
input wire s_axi_arvalid, output wire s_axi_arready,
output wire [31:0] s_axi_rdata, output wire [1:0] s_axi_rresp,
output wire s_axi_rvalid, input wire s_axi_rready,
input wire [7:0] digital_inputs,
output wire [7:0] test_outputs
);
axi_logic_analyzer #(.CLOCK_HZ(CLOCK_HZ), .ADDR_WIDTH(ADDR_WIDTH)) core (
.clk(clk),
.aresetn(aresetn),
.s_axi_awaddr(s_axi_awaddr),
.s_axi_awprot(s_axi_awprot),
.s_axi_awvalid(s_axi_awvalid),
.s_axi_awready(s_axi_awready),
.s_axi_wdata(s_axi_wdata),
.s_axi_wstrb(s_axi_wstrb),
.s_axi_wvalid(s_axi_wvalid),
.s_axi_wready(s_axi_wready),
.s_axi_bresp(s_axi_bresp),
.s_axi_bvalid(s_axi_bvalid),
.s_axi_bready(s_axi_bready),
.s_axi_araddr(s_axi_araddr),
.s_axi_arprot(s_axi_arprot),
.s_axi_arvalid(s_axi_arvalid),
.s_axi_arready(s_axi_arready),
.s_axi_rdata(s_axi_rdata),
.s_axi_rresp(s_axi_rresp),
.s_axi_rvalid(s_axi_rvalid),
.s_axi_rready(s_axi_rready),
.digital_inputs(digital_inputs),
.test_outputs(test_outputs)
);
endmodule
@@ -0,0 +1,98 @@
`timescale 1ns / 1ps
// Eight-channel DSView Stage and Serial triggers. Each pattern contains
// level mask, expected value, and required-change mask in ascending bytes.
module la_advanced_trigger (
input wire clk, reset, arm, tick,
input wire [7:0] sample,
input wire serial_mode,
input wire [3:0] last_stage,
input wire config_write,
input wire [3:0] config_stage, config_flags,
input wire [23:0] config_a, config_b,
input wire [31:0] config_count,
output wire hit
);
reg [83:0] stages [0:15];
reg [83:0] condition;
reg [3:0] stage, last;
reg [31:0] count;
reg serial;
reg [7:0] previous;
reg previous_valid;
reg [23:0] start_flag, stop_flag, clock_flag;
reg [2:0] data_channel;
reg [15:0] data_mask, data_value, shift;
reg [4:0] bits, received;
reg serial_active;
function pattern_hit;
input [23:0] pattern;
input [7:0] value, prior;
input seen;
begin
pattern_hit = (value & pattern[7:0]) == pattern[15:8] &&
(pattern[23:16] == 0 ||
(seen && ((value ^ prior) & pattern[23:16]) == pattern[23:16]));
end
endfunction
wire a = pattern_hit(condition[23:0], sample, previous, previous_valid) ^ condition[82];
wire b = pattern_hit(condition[47:24], sample, previous, previous_valid) ^ condition[83];
wire stage_match = condition[80] ? (a && b) : (a || b);
wire stage_done = stage_match && count == condition[79:48];
wire start_hit = pattern_hit(start_flag, sample, previous, previous_valid);
wire stop_hit = stop_flag != 0 && pattern_hit(stop_flag, sample, previous, previous_valid);
wire clock_hit = pattern_hit(clock_flag, sample, previous, previous_valid);
wire [15:0] shifted = {shift[14:0], sample[data_channel]};
wire serial_hit = serial_active && !stop_hit && clock_hit &&
received >= bits && (shifted & data_mask) == data_value;
assign hit = tick && (serial ? serial_hit : stage_done && stage == last);
always @(posedge clk) begin
if (config_write) begin
stages[config_stage] <= {config_flags, config_count, config_b, config_a};
case (config_stage)
0: begin start_flag <= config_a; stop_flag <= config_b; end
1: clock_flag <= config_a;
2: data_channel <= config_count[2:0];
3: begin data_mask <= config_b[15:0]; data_value <= config_a[15:0]; bits <= config_count[4:0]; end
default: ;
endcase
end
if (reset || arm) begin
condition <= stages[0];
stage <= 0;
last <= last_stage;
count <= 0;
serial <= serial_mode;
previous_valid <= 0;
serial_active <= 0;
shift <= 0;
received <= 0;
end else if (tick) begin
previous <= sample;
previous_valid <= 1;
if (serial) begin
if (stop_hit) begin
serial_active <= 0;
shift <= 0;
received <= 0;
end else if (serial_active && clock_hit) begin
shift <= shifted;
if (received < bits)
received <= received + 1'b1;
end else if (start_hit) begin
serial_active <= 1;
end
end else if (stage_done && stage != last) begin
condition <= stages[stage + 4'd1];
stage <= stage + 1'b1;
count <= 0;
end else if (stage_match) begin
count <= count + 1'b1;
end else if (condition[81]) begin
count <= 0;
end
end
end
endmodule
+92
View File
@@ -0,0 +1,92 @@
`timescale 1ns / 1ps
// Triggered, lossless encoding of constant digital runs. Each run emits the
// sample byte followed by its duration minus one (1..256 sample ticks).
// The small queue absorbs clusters of edges. Overflow latches a fault
// instead of slowing the sampling clock or silently dropping edges.
module la_run_encoder (
input wire clk, reset, arm, stop, tick,
input wire [7:0] sample, channel_mask,
input wire [7:0] level_mask, level_value, edge_mask, edge_value, any_edge_mask,
output wire stored,
output reg overflow,
output wire valid,
output wire [7:0] data,
input wire ready
);
reg [7:0] channels, levels, values, directed, direction, edges;
reg [7:0] previous, run_sample, run_length, run_input;
reg previous_valid, triggered, run_tick, run_started;
wire [7:0] selected = sample & channels;
wire level_hit = (selected & levels) == values;
wire edge_hit = edges == 0 || (previous_valid &&
((selected ^ previous) & edges) == edges && (selected & directed) == direction);
assign stored = tick && !overflow && (triggered || (level_hit && edge_hit));
(* ram_style = "distributed" *) reg [15:0] runs [0:31];
reg [5:0] wr_ptr, rd_ptr;
reg [15:0] output_run;
reg output_valid, high_byte;
wire full = wr_ptr == {~rd_ptr[5], rd_ptr[4:0]};
wire emit = run_tick && !overflow && run_started &&
(run_input != run_sample || run_length == 255);
assign valid = output_valid && !overflow;
assign data = high_byte ? output_run[15:8] : output_run[7:0];
always @(posedge clk) begin
if (reset || stop || arm) begin
channels <= channel_mask;
levels <= level_mask & channel_mask;
values <= level_value & level_mask & channel_mask;
directed <= edge_mask & channel_mask;
direction <= edge_value & edge_mask & channel_mask;
edges <= (edge_mask | any_edge_mask) & channel_mask;
previous_valid <= 0;
triggered <= 0;
run_tick <= 0;
run_started <= 0;
overflow <= 0;
wr_ptr <= 0;
rd_ptr <= 0;
output_valid <= 0;
high_byte <= 0;
end else begin
// The trigger comparison and the run encoder each get a full cycle.
run_tick <= stored;
run_input <= selected;
if (tick) begin
previous <= selected;
previous_valid <= 1;
end
if (stored)
triggered <= 1;
if (run_tick && !overflow) begin
run_started <= 1;
if (!run_started || emit) begin
run_sample <= run_input;
run_length <= 0;
end else begin
run_length <= run_length + 1'b1;
end
end
if (emit) begin
if (full)
overflow <= 1;
else
wr_ptr <= wr_ptr + 1'b1;
end
if (!output_valid && rd_ptr != wr_ptr) begin
output_run <= runs[rd_ptr[4:0]];
rd_ptr <= rd_ptr + 1'b1;
output_valid <= 1;
end else if (valid && ready) begin
high_byte <= !high_byte;
if (high_byte)
output_valid <= 0;
end
end
end
always @(posedge clk)
if (!reset && !arm && !stop && emit && !full)
runs[wr_ptr[4:0]] <= {run_length, run_sample};
endmodule
+134
View File
@@ -0,0 +1,134 @@
`timescale 1ns / 1ps
// Record a finite window from the first matching sample, then read it out.
// Input samples are already synchronized to clk. Only the readout can stall.
module la_trigger_buffer #(
parameter integer ADDR_WIDTH = 16,
parameter integer EXTERNAL_TRIGGER = 0
)(
input wire clk, reset, arm, stop,
input wire [7:0] sample,
input wire [16:0] divider,
input wire [ADDR_WIDTH-1:0] last_address,
input wire [ADDR_WIDTH-1:0] pretrigger,
input wire use_external, external_hit,
output wire trigger_tick,
input wire [7:0] level_mask, level_value, edge_mask, edge_value, any_edge_mask,
output reg capturing,
output wire stored,
output reg data_valid,
output reg [7:0] data,
input wire ready
);
reg [16:0] period, timer;
reg [ADDR_WIDTH-1:0] write_address, read_address, pre, history, post_left, read_left;
reg [7:0] levels, values, directed, direction, edges, previous;
reg previous_valid, triggered, external_mode;
reg [1:0] read_pending;
wire [7:0] memory_data;
wire sample_tick = capturing && timer == 0 && !reset && !stop && !arm;
wire match_level = (sample & levels) == values;
wire match_edge = edges == 0 || (previous_valid &&
((sample ^ previous) & edges) == edges && (sample & directed) == direction);
assign trigger_tick = sample_tick && !triggered && history == 0;
wire fire = trigger_tick && (external_mode ? external_hit : match_level && match_edge);
assign stored = sample_tick && (triggered || fire);
reg write_pending;
reg [ADDR_WIDTH-1:0] pending_address;
reg [7:0] pending_sample;
always @(posedge clk) begin
write_pending <= sample_tick && (pre != 0 || triggered || fire);
pending_address <= write_address;
pending_sample <= sample;
end
la_capture_buffer #(.SAMPLE_WIDTH(8), .ADDR_WIDTH(ADDR_WIDTH)) memory (
.wr_clk(clk), .wr_en(write_pending && !reset), .wr_addr(pending_address), .wr_data(pending_sample),
.rd_clk(clk), .rd_addr(read_address), .rd_data(memory_data)
);
always @(posedge clk) begin
if (reset || stop) begin
capturing <= 0;
data_valid <= 0;
read_pending <= 0;
period <= 0;
timer <= 0;
pre <= 0;
history <= 0;
post_left <= 0;
read_left <= 0;
write_address <= 0;
read_address <= 0;
levels <= 0;
values <= 0;
directed <= 0;
direction <= 0;
edges <= 0;
previous <= 0;
previous_valid <= 0;
triggered <= 0;
external_mode <= 0;
end else if (arm) begin
capturing <= 1;
data_valid <= 0;
read_pending <= 0;
period <= divider - 1'b1;
timer <= 0;
pre <= EXTERNAL_TRIGGER && use_external ? pretrigger : 0;
history <= EXTERNAL_TRIGGER && use_external ? pretrigger : 0;
post_left <= last_address - (EXTERNAL_TRIGGER && use_external ? pretrigger : 0);
read_left <= last_address;
write_address <= 0;
read_address <= 0;
levels <= level_mask;
values <= level_value & level_mask;
directed <= edge_mask;
direction <= edge_value & edge_mask;
edges <= edge_mask | any_edge_mask;
previous_valid <= 0;
triggered <= 0;
external_mode <= EXTERNAL_TRIGGER && use_external;
end else if (capturing) begin
if (sample_tick) begin
timer <= period;
previous <= sample;
previous_valid <= 1;
if (history != 0)
history <= history - 1'b1;
if (pre != 0 || triggered || fire)
write_address <= write_address + 1'b1;
if (fire) begin
triggered <= 1;
read_address <= write_address - pre;
if (post_left == 0) begin
capturing <= 0;
read_pending <= 3;
end
end else if (triggered) begin
post_left <= post_left - 1'b1;
if (post_left == 1) begin
capturing <= 0;
read_pending <= 3;
end
end
end else begin
timer <= timer - 1'b1;
end
end else if (read_pending != 0) begin
read_pending <= read_pending - 1'b1;
// Keep the BRAM read mux out of the downstream FIFO write path.
if (read_pending == 1) begin
data <= memory_data;
data_valid <= 1;
end
end else if (data_valid && ready) begin
data_valid <= 0;
if (read_left != 0) begin
read_left <= read_left - 1'b1;
read_address <= read_address + 1'b1;
read_pending <= 2;
end
end
end
endmodule
+184
View File
@@ -0,0 +1,184 @@
`timescale 1ns/1ps
// Continuous byte sampler and write-only DMA for the reserved 8 MiB DDR ring.
module zynq_la_stream #(
parameter [31:0] DDR_BASE = 32'h3e000000
) (
(* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 clk CLK" *)
(* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF s_axi:m_axi, ASSOCIATED_RESET aresetn" *)
input wire clk,
(* X_INTERFACE_PARAMETER = "POLARITY ACTIVE_LOW" *)
input wire aresetn,
input wire [7:0] digital_inputs,
(* X_INTERFACE_PARAMETER = "PROTOCOL AXI4LITE, ADDR_WIDTH 12, DATA_WIDTH 32" *)
input wire [11:0] s_axi_awaddr,
input wire s_axi_awvalid,
output wire s_axi_awready,
input wire [31:0] s_axi_wdata,
input wire [3:0] s_axi_wstrb,
input wire s_axi_wvalid,
output wire s_axi_wready,
output reg [1:0] s_axi_bresp,
output reg s_axi_bvalid,
input wire s_axi_bready,
input wire [11:0] s_axi_araddr,
input wire s_axi_arvalid,
output wire s_axi_arready,
output reg [31:0] s_axi_rdata,
output wire [1:0] s_axi_rresp,
output reg s_axi_rvalid,
input wire s_axi_rready,
(* X_INTERFACE_PARAMETER = "PROTOCOL AXI4, READ_WRITE_MODE WRITE_ONLY, ADDR_WIDTH 32, DATA_WIDTH 64, MAX_BURST_LENGTH 16, NUM_WRITE_OUTSTANDING 1" *)
output wire [31:0] m_axi_awaddr,
output wire [7:0] m_axi_awlen,
output wire [2:0] m_axi_awsize,
output wire [1:0] m_axi_awburst,
output wire [3:0] m_axi_awcache,
output wire [2:0] m_axi_awprot,
output wire m_axi_awvalid,
input wire m_axi_awready,
output wire [63:0] m_axi_wdata,
output wire [7:0] m_axi_wstrb,
output wire m_axi_wlast,
output wire m_axi_wvalid,
input wire m_axi_wready,
input wire [1:0] m_axi_bresp,
input wire m_axi_bvalid,
output wire m_axi_bready
);
localparam [1:0] IDLE=0, ADDR=1, DATA=2, RESP=3;
localparam [31:0] MAGIC=32'h31534c5a;
(* ASYNC_REG = "TRUE" *) reg [7:0] input_meta, input_sync;
reg [1:0] state;
reg running;
reg [1:0] fault; // 1: FIFO or ring overflow. 2: DDR AXI response error.
reg [15:0] divider, ticks;
reg [2:0] byte_index;
reg [63:0] assembly;
reg [7:0] wr_pos, rd_pos;
reg [8:0] used;
(* ram_style = "distributed" *) reg [63:0] fifo [0:255];
reg [3:0] beat;
reg [6:0] burst_index;
reg [31:0] produced, consumed;
wire ring_full = produced-consumed == 32'd512;
wire sample_now = running && ticks==0;
wire pop = m_axi_wvalid && m_axi_wready;
wire word_now = sample_now && byte_index==7;
wire push = word_now && (used!=256 || pop);
wire [63:0] next_word = {input_sync, assembly[63:8]};
reg aw_pending, w_pending;
reg [11:0] aw_addr;
reg [31:0] w_data;
reg [3:0] w_strb;
wire commit = aw_pending && w_pending && !s_axi_bvalid;
wire start = commit && w_strb==4'hf && aw_addr==12'h014 &&
w_data==1 && !running && state==IDLE;
wire stop = commit && w_strb==4'hf && aw_addr==12'h014 && w_data==2;
assign s_axi_awready = aresetn && !aw_pending && !s_axi_bvalid;
assign s_axi_wready = aresetn && !w_pending && !s_axi_bvalid;
assign s_axi_arready = aresetn && !s_axi_rvalid;
assign s_axi_rresp = 2'b00;
assign m_axi_awaddr = {DDR_BASE[31:23], produced[8:0], burst_index, 7'b0};
assign m_axi_awlen = 8'd15;
assign m_axi_awsize = 3'd3;
assign m_axi_awburst = 2'b01;
assign m_axi_awcache = 4'b0010; // Normal noncacheable, nonbufferable.
assign m_axi_awprot = 3'b000;
assign m_axi_awvalid = state==ADDR;
assign m_axi_wdata = fifo[rd_pos];
assign m_axi_wstrb = 8'hff;
assign m_axi_wlast = beat==15;
assign m_axi_wvalid = state==DATA;
assign m_axi_bready = state==RESP;
always @(posedge clk) begin
input_meta <= digital_inputs;
input_sync <= input_meta;
if (!aresetn) begin
aw_pending<=0; w_pending<=0;
s_axi_bvalid<=0; s_axi_bresp<=0; s_axi_rvalid<=0; s_axi_rdata<=0;
divider<=10; consumed<=0;
end else begin
if (s_axi_awvalid && s_axi_awready) begin aw_addr<=s_axi_awaddr; aw_pending<=1; end
if (s_axi_wvalid && s_axi_wready) begin
w_data<=s_axi_wdata; w_strb<=s_axi_wstrb; w_pending<=1;
end
if (s_axi_bvalid && s_axi_bready) s_axi_bvalid<=0;
if (commit) begin
aw_pending<=0; w_pending<=0; s_axi_bvalid<=1; s_axi_bresp<=2'b10;
if (w_strb==4'hf) case (aw_addr)
12'h010: if (!running && state==IDLE && w_data>=10 && w_data<=65535) begin
divider<=w_data[15:0]; s_axi_bresp<=0;
end
12'h014: if (start || stop) s_axi_bresp<=0;
12'h01c: if (w_data==consumed+1 && consumed!=produced) begin
consumed<=w_data; s_axi_bresp<=0;
end
default: ;
endcase
end
if (start) consumed<=0;
if (s_axi_rvalid && s_axi_rready) s_axi_rvalid<=0;
if (s_axi_arvalid && s_axi_arready) begin
s_axi_rvalid<=1;
case (s_axi_araddr)
12'h000: s_axi_rdata<=MAGIC;
12'h004: s_axi_rdata<={28'b0, fault, (state!=IDLE), running};
12'h008: s_axi_rdata<=100000000;
12'h00c: s_axi_rdata<=16384;
12'h010: s_axi_rdata<={16'b0,divider};
12'h018: s_axi_rdata<=produced;
12'h01c: s_axi_rdata<=consumed;
default: s_axi_rdata<=0;
endcase
end
end
end
always @(posedge clk) begin
if (push && aresetn) fifo[wr_pos] <= next_word;
if (!aresetn || start) begin
state<=IDLE; running<=aresetn; fault<=0;
ticks<=0; byte_index<=0; assembly<=0;
wr_pos<=0; rd_pos<=0; used<=0;
beat<=0; burst_index<=0; produced<=0;
end else begin
if (stop) running<=0;
if (running) begin
if (sample_now) begin
ticks<=divider-1'b1;
assembly<=next_word;
byte_index<=byte_index+1'b1;
end else ticks<=ticks-1'b1;
if (word_now && !push) begin running<=0; fault<=1; end
end
if (push) wr_pos<=wr_pos+1'b1;
if (pop) rd_pos<=rd_pos+1'b1;
case ({push,pop})
2'b10: used<=used+1'b1;
2'b01: used<=used-1'b1;
default: ;
endcase
case (state)
IDLE: if (running && !stop && !ring_full && used>=16) state<=ADDR;
ADDR: if (m_axi_awready) begin state<=DATA; beat<=0; end
DATA: if (pop) begin
beat<=beat+1'b1;
if (m_axi_wlast) state<=RESP;
end
RESP: if (m_axi_bvalid) begin
state<=IDLE;
if (m_axi_bresp!=0) begin running<=0; fault<=2; end
else begin
burst_index<=burst_index+1'b1;
if (burst_index==127) produced<=produced+1'b1;
end
end
default: state<=IDLE;
endcase
end
end
endmodule
@@ -0,0 +1,133 @@
`timescale 1ns/1ps
module tb_axi_logic_analyzer;
reg clk=0; always #5 clk=~clk;
reg aresetn=0;
reg [11:0] s_axi_awaddr=0,s_axi_araddr=0;
reg [2:0] s_axi_awprot=0,s_axi_arprot=0;
reg s_axi_awvalid=0,s_axi_wvalid=0,s_axi_bready=0,s_axi_arvalid=0,s_axi_rready=0;
wire s_axi_awready,s_axi_wready,s_axi_bvalid,s_axi_arready,s_axi_rvalid;
reg [31:0] s_axi_wdata=0; reg [3:0] s_axi_wstrb=15;
wire [31:0] s_axi_rdata; wire [1:0] s_axi_bresp,s_axi_rresp;
reg [7:0] digital_inputs=0; wire [7:0] test_outputs;
axi_logic_analyzer #(.ADDR_WIDTH(4)) dut(.*);
task write_reg(input [11:0] address,input [31:0] value,input [1:0] response);
begin
// W before AW exercises independent AXI channels.
@(negedge clk); s_axi_wdata=value; s_axi_wvalid=1;
do @(posedge clk); while(!s_axi_wready);
@(negedge clk); s_axi_wvalid=0;
repeat(2) @(negedge clk);
s_axi_awaddr=address; s_axi_awvalid=1;
do @(posedge clk); while(!s_axi_awready);
@(negedge clk); s_axi_awvalid=0;
wait(s_axi_bvalid); #1;
if(s_axi_bresp!==response) $fatal(1,"write response addr=%h",address);
repeat(2) begin @(posedge clk); #1; if(!s_axi_bvalid || s_axi_bresp!==response) $fatal(1,"B unstable"); end
@(negedge clk); s_axi_bready=1;
@(negedge clk); s_axi_bready=0;
end
endtask
task read_reg(input [11:0] address,input [31:0] value,input [1:0] response);
begin
@(negedge clk); s_axi_araddr=address; s_axi_arvalid=1;
do @(posedge clk); while(!s_axi_arready);
@(negedge clk); s_axi_arvalid=0;
wait(s_axi_rvalid); #1;
if(s_axi_rresp!==response || (response==0 && s_axi_rdata!==value))
$fatal(1,"read addr=%h got=%h expected=%h resp=%h",address,s_axi_rdata,value,s_axi_rresp);
repeat(2) begin @(posedge clk); #1; if(!s_axi_rvalid || (response==0 && s_axi_rdata!==value)) $fatal(1,"R unstable"); end
@(negedge clk); s_axi_rready=1;
@(negedge clk); s_axi_rready=0;
end
endtask
integer n,seen=0,last_cycle=0,cycle=0;
integer ch,highs[0:7],last_rise[0:7];
reg [7:0] prior_output;
reg tracking=0; reg [7:0] expected[0:15];
always @(posedge clk) begin
cycle=cycle+1;
if(tracking && dut.store_sample) begin
if(seen>0 && cycle-last_cycle!=4) $fatal(1,"divider interval");
expected[seen]=dut.input_sync; seen=seen+1; last_cycle=cycle;
end
end
initial begin
repeat(4) @(negedge clk); aresetn=1;
read_reg('h00,32'h31414c5a,0); read_reg('h0c,16,0);
read_reg('h30,0,2);
write_reg('h10,0,2); write_reg('h10,17,2);
write_reg('h14,65536,2); write_reg('h03,0,2);
s_axi_wstrb=1; write_reg('h10,4,2); s_axi_wstrb=15;
write_reg('h10,12,0); write_reg('h14,3,0);
tracking=1; write_reg('h28,1,0);
while(!dut.done) begin @(negedge clk); digital_inputs=digital_inputs+7; end
tracking=0;
if(seen!=12) $fatal(1,"wrong count");
read_reg('h04,6,0); read_reg('h34,12,0);
for(n=0;n<12;n=n+1) begin write_reg('h2c,n,0); read_reg('h30,{24'b0,expected[n]},0); end
write_reg('h2c,12,0); read_reg('h30,0,2);
// Rearm, reject configuration while waiting, edge includes triggering sample.
digital_inputs=0; write_reg('h10,1,0); write_reg('h14,0,0);
write_reg('h20,128,0); write_reg('h24,128,0); write_reg('h28,1,0);
repeat(10) @(negedge clk);
read_reg('h04,1,0); write_reg('h10,2,2);
digital_inputs=128; wait(dut.done);
write_reg('h2c,0,0); read_reg('h30,128,0); read_reg('h34,1,0);
write_reg('h28,1,0); repeat(10) @(negedge clk);
// A static high is not a new rising edge.
read_reg('h04,1,0); write_reg('h28,2,0); read_reg('h04,0,0);
write_reg('h3c,256,0); write_reg('h40,1,0);
if(test_outputs!==255) $fatal(1,"PWM full duty");
write_reg('h3c,0,0); if(test_outputs!==0) $fatal(1,"PWM zero duty");
write_reg('h3c,257,2); write_reg('h40,0,0);
// Every generated channel: 50% duty and a factor-of-two period step.
write_reg('h3c,128,0); write_reg('h40,1,0);
for(ch=0;ch<8;ch=ch+1) begin highs[ch]=0; last_rise[ch]=-1; end
prior_output=test_outputs;
for(n=0;n<65536;n=n+1) begin
@(negedge clk);
for(ch=0;ch<8;ch=ch+1) begin
if(test_outputs[ch]) highs[ch]=highs[ch]+1;
if(test_outputs[ch] && !prior_output[ch]) begin
if(last_rise[ch]>=0 && n-last_rise[ch]!=(256<<ch)) $fatal(1,"PWM period ch=%d",ch);
last_rise[ch]=n;
end
end
prior_output=test_outputs;
end
for(ch=0;ch<8;ch=ch+1) if(highs[ch]!=32768) $fatal(1,"PWM duty ch=%d",ch);
write_reg('h40,2,0); write_reg('h40,3,2);
for(ch=0;ch<8;ch=ch+1) begin highs[ch]=0; last_rise[ch]=-1; end
prior_output=test_outputs;
for(n=0;n<1024;n=n+1) begin
@(negedge clk);
for(ch=0;ch<8;ch=ch+1) begin
if(test_outputs[ch]) highs[ch]=highs[ch]+1;
if(test_outputs[ch] && !prior_output[ch]) begin
if(last_rise[ch]>=0 && n-last_rise[ch]!=(2<<ch)) $fatal(1,"fast period ch=%d",ch);
last_rise[ch]=n;
end
end
prior_output=test_outputs;
end
for(ch=0;ch<8;ch=ch+1) if(highs[ch]!=512) $fatal(1,"fast duty ch=%d",ch);
// Reset cancels a half-written AXI transaction and disarms acquisition.
@(negedge clk); s_axi_awvalid=1; s_axi_awaddr='h28;
@(negedge clk); aresetn=0; s_axi_awvalid=0;
repeat(3) @(negedge clk); aresetn=1;
read_reg('h04,0,0);
read_reg('h44,32'h3146565a,0);
write_reg('h4c,100000,0); write_reg('h50,1000,0); write_reg('h54,2000,0);
write_reg('h58,2500,0); write_reg('h5c,100,0); write_reg('h60,20000,0);
write_reg('h48,1,0); read_reg('h64,5,0);
write_reg('h58,3000,2); write_reg('h40,1,2); // output ownership
write_reg('h4c,-100000,0); write_reg('h48,5,0); read_reg('h70,-100000,0);
write_reg('h14,500,0); write_reg('h28,1,0); // capture concurrently
write_reg('h48,4,0); write_reg('h48,2,0); read_reg('h64,0,0);
read_reg('h04,5,0); // stopping VFD does not stop capture
write_reg('h28,2,0);
write_reg('h5c,2000,0); write_reg('h48,1,2); // reject invalid deadtime/top
$display("PASS tb_axi_logic_analyzer"); $finish;
end
initial begin #1000000; $fatal(1,"timeout"); end
endmodule
@@ -0,0 +1,86 @@
`timescale 1ns / 1ps
module tb_la_advanced_trigger;
reg clk = 0;
always #2.5 clk = !clk;
reg reset = 1, arm = 0, tick = 0, serial_mode = 0, config_write = 0;
reg [7:0] sample = 0;
reg [3:0] last_stage = 1, config_stage = 0, config_flags = 0;
reg [23:0] config_a = 0, config_b = 0;
reg [31:0] config_count = 0;
wire hit;
la_advanced_trigger dut (.*);
task configure(input [3:0] index, flags, input [23:0] a, b, input [31:0] count);
@(negedge clk);
config_write = 1;
config_stage = index;
config_flags = flags;
config_a = a;
config_b = b;
config_count = count;
@(negedge clk);
config_write = 0;
endtask
task rearm;
@(negedge clk);
arm = 1;
@(negedge clk);
arm = 0;
endtask
task send_sample(input [7:0] value, input expected);
@(negedge clk);
sample = value;
tick = 1;
#1;
if (hit !== expected)
$fatal(1, "sample %h: expected trigger %d, got %d", value, expected, hit);
@(negedge clk);
tick = 0;
endtask
initial begin
repeat (2) @(negedge clk);
reset = 0;
// Stage 0 requires three adjacent samples with D1 high.
configure(0, 4'b0011, 24'h000202, 0, 2);
// Stage 1 accepts a rising D0 or a falling D2.
configure(1, 4'b0000, 24'h010101, 24'h040004, 0);
rearm();
send_sample(0, 0);
send_sample(2, 0);
send_sample(2, 0);
send_sample(0, 0);
send_sample(1, 0);
send_sample(2, 0);
send_sample(2, 0);
send_sample(2, 0);
send_sample(6, 0);
send_sample(2, 1);
serial_mode = 1;
last_stage = 3;
// D2 is the frame gate, D0 clocks D1. Match 101 MSB first.
configure(0, 0, 24'h040404, 24'h040004, 0);
configure(1, 0, 24'h010101, 0, 0);
configure(2, 0, 0, 0, 1);
configure(3, 0, 5, 7, 2);
rearm();
send_sample(0, 0);
send_sample(4, 0);
send_sample(7, 0);
send_sample(4, 0);
send_sample(5, 0);
send_sample(0, 0); // STOP clears 10, so no match across frames.
send_sample(4, 0);
send_sample(7, 0);
send_sample(4, 0);
send_sample(5, 0);
send_sample(4, 0);
send_sample(7, 1);
$display("PASS tb_la_advanced_trigger: contiguous count, stage OR, serial start/stop/data");
$finish;
end
endmodule
@@ -0,0 +1,101 @@
`timescale 1ns/1ps
module tb_zynq_la_stream;
reg clk=0;
always #5 clk=~clk;
reg aresetn=0;
reg [7:0] digital_inputs=0;
reg [11:0] s_axi_awaddr=0,s_axi_araddr=0;
reg [31:0] s_axi_wdata=0;
reg [3:0] s_axi_wstrb=15;
reg s_axi_awvalid=0,s_axi_wvalid=0,s_axi_bready=1;
reg s_axi_arvalid=0,s_axi_rready=1;
wire s_axi_awready,s_axi_wready,s_axi_bvalid,s_axi_arready,s_axi_rvalid;
wire [1:0] s_axi_bresp,s_axi_rresp;
wire [31:0] s_axi_rdata,m_axi_awaddr;
wire [7:0] m_axi_awlen,m_axi_wstrb;
wire [2:0] m_axi_awsize,m_axi_awprot;
wire [1:0] m_axi_awburst;
wire [3:0] m_axi_awcache;
wire m_axi_awvalid,m_axi_wlast,m_axi_wvalid,m_axi_bready;
wire [63:0] m_axi_wdata;
reg m_axi_bvalid=0;
reg [1:0] m_axi_bresp=0;
reg pause_data=0, check_bytes=1;
integer cycle=0;
wire m_axi_awready = cycle % 5 == 3;
wire m_axi_wready = !pause_data && cycle % 3 != 0;
zynq_la_stream dut(.*);
reg [7:0] expected[0:65535];
integer captured=0, transmitted=0, bursts=0, beat=0, i;
always @(negedge clk) begin
cycle=cycle+1;
digital_inputs=digital_inputs+1;
end
always @(posedge clk) if(aresetn) begin
if(dut.sample_now && check_bytes) begin
expected[captured]=digital_inputs-8'd2;
captured=captured+1;
end
if(m_axi_awvalid && m_axi_awready) begin
if(m_axi_awaddr!==32'h3e000000+128*bursts || m_axi_awlen!=15 ||
m_axi_awsize!=3 || m_axi_awburst!=1) $fatal(1,"bad DDR burst address/geometry");
bursts=bursts+1;
beat=0;
end
if(m_axi_wvalid && m_axi_wready) begin
if(m_axi_wlast !== (beat==15) || m_axi_wstrb!=255) $fatal(1,"bad DDR beat");
if(check_bytes) for(i=0;i<8;i=i+1)
if(m_axi_wdata[i*8+:8]!==expected[transmitted+i])
$fatal(1,"bad DDR data index=%d got=%h expected=%h",
transmitted+i,m_axi_wdata[i*8+:8],expected[transmitted+i]);
transmitted=transmitted+8;
beat=beat+1;
if(m_axi_wlast) m_axi_bvalid<=1;
end
if(m_axi_bvalid && m_axi_bready) m_axi_bvalid<=0;
end
task write_ctrl(input [11:0] address,input [31:0] value);
begin
@(negedge clk);
s_axi_awaddr=address; s_axi_awvalid=1;
@(posedge clk);
while(!s_axi_awready) @(posedge clk);
@(negedge clk);
s_axi_awvalid=0; s_axi_wdata=value; s_axi_wvalid=1;
@(posedge clk);
while(!s_axi_wready) @(posedge clk);
@(negedge clk);
s_axi_wvalid=0;
wait(s_axi_bvalid); #1;
if(s_axi_bresp!=0) $fatal(1,"register write failed");
@(negedge clk);
end
endtask
initial begin
repeat(4) @(negedge clk); aresetn=1;
write_ctrl(12'h010,10);
write_ctrl(12'h014,1);
wait(dut.produced==1); @(negedge clk);
if(transmitted!=16384 || captured<16384 || dut.fault!=0) $fatal(1,"bad block commit");
write_ctrl(12'h01c,1);
if(dut.consumed!=1) $fatal(1,"block not acknowledged");
write_ctrl(12'h014,2);
wait(dut.state==0); @(negedge clk);
if(dut.running || dut.fault!=0) $fatal(1,"stop did not drain");
bursts=0;
check_bytes=0;
pause_data=1;
write_ctrl(12'h014,1);
wait(dut.fault==1); @(negedge clk);
if(dut.running || dut.produced!=0 || bursts!=1) $fatal(1,"overflow did not stop sampling");
pause_data=0;
wait(dut.state==0); @(negedge clk);
if(dut.produced!=0 || dut.fault!=1) $fatal(1,"overflow published incomplete data");
$display("PASS tb_zynq_la_stream byte order, stalled AXI, commit, ACK, stop and overflow");
$finish;
end
initial begin #3000000; $fatal(1,"timeout"); end
endmodule
+18
View File
@@ -0,0 +1,18 @@
# Цифровая осциллограмма для HDMI
`rtl/zynq_logic_preview.v` выдаёт RGB, DE, HSYNC и VSYNC для
640×480 при пиксельной частоте 25,2 МГц. Для HDMI подключите внешний
RGB→TMDS передатчик, например Digilent rgb2dvi с сериализацией 126 МГц.
Домен `capture_clk` заполняет скрытый банк из 512 точек. Параметр
`CAPTURE_HZ` должен соответствовать его частоте, по умолчанию 125 МГц.
Если `stream_active` поднят, отсчёты берутся по `stream_valid` из
`stream_sample`. При простое локальные входы `digital_inputs` читаются
на 5 Мвыб/с. Кадр начинается по нарастающему фронту D3 или
автоматически после 10 мс ожидания.
Домен `pixel_clk` переключает банк в вертикальном гашении и возвращает
подтверждение записи. Синхронизируйте снятие сброса отдельно для каждого
домена. RGB-байты расположены в порядке R, B, G для rgb2dvi. Тест
`tests/tb_zynq_logic_preview.sv` проверяет горизонтальные уровни и
вертикальные фронты в восьмой строке.
@@ -0,0 +1,134 @@
`timescale 1ns/1ps
// Два банка: сеть пишет новый кадр, HDMI читает последний завершённый.
// Передача номера банка происходит только во время вертикального гашения.
module zynq_logic_preview #(
parameter integer CAPTURE_HZ = 125000000
) (
input wire capture_clk,
input wire capture_reset,
input wire [7:0] digital_inputs,
input wire stream_active,
input wire stream_valid,
input wire [7:0] stream_sample,
input wire pixel_clk,
input wire pixel_reset,
output wire [23:0] rgb,
output reg de,
output reg hsync,
output reg vsync
);
localparam integer POINTS = 512;
localparam integer LOCAL_DIVIDER = CAPTURE_HZ / 5000000;
localparam integer AUTO_TICKS = CAPTURE_HZ / 100; // 10 ms maximum wait for D3.
reg [7:0] banks [0:2*POINTS-1];
(* ASYNC_REG = "TRUE" *) reg [7:0] input_meta, input_sync;
(* ASYNC_REG = "TRUE" *) reg [1:0] acknowledge_sync;
(* ASYNC_REG = "TRUE" *) reg [1:0] publish_sync;
reg publish, acknowledge, frame_ready;
reg [8:0] write_column;
localparam integer TIMER_BITS = LOCAL_DIVIDER > 1 ? $clog2(LOCAL_DIVIDER) : 1;
reg [TIMER_BITS-1:0] sample_timer;
reg [20:0] trigger_wait;
reg prior_trigger, capturing, prior_stream;
wire sample_due = stream_active ? stream_valid : sample_timer == 0;
wire [7:0] sample = stream_active ? stream_sample : input_sync;
wire bank_free = acknowledge_sync[1] == publish;
wire begin_frame = !capturing && ((!prior_trigger && sample[3]) || trigger_wait == AUTO_TICKS);
wire write_sample = !capture_reset && bank_free && sample_due &&
prior_stream == stream_active && (capturing || begin_frame);
always @(posedge capture_clk) begin
input_meta <= digital_inputs;
input_sync <= input_meta;
if (write_sample)
banks[{~publish, write_column}] <= sample;
if (capture_reset) begin
acknowledge_sync <= 0;
publish <= 0;
write_column <= 0;
sample_timer <= 0;
trigger_wait <= 0;
prior_trigger <= 0;
capturing <= 0;
prior_stream <= 0;
end else begin
acknowledge_sync <= {acknowledge_sync[0], acknowledge};
prior_stream <= stream_active;
if (sample_due)
prior_trigger <= sample[3];
sample_timer <= sample_timer == 0 ? LOCAL_DIVIDER - 1 : sample_timer - 1'b1;
if (prior_stream != stream_active) begin
// Кадр целиком относится к одному источнику и одной частоте.
write_column <= 0;
trigger_wait <= 0;
capturing <= 0;
end else if (write_sample) begin
trigger_wait <= 0;
write_column <= write_column + 1'b1;
capturing <= write_column != POINTS - 1;
if (write_column == POINTS - 1)
publish <= !publish;
end else if (bank_free && !capturing && trigger_wait < AUTO_TICKS) begin
trigger_wait <= trigger_wait + 1'b1;
end
end
end
// 640x480 at 60 Hz (pixel clock 25.2 MHz), negative sync polarity.
reg [9:0] x, y;
reg [9:0] screen_x, screen_y;
reg [7:0] level, previous_level;
wire [8:0] read_column = x[8:0] - 9'd64;
always @(posedge pixel_clk) begin
level <= banks[{acknowledge, read_column}];
previous_level <= level;
if (pixel_reset) begin
publish_sync <= 0;
acknowledge <= 0;
frame_ready <= 0;
x <= 0;
y <= 0;
screen_x <= 0;
screen_y <= 0;
de <= 0;
hsync <= 1;
vsync <= 1;
end else begin
publish_sync <= {publish_sync[0], publish};
if (x == 0 && y == 480 && acknowledge != publish_sync[1]) begin
acknowledge <= publish_sync[1];
frame_ready <= 1;
end
if (x == 799) begin
x <= 0;
y <= y == 524 ? 0 : y + 1'b1;
end else x <= x + 1'b1;
screen_x <= x;
screen_y <= y;
de <= x < 640 && y < 480;
hsync <= !(x >= 656 && x < 752);
vsync <= !(y >= 490 && y < 492);
end
end
// Восьми строкам отведено по 56 пикселей. Вертикальный штрих рисует
// фронт между соседними точками, а не соединяет их наклонным отрезком.
wire [8:0] row = screen_y - 10'd16;
wire [2:0] channel = row >= 392 ? 7 : row >= 336 ? 6 : row >= 280 ? 5 :
row >= 224 ? 4 : row >= 168 ? 3 : row >= 112 ? 2 : row >= 56 ? 1 : 0;
wire [5:0] row_y = row - ({6'd0, channel} << 6) + ({6'd0, channel} << 3);
wire plot = screen_x >= 64 && screen_x < 576 && screen_y >= 16 && screen_y < 464;
wire trace = frame_ready && plot &&
((level[channel] ? (row_y == 17 || row_y == 18) : (row_y == 37 || row_y == 38)) ||
(screen_x != 64 && level[channel] != previous_level[channel] && row_y >= 17 && row_y <= 38));
wire grid = plot && (screen_x[5:0] == 0 || row_y == 0 || row_y == 27);
wire channel_mark = screen_x >= 42 && screen_x < 51 &&
screen_y >= 16 && screen_y < 464 && row_y >= 23 && row_y < 32;
// rgb2dvi takes RGB in the order R, B, G on its three byte lanes.
assign rgb = trace ? 24'h7040ff : channel_mark ? 24'h8080a0 :
grid ? 24'h203028 : 24'h08120e;
endmodule
@@ -0,0 +1,51 @@
`timescale 1ns/1ps
module tb_zynq_logic_preview;
reg capture_clk = 0, pixel_clk = 0;
always #4 capture_clk = !capture_clk;
always #20 pixel_clk = !pixel_clk;
reg capture_reset = 1, pixel_reset = 1;
reg [7:0] data = 0;
reg [2:0] counter = 0;
always @(negedge capture_clk) begin
counter <= counter + 1'b1;
data <= counter[2] ? 8'hff : 8'h00;
end
wire [23:0] rgb;
wire de, hsync, vsync;
zynq_logic_preview dut (
.capture_clk(capture_clk), .capture_reset(capture_reset), .digital_inputs(8'd0),
.stream_active(1'b1), .stream_valid(1'b1), .stream_sample(data),
.pixel_clk(pixel_clk), .pixel_reset(pixel_reset),
.rgb(rgb), .de(de), .hsync(hsync), .vsync(vsync)
);
integer high_pixels = 0, low_pixels = 0, edges = 0, active_pixels = 0;
// D7 is the last row. It must be visible and carry the same waveform as D0.
always @(negedge pixel_clk) if (dut.frame_ready && de) begin
if (dut.screen_y == 16 + 56*7 + 17 && rgb == 24'h7040ff)
high_pixels = high_pixels + 1;
if (dut.screen_y == 16 + 56*7 + 37 && rgb == 24'h7040ff)
low_pixels = low_pixels + 1;
if (dut.screen_y == 16 + 56*7 + 27 && rgb == 24'h7040ff)
edges = edges + 1;
if (dut.screen_y == 16 + 56*7 + 17)
active_pixels = active_pixels + 1;
end
initial begin
#200 capture_reset = 0;
pixel_reset = 0;
wait(dut.frame_ready);
wait(dut.screen_y == 479);
if (high_pixels < 240 || low_pixels < 240 || edges < 120 || active_pixels != 640)
$fatal(1, "Wrong final row: high=%d low=%d edge=%d active=%d",
high_pixels, low_pixels, edges, active_pixels);
$display("PASS tb_zynq_logic_preview: all eight rows fit, D7 renders levels and vertical edges");
$finish;
end
initial begin
#50000000;
$fatal(1, "No complete HDMI frame");
end
endmodule
+76
View File
@@ -0,0 +1,76 @@
# Ethernet-поток цифровых входов
`zynq_pl_ethernet` соединяет 1G MAC, ARP/IPv4/UDP, FIFO отсчётов и PHY
RTL8211F. Передача выполняется в PL. На вход `clk` подайте 125 МГц
для MAC, на `sample_clk` — 200 МГц для захвата, на
`delay_ref_clk` — стабильные 200 МГц для IDELAYCTRL. Синхронизируйте
снятие `sample_reset` с `sample_clk`. Параметры
`LOCAL_IP` и `LOCAL_MAC` задаются при инстанцировании. Подключите ПК
и FPGA к одной подсети /24. ARP-блок использует `.1` этой подсети
при проверке адреса назначения.
- `rtl8211f_init.v` формирует сброс PHY и по MDIO читает, меняет и
проверяет задержки RX/TX. Адрес PHY в этой конфигурации равен 1.
При MDC 1,25 МГц чтение выполняется через 400 нс после фронта.
Линии `phy_mdio_i`, `phy_mdio_o` и `phy_mdio_t` подключаются
к IOBUF на верхнем уровне проекта. В Vivado BD экспортируйте
интерфейс `mdio`, чтобы обёртка поставила IOBUF на вывод корпуса.
- `zynq_pl_capture.v` синхронизирует восемь входов, передаёт отсчёты
между доменами через асинхронный FIFO и выдаёт UDP-блоки
с номером и защищает захват идентификатором сессии. KEEPALIVE
продлевает захват, STOP освобождает FIFO.
- При `BUFFER_ENABLED=1` этот блок также принимает ZLB1 на том же
UDP-порту. Аппаратный триггер запускает запись до 65 536 отсчётов
на восьми каналах при 200 Мвыб/с. После записи окно передаётся через
общий FIFO. Подключите `../logic-analyzer/rtl/la_trigger_buffer.v`
и `../logic-analyzer/rtl/la_capture_buffer.v`. Обёртка
`zynq_pl_ethernet` включает этот режим.
- ZLT1 добавляет предысторию в BRAM, настройку до 16 стадий
и последовательный триггер. Подключите
`../logic-analyzer/rtl/la_advanced_trigger.v`. Каждая стадия
подтверждается перед загрузкой следующей.
- При `RLE_ENABLED=1` команда ZLR1 запускает непрерывную запись при
200 Мвыб/с от аппаратного триггера. Подключите
`../logic-analyzer/rtl/la_run_encoder.v`. Блок передаёт пары
байтов «отсчёт, длина минус один» и сообщает о переполнении, если
сеть и очередь не успевают передать поток переходов.
- `zynq_pl_vfd.v` принимает ZVF1 на UDP 37573 и передаёт команды через
XPM handshake в домен генератора. Два слова перед ZVF1 содержат токен
и номер запроса. Повтор запроса возвращает сохранённый ответ.
`vfd_command` содержит 3 бита операции и шесть 32-битных параметров
в порядке target, up, down, top, dead, mod от младших разрядов.
`vfd_reply` содержит два бита ошибки, три флага, target и actual
от старших разрядов. Ответы чередуются с пакетами захвата на границах UDP.
- `preview_active`, `preview_valid` и `preview_sample` в домене
`sample_clk` передают те же отсчёты на отрисовку.
- `include/zynq_la_pl.h` задаёт форматы ZLT1, ZLR1, ZLB1, ZLP2 и ZLP1, UDP-порт 37575.
ZLP2 передаёт маску каналов в START. При 1–4 каналах на байт
приходится два отсчёта, максимальная частота 200 Мвыб/с. При 5–8
каналах передаётся байт на отсчёт, предел 100 Мвыб/с.
- `tests/check_zynq_rj2.py <IPv4>` проверяет порядок и измеряет темп
поступления отсчётов. Делитель выбирается через `--divider`, маска
каналов через `--mask` (например `0xa5` для D0, D2, D5 и D7).
Приёмник восстанавливает порядок блоков в окне на 4096 пакетов.
Для синтеза нужны Xilinx 7 Series и модули
[verilog-ethernet](https://github.com/alexforencich/verilog-ethernet/tree/77320a9471d19c7dd383914bc049e02d9f4f1ffb)
включая `lib/axis`. Пины, синхронизацию и задержки RGMII задаёт
проект платы. XSIM-проверка блока отсчётов:
```text
xvlog -sv ../logic-analyzer/rtl/la_capture_buffer.v ../logic-analyzer/rtl/la_trigger_buffer.v ../logic-analyzer/rtl/la_advanced_trigger.v ../logic-analyzer/rtl/la_run_encoder.v rtl/zynq_pl_capture.v tests/tb_zynq_pl_capture.sv
xvlog <Vivado>/data/verilog/src/glbl.v
xelab -L xpm tb_zynq_pl_capture glbl -s rj2_capture
xsim rj2_capture -runall
```
`tests/tb_zynq_pl_ethernet.sv` проверяет ARP и ответ ZLP2 на уровне
Ethernet-кадров. В проекте Vivado с RTL Ethernet добавьте этот файл в
набор источников симуляции и выберите `tb_zynq_pl_ethernet` как верхний модуль.
Проверка инициализации PHY с задержкой ответа MDIO 300 нс:
```text
iverilog -g2012 -s tb_rtl8211f_init -o mdio.vvp rtl/rtl8211f_init.v tests/tb_rtl8211f_init.sv
vvp mdio.vvp
```
@@ -0,0 +1,44 @@
#ifndef ZYNQ_LA_PL_H
#define ZYNQ_LA_PL_H
/* ZLP1 and ZLP2 run over UDP on RJ2. Each word is little endian.
* ZLP2 START: divider in bits 0..15 and channel mask in bits 16..23.
* With 1..4 channels, each byte contains two samples, older nibble first.
* Nibble bits follow ascending enabled channel numbers. With 5..8 channels,
* the payload contains one sample byte at the original channel positions.
* Request: magic, operation, session (nonzero), divider.
* Reply: magic, type, session, value. DATA adds ZLP_BLOCK sample bytes.
* A START session expires if KEEPALIVE is absent for two seconds.
* ZLB1 captures eight bits per sample in BRAM before sending DATA.
* Its START appends three words to the four-word request: sample count
* (1024..65536, multiple of ZLP_BLOCK), level mask/value and directed
* edge mask/value (low to high bytes), and any-edge mask (low byte).
* PROBE, STOP, KEEPALIVE and all reply formats are shared.
* ZLR1 uses the ZLB1 START shape. The divider is 1, the channel mask
* occupies bits 16..23, and the count word is zero for continuous capture.
* DATA holds pairs of a sample byte and a run length minus one (1..256).
* If samples cannot be encoded and transferred in time, it reports OVERFLOW.
* ZLT1 uses the same buffered DATA and adds CONFIG (op 4). CONFIG appends
* two 24-bit patterns and a 32-bit count to the 16-byte request. Its value
* packs stage index (low nibble) and AND, contiguous, invert A/B flags.
* Patterns contain level mask, level value, and change mask. Reply type 3
* acknowledges a stage. START appends count, serial/last-stage/pretrigger,
* and zero. Serial uses stages 0..3 for start/stop, clock, data pin, and
* data value/mask/bit count minus one.
*/
#define ZLP_MAGIC 0x31504c5au
#define ZLP2_MAGIC 0x32504c5au
#define ZLB_MAGIC 0x31424c5au
#define ZLR_MAGIC 0x31524c5au
#define ZLT_MAGIC 0x31544c5au
#define ZLP_PORT 37575
#define ZLP_CLOCK_HZ 100000000u
#define ZLP2_CLOCK_HZ 200000000u
#define ZLP_LEGACY_CLOCK_HZ 125000000u
#define ZLP_BLOCK 1024u
#define ZLP_MIN_DIVIDER 1u
#define ZLP_LEGACY_MIN_DIVIDER 2u
enum zlp_operation { ZLP_PROBE, ZLP_START, ZLP_STOP, ZLP_KEEPALIVE, ZLP_CONFIG };
enum zlp_type { ZLP_INFO, ZLP_DATA, ZLP_ERROR, ZLP_CONFIGURED };
enum zlp_error { ZLP_BAD_DIVIDER=1, ZLP_BUSY, ZLP_OVERFLOW };
#endif
+105
View File
@@ -0,0 +1,105 @@
`timescale 1ns / 1ps
// Configure PHY2 at MDIO address 1 for PHY-provided RX and TX clock delays.
module rtl8211f_init (
input wire clk,
input wire reset,
output wire phy_reset_n,
output reg mdc,
input wire mdio_i,
output wire mdio_o,
output wire mdio_t,
output reg ready
);
// At 125 MHz, 50 cycles give the PHY 400 ns to return each MDIO bit.
// RTL8211F can take up to 300 ns after MDC rises.
reg [22:0] boot_wait;
reg [5:0] phase;
reg [3:0] step;
reg [6:0] bits_left;
reg [63:0] frame;
reg reading;
reg [15:0] read_data;
reg read_ack;
reg [15:0] tx_control, rx_control;
wire waiting = boot_wait < 7500000;
assign phy_reset_n = !reset && boot_wait >= 1250000;
assign mdio_o = frame[63];
assign mdio_t = bits_left == 0 || (reading && bits_left <= 18);
task transaction;
input is_read;
input [4:0] addr;
input [15:0] value;
begin
frame <= {32'hffffffff, 2'b01, (is_read ? 2'b10 : 2'b01),
5'd1, addr, 2'b10, value};
reading <= is_read;
bits_left <= 64;
phase <= 0;
read_ack <= 0;
end
endtask
always @(posedge clk) begin
if (reset) begin
boot_wait <= 0;
phase <= 0;
step <= 0;
bits_left <= 0;
frame <= 0;
reading <= 0;
read_data <= 0;
read_ack <= 0;
tx_control <= 0;
rx_control <= 0;
mdc <= 0;
ready <= 0;
end else if (waiting) begin
boot_wait <= boot_wait + 1'b1;
end else if (bits_left != 0) begin
if (phase == 49) begin
phase <= 0;
mdc <= !mdc;
if (mdc) begin
if (reading) begin
// PHY launches ACK during the first turnaround clock.
// The final clock lets it release the bus before the next preamble.
if (bits_left == 18)
read_ack <= !mdio_i;
else if (bits_left >= 2 && bits_left <= 17)
read_data <= {read_data[14:0], mdio_i};
end
bits_left <= bits_left - 1'b1;
frame <= frame << 1;
if (bits_left == 1)
step <= step + 1'b1;
end
end else begin
phase <= phase + 1'b1;
end
end else begin
case (step)
0: transaction(0, 5'h1f, 16'h0d08);
1: transaction(1, 5'h11, 0);
2: if (read_ack) begin
tx_control <= read_data | 16'h0100;
transaction(0, 5'h11, read_data | 16'h0100);
end else step <= 0;
3: transaction(1, 5'h15, 0);
4: if (read_ack) begin
rx_control <= read_data | 16'h0008;
transaction(0, 5'h15, read_data | 16'h0008);
end else step <= 0;
5: transaction(1, 5'h11, 0);
6: if (read_ack && read_data == tx_control)
transaction(1, 5'h15, 0);
else step <= 0;
7: if (read_ack && read_data == rx_control)
transaction(0, 5'h1f, 0);
else step <= 0;
8: ready <= 1;
default: step <= 0;
endcase
end
end
endmodule
@@ -0,0 +1,481 @@
`timescale 1ns / 1ps
// Sample eight digital inputs and packetize them for the RJ2 UDP endpoint.
module zynq_pl_capture #(
parameter integer CLOCK_HZ = 125000000,
parameter integer SAMPLE_HZ = 200000000,
parameter integer BLOCK = 1024,
parameter integer BUFFER_ENABLED = 0,
parameter integer RLE_ENABLED = 0,
parameter [15:0] PORT = 16'd37575
)(
input wire clk,
input wire reset,
input wire sample_clk,
input wire sample_reset,
input wire [7:0] digital_inputs,
input wire rx_header_valid,
input wire [31:0] rx_ip,
input wire [15:0] rx_port,
input wire [15:0] rx_destination,
input wire [15:0] rx_length,
input wire [7:0] rx_data,
input wire rx_valid,
input wire rx_last,
input wire rx_error,
output wire tx_header_valid,
input wire tx_header_ready,
output reg [31:0] tx_ip,
output reg [15:0] tx_port,
output wire [15:0] tx_length,
output wire [7:0] tx_data,
output wire tx_valid,
input wire tx_ready,
output wire tx_last,
output reg preview_active,
output reg preview_valid,
output reg [7:0] preview_sample
);
localparam [31:0] ZLP1 = 32'h31504c5a, ZLP2 = 32'h32504c5a;
localparam [31:0] ZLB1 = 32'h31424c5a;
localparam [31:0] ZLR1 = 32'h31524c5a;
localparam [31:0] ZLT1 = 32'h31544c5a;
localparam integer FIFO_DEPTH = 8192;
localparam [1:0] TX_IDLE = 0, TX_HEADER = 1, TX_PAYLOAD = 2;
(* ASYNC_REG = "TRUE" *) reg [7:0] input_meta, input_sync;
always @(posedge sample_clk) begin
input_meta <= digital_inputs;
input_sync <= input_meta;
end
reg [223:0] request;
reg [4:0] rx_index;
reg rx_accept, rx_extended;
reg [31:0] request_ip;
reg [15:0] request_port;
reg command_valid;
always @(posedge clk) begin
command_valid <= 0;
if (reset) begin
request <= 0;
rx_index <= 0;
rx_accept <= 0;
rx_extended <= 0;
request_ip <= 0;
request_port <= 0;
end else begin
if (rx_header_valid) begin
rx_index <= 0;
rx_extended <= rx_length == 36;
rx_accept <= rx_destination == PORT && (rx_length == 24 || (BUFFER_ENABLED && rx_length == 36));
request_ip <= rx_ip;
request_port <= rx_port;
end
if (rx_valid && rx_accept) begin
request[rx_index*8 +: 8] <= rx_data;
rx_index <= rx_index + 1'b1;
if (rx_last || rx_index == (rx_extended ? 27 : 15)) begin
rx_accept <= 0;
command_valid <= rx_last && rx_index == (rx_extended ? 27 : 15) && !rx_error;
end
end
end
end
wire [31:0] command = request[63:32];
wire [31:0] nonce = request[95:64];
wire [31:0] request_magic = request[31:0];
wire advanced_request = BUFFER_ENABLED && request_magic == ZLT1;
wire buffer_request = BUFFER_ENABLED && (request_magic == ZLB1 || advanced_request);
wire rle_request = RLE_ENABLED && request_magic == ZLR1;
wire request_valid = command_valid && nonce != 0 &&
((buffer_request || rle_request) ? rx_extended == (command == 1 || (advanced_request && command == 4)) :
!rx_extended && (request_magic == ZLP1 || request_magic == ZLP2));
wire [7:0] channel_mask = request_magic == ZLP2 || rle_request ? request[119:112] : 8'hff;
wire [15:0] request_divider = request[111:96];
wire [3:0] channel_count = channel_mask[0] + channel_mask[1] + channel_mask[2] + channel_mask[3] +
channel_mask[4] + channel_mask[5] + channel_mask[6] + channel_mask[7];
wire packed_request = channel_count <= 4;
wire [16:0] sample_divider = request_magic == ZLP2 || buffer_request || rle_request ?
{1'b0, request_divider} : {request_divider, 1'b0};
wire valid_stream_start = request_divider != 0 && channel_mask != 0 && request[127:120] == 0 &&
(request_magic == ZLP2 || request[119:112] == 0) &&
(packed_request || sample_divider >= 2);
wire [31:0] buffer_count = request[159:128];
wire [15:0] buffer_last_address = buffer_count[15:0] - 1'b1;
wire [15:0] buffer_pretrigger = request[191:176];
wire [3:0] advanced_last = request[171:168];
wire advanced_serial = request[160];
wire [3:0] config_index = request[99:96];
reg [31:0] config_nonce, config_ip;
reg [15:0] config_port, config_seen;
wire config_owner = nonce == config_nonce && request_ip == config_ip && request_port == config_port;
wire [16:0] config_required = (17'd2 << advanced_last) - 1'b1;
wire valid_advanced_start = config_owner &&
(config_seen & config_required[15:0]) == config_required[15:0] &&
(!advanced_serial || advanced_last == 3) && buffer_pretrigger < buffer_count &&
request[223:192] == 0 && request[175:172] == 0 && request[167:161] == 0;
wire valid_buffer_start = request_divider != 0 && request[127:112] == 0 &&
buffer_count != 0 && buffer_count <= 65536 && (buffer_count & (BLOCK-1)) == 0 &&
(advanced_request ? valid_advanced_start : request[223:200] == 0);
wire valid_rle_start = request_divider == 1 && channel_mask != 0 &&
request[159:120] == 0 && request[223:200] == 0;
wire valid_start = buffer_request ? valid_buffer_start :
rle_request ? valid_rle_start : valid_stream_start;
function [11:0] selected_channels(input [7:0] mask);
integer channel;
reg [2:0] lane;
reg [11:0] indices;
begin
lane = 0;
indices = 0;
for (channel = 0; channel < 8; channel = channel + 1) begin
if (mask[channel] && lane < 4) begin
indices[3*lane +: 3] = channel[2:0];
lane = lane + 1'b1;
end
end
selected_channels = indices;
end
endfunction
reg active;
reg [31:0] owner_nonce, owner_ip, owner_magic;
reg [15:0] owner_port;
wire from_owner = request_ip == owner_ip && request_port == owner_port &&
nonce == owner_nonce && request_magic == owner_magic;
reg [111:0] sample_command_tx;
reg command_send, stopping;
wire command_received, sample_command_valid;
wire [111:0] sample_command;
wire command_idle = !command_send && !command_received;
wire config_command = sample_command_valid && sample_command[111];
wire start_command = sample_command_valid && !sample_command[111];
// Send a complete divider across the boundary. Zero commands STOP.
xpm_cdc_handshake #(
.DEST_EXT_HSK(0), .DEST_SYNC_FF(3), .SRC_SYNC_FF(3),
.INIT_SYNC_FF(1), .WIDTH(112)
) sample_control (
.src_clk(clk), .src_in(sample_command_tx), .src_send(command_send),
.src_rcv(command_received), .dest_clk(sample_clk),
.dest_out(sample_command), .dest_req(sample_command_valid), .dest_ack(1'b0)
);
(* ASYNC_REG = "TRUE" *) reg [2:0] reset_in_sample = 3'b111;
always @(posedge sample_clk)
reset_in_sample <= {reset_in_sample[1:0], reset};
wire fifo_reset = sample_reset || reset_in_sample[2];
reg sampling, sample_overflow;
reg [16:0] divider, sample_timer;
reg packed_mode, half_byte, rle_mode;
reg [11:0] channel_indices;
reg [3:0] first_sample;
wire [3:0] selected_bits = {
input_sync[channel_indices[11:9]], input_sync[channel_indices[8:6]],
input_sync[channel_indices[5:3]], input_sync[channel_indices[2:0]]
};
(* ASYNC_REG = "TRUE" *) reg [1:0] overflow_in_net;
reg [31:0] lease, packet_seq;
reg reply_pending;
reg [31:0] reply_ip, reply_nonce, reply_type, reply_value, reply_magic;
reg [15:0] reply_port;
reg [1:0] tx_state;
reg [127:0] tx_prefix;
reg [10:0] tx_index;
reg tx_samples;
wire sending_samples = tx_state != TX_IDLE && tx_samples;
wire fifo_full, fifo_empty, fifo_read_busy, fifo_write_busy;
wire [7:0] fifo_data;
wire [13:0] fifo_count;
wire sample_due = sampling && !sample_overflow && sample_timer == 0;
wire sample_valid = sample_due && !rle_mode && !fifo_full && !fifo_write_busy;
wire buffer_active, buffer_stored, buffer_valid;
wire rle_stored, rle_valid, rle_overflow;
wire [7:0] buffer_data, rle_data;
wire buffer_ready = !fifo_full && !fifo_write_busy;
wire fifo_write = (sample_valid && (!packed_mode || half_byte)) ||
((buffer_valid || rle_valid) && buffer_ready);
wire fifo_read = !fifo_empty && !fifo_read_busy &&
((tx_valid && tx_ready && tx_index >= 16) || (!active && !sending_samples));
always @(posedge sample_clk) begin
if (fifo_reset) begin
sampling <= 0;
sample_overflow <= 0;
divider <= 0;
sample_timer <= 0;
packed_mode <= 0;
rle_mode <= 0;
half_byte <= 0;
channel_indices <= 0;
first_sample <= 0;
end else if (start_command) begin
sampling <= sample_command[16:0] != 0 && !sample_command[86];
sample_overflow <= 0;
divider <= sample_command[16:0] - 1'b1;
packed_mode <= sample_command[29];
rle_mode <= RLE_ENABLED && sample_command[85] && !sample_command[86];
channel_indices <= sample_command[28:17];
half_byte <= 0;
sample_timer <= 0;
end else if (sampling && !sample_overflow) begin
if (sample_due) begin
sample_timer <= divider;
if ((fifo_full && !rle_mode) || (rle_overflow && rle_mode))
sample_overflow <= 1;
if (sample_valid && packed_mode) begin
half_byte <= !half_byte;
first_sample <= selected_bits;
end
end else begin
sample_timer <= sample_timer - 1'b1;
end
end
end
always @(posedge clk)
if (reset)
overflow_in_net <= 0;
else
overflow_in_net <= {overflow_in_net[0], sample_overflow};
// Register the preview handoff so the 200 MHz trigger and display logic
// have separate timing paths. Keep valid and its sample aligned.
always @(posedge sample_clk) begin
preview_active <= !fifo_reset && (sampling || buffer_active);
preview_valid <= !fifo_reset && (sample_valid || buffer_stored || rle_stored);
preview_sample <= input_sync;
end
generate if (BUFFER_ENABLED) begin : snapshot
wire buffer_arm = start_command && sample_command[86];
wire trigger_tick, advanced_hit;
reg advanced_capture;
la_advanced_trigger advanced (
.clk(sample_clk), .reset(fifo_reset), .arm(buffer_arm && sample_command[110]),
.tick(trigger_tick && advanced_capture), .sample(input_sync),
.serial_mode(sample_command[93]), .last_stage(sample_command[92:89]),
.config_write(config_command), .config_stage(sample_command[83:80]),
.config_flags(sample_command[87:84]), .config_a(sample_command[23:0]),
.config_b(sample_command[47:24]), .config_count(sample_command[79:48]), .hit(advanced_hit)
);
always @(posedge sample_clk)
if (fifo_reset)
advanced_capture <= 0;
else if (start_command)
advanced_capture <= sample_command[110];
la_trigger_buffer #(.EXTERNAL_TRIGGER(1)) memory (
.clk(sample_clk), .reset(fifo_reset),
.arm(buffer_arm), .stop(start_command && !sample_command[86]),
.sample(input_sync), .divider(sample_command[16:0]),
.last_address(sample_command[85:70]),
.pretrigger(sample_command[109:94]), .use_external(sample_command[110]),
.external_hit(advanced_hit), .trigger_tick(trigger_tick),
.level_mask(sample_command[37:30]), .level_value(sample_command[45:38]),
.edge_mask(sample_command[53:46]), .edge_value(sample_command[61:54]),
.any_edge_mask(sample_command[69:62]), .capturing(buffer_active),
.stored(buffer_stored), .data_valid(buffer_valid), .data(buffer_data), .ready(buffer_ready)
);
end else begin : no_snapshot
assign buffer_active = 0;
assign buffer_stored = 0;
assign buffer_valid = 0;
assign buffer_data = 0;
end endgenerate
generate if (RLE_ENABLED) begin : compressed
la_run_encoder encoder (
.clk(sample_clk), .reset(fifo_reset),
.arm(start_command && !sample_command[86] && sample_command[85]),
.stop(start_command && (sample_command[86] || !sample_command[85])),
.tick(sample_due && rle_mode), .sample(input_sync), .channel_mask(sample_command[77:70]),
.level_mask(sample_command[37:30]), .level_value(sample_command[45:38]),
.edge_mask(sample_command[53:46]), .edge_value(sample_command[61:54]),
.any_edge_mask(sample_command[69:62]), .stored(rle_stored),
.overflow(rle_overflow), .valid(rle_valid), .data(rle_data), .ready(buffer_ready)
);
end else begin : no_compressed
assign rle_stored = 0;
assign rle_valid = 0;
assign rle_overflow = 0;
assign rle_data = 0;
end endgenerate
xpm_fifo_async #(
.FIFO_MEMORY_TYPE("block"), .FIFO_WRITE_DEPTH(FIFO_DEPTH),
.WRITE_DATA_WIDTH(8), .READ_DATA_WIDTH(8), .CDC_SYNC_STAGES(2),
.READ_MODE("fwft"), .FIFO_READ_LATENCY(0),
.RD_DATA_COUNT_WIDTH(14), .USE_ADV_FEATURES("0400")
) sample_fifo (
.wr_clk(sample_clk), .rd_clk(clk), .rst(fifo_reset), .sleep(1'b0),
.din(buffer_valid ? buffer_data : rle_valid ? rle_data :
packed_mode ? {selected_bits, first_sample} : input_sync),
.wr_en(fifo_write), .full(fifo_full), .wr_rst_busy(fifo_write_busy),
.dout(fifo_data), .rd_en(fifo_read), .empty(fifo_empty), .rd_rst_busy(fifo_read_busy),
.rd_data_count(fifo_count), .injectdbiterr(1'b0), .injectsbiterr(1'b0)
);
assign tx_header_valid = tx_state == TX_HEADER;
assign tx_length = tx_samples ? BLOCK + 24 : 24;
assign tx_valid = tx_state == TX_PAYLOAD;
assign tx_data = tx_index < 16 ? tx_prefix[tx_index[3:0]*8 +: 8] : fifo_data;
assign tx_last = tx_index == (tx_samples ? BLOCK + 15 : 15);
task queue_reply;
input [31:0] ip, id, kind, value, magic;
input [15:0] port;
begin
reply_pending <= 1;
reply_ip <= ip;
reply_port <= port;
reply_nonce <= id;
reply_type <= kind;
reply_value <= value;
reply_magic <= magic;
end
endtask
always @(posedge clk) begin
if (reset) begin
active <= 0;
owner_nonce <= 0;
owner_ip <= 0;
owner_port <= 0;
owner_magic <= 0;
config_nonce <= 0;
config_ip <= 0;
config_port <= 0;
config_seen <= 0;
sample_command_tx <= 0;
command_send <= 0;
stopping <= 0;
lease <= 0;
packet_seq <= 0;
reply_pending <= 0;
reply_ip <= 0;
reply_port <= 0;
reply_nonce <= 0;
reply_type <= 0;
reply_value <= 0;
reply_magic <= 0;
tx_state <= TX_IDLE;
tx_ip <= 0;
tx_port <= 0;
tx_prefix <= 0;
tx_index <= 0;
tx_samples <= 0;
end else begin
case (tx_state)
TX_IDLE: begin
tx_index <= 0;
if (reply_pending) begin
reply_pending <= 0;
tx_ip <= reply_ip;
tx_port <= reply_port;
tx_prefix <= {reply_value, reply_nonce, reply_type, reply_magic};
tx_samples <= 0;
tx_state <= TX_HEADER;
end else if (active && fifo_count >= BLOCK) begin
tx_ip <= owner_ip;
tx_port <= owner_port;
tx_prefix <= {packet_seq, owner_nonce, 32'd1, owner_magic};
tx_samples <= 1;
tx_state <= TX_HEADER;
packet_seq <= packet_seq + 1;
end
end
TX_HEADER: if (tx_header_ready) tx_state <= TX_PAYLOAD;
TX_PAYLOAD: if (tx_ready) begin
if (tx_last)
tx_state <= TX_IDLE;
else
tx_index <= tx_index + 1'b1;
end
default: tx_state <= TX_IDLE;
endcase
if (command_send && command_received)
command_send <= 0;
if (stopping && command_idle) begin
sample_command_tx <= 0;
command_send <= 1;
stopping <= 0;
end
if (active) begin
if (lease == 0) begin
active <= 0;
stopping <= 1;
end else
lease <= lease - 1'b1;
end
if (request_valid) begin
case (command)
0: if (!reply_pending)
queue_reply(request_ip, nonce, 0,
request_magic == ZLP1 ? SAMPLE_HZ/2 : SAMPLE_HZ, request_magic, request_port);
1: if (active && from_owner) begin
lease <= 2*CLOCK_HZ;
end else if (!valid_start) begin
if (!reply_pending)
queue_reply(request_ip, nonce, 2, 1, request_magic, request_port);
end else if (!active && !stopping && command_idle && !sending_samples && fifo_empty && !fifo_read_busy) begin
active <= 1;
owner_nonce <= nonce;
owner_ip <= request_ip;
owner_port <= request_port;
owner_magic <= request_magic;
if (advanced_request)
sample_command_tx <= {2'b01, buffer_pretrigger, advanced_serial,
advanced_last, 2'd0, 1'b1, buffer_last_address, 53'd0, sample_divider};
else if (buffer_request)
sample_command_tx <= {1'b1, buffer_last_address, request[199:160], 13'd0, sample_divider};
else if (rle_request)
sample_command_tx <= {2'b01, 7'd0, channel_mask, request[199:160], 13'd0, sample_divider};
else
sample_command_tx <= {packed_request, selected_channels(channel_mask), sample_divider};
command_send <= 1;
packet_seq <= 0;
lease <= 2*CLOCK_HZ;
end else if (!reply_pending) begin
queue_reply(request_ip, nonce, 2, 2, request_magic, request_port);
end
2: if (active && from_owner) begin
active <= 0;
stopping <= 1;
end
3: if (active && from_owner) lease <= 2*CLOCK_HZ;
4: if (advanced_request && !reply_pending) begin
if (!active && !stopping && command_idle &&
(config_owner || config_index == 0) &&
request[127:104] == 0 && request[159:152] == 0 && request[191:184] == 0) begin
config_nonce <= nonce;
config_ip <= request_ip;
config_port <= request_port;
config_seen <= (config_owner ? config_seen : 16'd0) | (16'd1 << config_index);
sample_command_tx <= {1'b1, 23'd0, request[103:96],
request[223:192], request[183:160], request[151:128]};
command_send <= 1;
queue_reply(request_ip, nonce, 3, {28'd0, config_index}, request_magic, request_port);
end else begin
queue_reply(request_ip, nonce, 2, 2, request_magic, request_port);
end
end
endcase
end
if (active && overflow_in_net[1]) begin
active <= 0;
stopping <= 1;
queue_reply(owner_ip, owner_nonce, 2, 3, owner_magic, owner_port);
end
end
end
endmodule
@@ -0,0 +1,409 @@
/*
Copyright (c) 2014-2018 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Language: Verilog 2001
`timescale 1ns / 1ps
`default_nettype none
module zynq_pl_ethernet #(
parameter [31:0] LOCAL_IP = 32'hc0a84d02,
parameter [47:0] LOCAL_MAC = 48'h02_5a_4c_70_20_02,
parameter integer VFD_CLOCK_HZ = 175000000
)(
input wire clk,
input wire sample_clk,
input wire sample_reset,
input wire delay_ref_clk,
input wire reset,
input wire [7:0] digital_inputs,
input wire phy_rx_clk,
input wire [3:0] phy_rxd,
input wire phy_rx_ctl,
output wire phy_tx_clk,
output wire [3:0] phy_txd,
output wire phy_tx_ctl,
output wire phy_reset_n,
(* X_INTERFACE_INFO = "xilinx.com:interface:mdio:1.0 mdio MDC", X_INTERFACE_MODE = "Master" *)
output wire phy_mdc,
(* X_INTERFACE_INFO = "xilinx.com:interface:mdio:1.0 mdio MDIO_I" *)
input wire phy_mdio_i,
(* X_INTERFACE_INFO = "xilinx.com:interface:mdio:1.0 mdio MDIO_O" *)
output wire phy_mdio_o,
(* X_INTERFACE_INFO = "xilinx.com:interface:mdio:1.0 mdio MDIO_T" *)
output wire phy_mdio_t,
output wire preview_active,
output wire preview_valid,
output wire [7:0] preview_sample,
input wire vfd_clk, vfd_resetn,
output wire vfd_command_valid,
output wire [194:0] vfd_command,
input wire vfd_reply_valid,
input wire [68:0] vfd_reply
);
wire phy_ready;
rtl8211f_init phy_config (
.clk(clk), .reset(reset), .phy_reset_n(phy_reset_n), .mdc(phy_mdc),
.mdio_i(phy_mdio_i), .mdio_o(phy_mdio_o), .mdio_t(phy_mdio_t), .ready(phy_ready)
);
// IDELAY compensates for the IBUF/BUFIO clock insertion delay at the IDDRs.
wire delay_ready;
(* IODELAY_GROUP = "RJ2_RGMII" *)
IDELAYCTRL rx_delay_control (.REFCLK(delay_ref_clk), .RST(reset), .RDY(delay_ready));
(* ASYNC_REG = "TRUE" *) reg [1:0] delay_ready_sync;
always @(posedge clk)
if (reset) delay_ready_sync <= 0;
else delay_ready_sync <= {delay_ready_sync[0], delay_ready};
wire rst = reset || !phy_ready || !delay_ready_sync[1];
wire [4:0] rx_pins = {phy_rx_ctl, phy_rxd};
wire [4:0] rx_delayed;
genvar lane;
generate for (lane = 0; lane < 5; lane = lane + 1) begin : rj2_input
(* IODELAY_GROUP = "RJ2_RGMII" *)
IDELAYE2 #(
.DELAY_SRC("IDATAIN"), .IDELAY_TYPE("FIXED"), .IDELAY_VALUE(12),
.REFCLK_FREQUENCY(200.0), .HIGH_PERFORMANCE_MODE("TRUE"), .SIGNAL_PATTERN("DATA")
) delay_data (
.IDATAIN(rx_pins[lane]), .DATAOUT(rx_delayed[lane]),
.C(1'b0), .CE(1'b0), .CINVCTRL(1'b0), .CNTVALUEIN(5'd0), .CNTVALUEOUT(),
.DATAIN(1'b0), .INC(1'b0), .LD(1'b0), .LDPIPEEN(1'b0), .REGRST(1'b0)
);
end endgenerate
wire [7:0] rx_axis_tdata;
wire rx_axis_tvalid;
wire rx_axis_tready;
wire rx_axis_tlast;
wire rx_axis_tuser;
wire [7:0] tx_axis_tdata;
wire tx_axis_tvalid;
wire tx_axis_tready;
wire tx_axis_tlast;
wire tx_axis_tuser;
wire rx_eth_hdr_ready;
wire rx_eth_hdr_valid;
wire [47:0] rx_eth_dest_mac;
wire [47:0] rx_eth_src_mac;
wire [15:0] rx_eth_type;
wire [7:0] rx_eth_payload_axis_tdata;
wire rx_eth_payload_axis_tvalid;
wire rx_eth_payload_axis_tready;
wire rx_eth_payload_axis_tlast;
wire rx_eth_payload_axis_tuser;
wire tx_eth_hdr_ready;
wire tx_eth_hdr_valid;
wire [47:0] tx_eth_dest_mac;
wire [47:0] tx_eth_src_mac;
wire [15:0] tx_eth_type;
wire [7:0] tx_eth_payload_axis_tdata;
wire tx_eth_payload_axis_tvalid;
wire tx_eth_payload_axis_tready;
wire tx_eth_payload_axis_tlast;
wire tx_eth_payload_axis_tuser;
wire rx_udp_hdr_valid;
wire [31:0] rx_udp_ip_source_ip, rx_udp_ip_dest_ip;
wire [15:0] rx_udp_source_port, rx_udp_dest_port, rx_udp_length;
wire [7:0] rx_udp_payload_axis_tdata;
wire rx_udp_payload_axis_tvalid, rx_udp_payload_axis_tlast, rx_udp_payload_axis_tuser;
wire tx_udp_hdr_valid, tx_udp_hdr_ready;
wire [31:0] tx_udp_ip_dest_ip;
wire [15:0] tx_udp_dest_port, tx_udp_length;
wire [7:0] tx_udp_payload_axis_tdata;
wire tx_udp_payload_axis_tvalid, tx_udp_payload_axis_tready, tx_udp_payload_axis_tlast;
wire [15:0] tx_udp_source_port;
wire capture_hdr, capture_hdr_ready, capture_valid, capture_ready, capture_last;
wire [31:0] capture_ip;
wire [15:0] capture_port, capture_length;
wire [7:0] capture_data;
wire vfd_hdr, vfd_hdr_ready, vfd_valid, vfd_ready, vfd_last;
wire [31:0] vfd_ip;
wire [15:0] vfd_port;
wire [7:0] vfd_data;
localparam [1:0] TX_IDLE = 0, TX_HEADER = 1, TX_PAYLOAD = 2;
reg [1:0] tx_state;
reg tx_vfd;
// Control replies can go between sample packets. A payload keeps its owner
// until the UDP core accepts its last byte, including during backpressure.
assign tx_udp_hdr_valid = tx_state == TX_HEADER && (tx_vfd ? vfd_hdr : capture_hdr);
assign vfd_hdr_ready = tx_state == TX_HEADER && tx_vfd && tx_udp_hdr_ready;
assign capture_hdr_ready = tx_state == TX_HEADER && !tx_vfd && tx_udp_hdr_ready;
assign tx_udp_ip_dest_ip = tx_vfd ? vfd_ip : capture_ip;
assign tx_udp_source_port = tx_vfd ? 16'd37573 : 16'd37575;
assign tx_udp_dest_port = tx_vfd ? vfd_port : capture_port;
assign tx_udp_length = tx_vfd ? 16'd48 : capture_length;
assign tx_udp_payload_axis_tdata = tx_vfd ? vfd_data : capture_data;
assign tx_udp_payload_axis_tlast = tx_vfd ? vfd_last : capture_last;
assign tx_udp_payload_axis_tvalid = tx_state == TX_PAYLOAD && (tx_vfd ? vfd_valid : capture_valid);
assign vfd_ready = tx_state == TX_PAYLOAD && tx_vfd && tx_udp_payload_axis_tready;
assign capture_ready = tx_state == TX_PAYLOAD && !tx_vfd && tx_udp_payload_axis_tready;
always @(posedge clk) begin
if (rst) begin
tx_state <= TX_IDLE;
tx_vfd <= 0;
end else if (tx_state == TX_IDLE && (vfd_hdr || capture_hdr)) begin
tx_state <= TX_HEADER;
tx_vfd <= vfd_hdr;
end else if (tx_udp_hdr_valid && tx_udp_hdr_ready) begin
tx_state <= TX_PAYLOAD;
end else if (tx_udp_payload_axis_tvalid && tx_udp_payload_axis_tready && tx_udp_payload_axis_tlast) begin
tx_state <= TX_IDLE;
end
end
zynq_pl_capture #(.BUFFER_ENABLED(1), .RLE_ENABLED(1)) capture (
.clk(clk), .reset(rst), .sample_clk(sample_clk), .sample_reset(sample_reset),
.digital_inputs(digital_inputs),
.rx_header_valid(rx_udp_hdr_valid), .rx_ip(rx_udp_ip_source_ip),
.rx_port(rx_udp_source_port), .rx_destination(rx_udp_dest_port), .rx_length(rx_udp_length),
.rx_data(rx_udp_payload_axis_tdata), .rx_valid(rx_udp_payload_axis_tvalid),
.rx_last(rx_udp_payload_axis_tlast), .rx_error(rx_udp_payload_axis_tuser),
.tx_header_valid(capture_hdr), .tx_header_ready(capture_hdr_ready),
.tx_ip(capture_ip), .tx_port(capture_port), .tx_length(capture_length),
.tx_data(capture_data), .tx_valid(capture_valid),
.tx_ready(capture_ready), .tx_last(capture_last),
.preview_active(preview_active), .preview_valid(preview_valid), .preview_sample(preview_sample)
);
zynq_pl_vfd #(.VFD_CLOCK_HZ(VFD_CLOCK_HZ)) pwm_control (
.clk(clk), .reset(rst),
.rx_header_valid(rx_udp_hdr_valid && rx_udp_ip_dest_ip == LOCAL_IP),
.rx_ip(rx_udp_ip_source_ip), .rx_port(rx_udp_source_port),
.rx_destination(rx_udp_dest_port), .rx_length(rx_udp_length),
.rx_data(rx_udp_payload_axis_tdata), .rx_valid(rx_udp_payload_axis_tvalid),
.rx_last(rx_udp_payload_axis_tlast), .rx_error(rx_udp_payload_axis_tuser),
.tx_header_valid(vfd_hdr), .tx_header_ready(vfd_hdr_ready),
.tx_ip(vfd_ip), .tx_port(vfd_port), .tx_data(vfd_data),
.tx_valid(vfd_valid), .tx_ready(vfd_ready), .tx_last(vfd_last),
.vfd_clk(vfd_clk), .vfd_resetn(vfd_resetn),
.vfd_command_valid(vfd_command_valid), .vfd_command(vfd_command),
.vfd_reply_valid(vfd_reply_valid), .vfd_reply(vfd_reply)
);
eth_mac_1g_rgmii_fifo #(
.TARGET("XILINX"),
.IODDR_STYLE("IODDR"),
.CLOCK_INPUT_STYLE("BUFR"),
.USE_CLK90("FALSE"),
.ENABLE_PADDING(1),
.MIN_FRAME_LENGTH(64),
.TX_FIFO_DEPTH(4096),
.TX_FRAME_FIFO(1),
.RX_FIFO_DEPTH(4096),
.RX_FRAME_FIFO(1)
)
eth_mac_inst (
.gtx_clk(clk),
.gtx_clk90(clk),
.gtx_rst(rst),
.logic_clk(clk),
.logic_rst(rst),
.tx_axis_tdata(tx_axis_tdata),
.tx_axis_tvalid(tx_axis_tvalid),
.tx_axis_tready(tx_axis_tready),
.tx_axis_tlast(tx_axis_tlast),
.tx_axis_tuser(tx_axis_tuser),
.rx_axis_tdata(rx_axis_tdata),
.rx_axis_tvalid(rx_axis_tvalid),
.rx_axis_tready(rx_axis_tready),
.rx_axis_tlast(rx_axis_tlast),
.rx_axis_tuser(rx_axis_tuser),
.rgmii_rx_clk(phy_rx_clk),
.rgmii_rxd(rx_delayed[3:0]),
.rgmii_rx_ctl(rx_delayed[4]),
.rgmii_tx_clk(phy_tx_clk),
.rgmii_txd(phy_txd),
.rgmii_tx_ctl(phy_tx_ctl),
.tx_fifo_overflow(),
.tx_fifo_bad_frame(),
.tx_fifo_good_frame(),
.rx_error_bad_frame(),
.rx_error_bad_fcs(),
.rx_fifo_overflow(),
.rx_fifo_bad_frame(),
.rx_fifo_good_frame(),
.speed(),
.cfg_ifg(8'd12),
.cfg_tx_enable(1'b1),
.cfg_rx_enable(1'b1)
);
eth_axis_rx
eth_axis_rx_inst (
.clk(clk),
.rst(rst),
// AXI input
.s_axis_tdata(rx_axis_tdata),
.s_axis_tvalid(rx_axis_tvalid),
.s_axis_tready(rx_axis_tready),
.s_axis_tlast(rx_axis_tlast),
.s_axis_tuser(rx_axis_tuser),
// Ethernet frame output
.m_eth_hdr_valid(rx_eth_hdr_valid),
.m_eth_hdr_ready(rx_eth_hdr_ready),
.m_eth_dest_mac(rx_eth_dest_mac),
.m_eth_src_mac(rx_eth_src_mac),
.m_eth_type(rx_eth_type),
.m_eth_payload_axis_tdata(rx_eth_payload_axis_tdata),
.m_eth_payload_axis_tvalid(rx_eth_payload_axis_tvalid),
.m_eth_payload_axis_tready(rx_eth_payload_axis_tready),
.m_eth_payload_axis_tlast(rx_eth_payload_axis_tlast),
.m_eth_payload_axis_tuser(rx_eth_payload_axis_tuser),
// Status signals
.busy(),
.error_header_early_termination()
);
eth_axis_tx
eth_axis_tx_inst (
.clk(clk),
.rst(rst),
// Ethernet frame input
.s_eth_hdr_valid(tx_eth_hdr_valid),
.s_eth_hdr_ready(tx_eth_hdr_ready),
.s_eth_dest_mac(tx_eth_dest_mac),
.s_eth_src_mac(tx_eth_src_mac),
.s_eth_type(tx_eth_type),
.s_eth_payload_axis_tdata(tx_eth_payload_axis_tdata),
.s_eth_payload_axis_tvalid(tx_eth_payload_axis_tvalid),
.s_eth_payload_axis_tready(tx_eth_payload_axis_tready),
.s_eth_payload_axis_tlast(tx_eth_payload_axis_tlast),
.s_eth_payload_axis_tuser(tx_eth_payload_axis_tuser),
// AXI output
.m_axis_tdata(tx_axis_tdata),
.m_axis_tvalid(tx_axis_tvalid),
.m_axis_tready(tx_axis_tready),
.m_axis_tlast(tx_axis_tlast),
.m_axis_tuser(tx_axis_tuser),
// Status signals
.busy()
);
udp_complete
udp_complete_inst (
.clk(clk),
.rst(rst),
// Ethernet frame input
.s_eth_hdr_valid(rx_eth_hdr_valid),
.s_eth_hdr_ready(rx_eth_hdr_ready),
.s_eth_dest_mac(rx_eth_dest_mac),
.s_eth_src_mac(rx_eth_src_mac),
.s_eth_type(rx_eth_type),
.s_eth_payload_axis_tdata(rx_eth_payload_axis_tdata),
.s_eth_payload_axis_tvalid(rx_eth_payload_axis_tvalid),
.s_eth_payload_axis_tready(rx_eth_payload_axis_tready),
.s_eth_payload_axis_tlast(rx_eth_payload_axis_tlast),
.s_eth_payload_axis_tuser(rx_eth_payload_axis_tuser),
// Ethernet frame output
.m_eth_hdr_valid(tx_eth_hdr_valid),
.m_eth_hdr_ready(tx_eth_hdr_ready),
.m_eth_dest_mac(tx_eth_dest_mac),
.m_eth_src_mac(tx_eth_src_mac),
.m_eth_type(tx_eth_type),
.m_eth_payload_axis_tdata(tx_eth_payload_axis_tdata),
.m_eth_payload_axis_tvalid(tx_eth_payload_axis_tvalid),
.m_eth_payload_axis_tready(tx_eth_payload_axis_tready),
.m_eth_payload_axis_tlast(tx_eth_payload_axis_tlast),
.m_eth_payload_axis_tuser(tx_eth_payload_axis_tuser),
.s_ip_hdr_valid(1'b0), .s_ip_dscp(6'd0), .s_ip_ecn(2'd0), .s_ip_length(16'd0),
.s_ip_ttl(8'd0), .s_ip_protocol(8'd0), .s_ip_source_ip(32'd0), .s_ip_dest_ip(32'd0),
.s_ip_payload_axis_tdata(8'd0), .s_ip_payload_axis_tvalid(1'b0),
.s_ip_payload_axis_tlast(1'b0), .s_ip_payload_axis_tuser(1'b0),
.m_ip_hdr_ready(1'b1), .m_ip_payload_axis_tready(1'b1),
// UDP frame input
.s_udp_hdr_valid(tx_udp_hdr_valid),
.s_udp_hdr_ready(tx_udp_hdr_ready),
.s_udp_ip_dscp(6'd0),
.s_udp_ip_ecn(2'd0),
.s_udp_ip_ttl(8'd64),
.s_udp_ip_source_ip(LOCAL_IP),
.s_udp_ip_dest_ip(tx_udp_ip_dest_ip),
.s_udp_source_port(tx_udp_source_port),
.s_udp_dest_port(tx_udp_dest_port),
.s_udp_length(tx_udp_length),
.s_udp_checksum(16'd0),
.s_udp_payload_axis_tdata(tx_udp_payload_axis_tdata),
.s_udp_payload_axis_tvalid(tx_udp_payload_axis_tvalid),
.s_udp_payload_axis_tready(tx_udp_payload_axis_tready),
.s_udp_payload_axis_tlast(tx_udp_payload_axis_tlast),
.s_udp_payload_axis_tuser(1'b0),
// UDP frame output
.m_udp_hdr_valid(rx_udp_hdr_valid),
.m_udp_hdr_ready(1'b1),
.m_udp_eth_dest_mac(),
.m_udp_eth_src_mac(),
.m_udp_eth_type(),
.m_udp_ip_version(),
.m_udp_ip_ihl(),
.m_udp_ip_dscp(),
.m_udp_ip_ecn(),
.m_udp_ip_length(),
.m_udp_ip_identification(),
.m_udp_ip_flags(),
.m_udp_ip_fragment_offset(),
.m_udp_ip_ttl(),
.m_udp_ip_protocol(),
.m_udp_ip_header_checksum(),
.m_udp_ip_source_ip(rx_udp_ip_source_ip),
.m_udp_ip_dest_ip(rx_udp_ip_dest_ip),
.m_udp_source_port(rx_udp_source_port),
.m_udp_dest_port(rx_udp_dest_port),
.m_udp_length(rx_udp_length),
.m_udp_checksum(),
.m_udp_payload_axis_tdata(rx_udp_payload_axis_tdata),
.m_udp_payload_axis_tvalid(rx_udp_payload_axis_tvalid),
.m_udp_payload_axis_tready(1'b1),
.m_udp_payload_axis_tlast(rx_udp_payload_axis_tlast),
.m_udp_payload_axis_tuser(rx_udp_payload_axis_tuser),
// Status signals
.ip_rx_busy(),
.ip_tx_busy(),
.udp_rx_busy(),
.udp_tx_busy(),
.ip_rx_error_header_early_termination(),
.ip_rx_error_payload_early_termination(),
.ip_rx_error_invalid_header(),
.ip_rx_error_invalid_checksum(),
.ip_tx_error_payload_early_termination(),
.ip_tx_error_arp_failed(),
.udp_rx_error_header_early_termination(),
.udp_rx_error_payload_early_termination(),
.udp_tx_error_payload_early_termination(),
// Configuration
.local_mac(LOCAL_MAC),
.local_ip(LOCAL_IP),
// The ARP core classifies local peers by comparing against the gateway's subnet.
.gateway_ip({LOCAL_IP[31:8], 8'd1}),
.subnet_mask(32'hffffff00),
.clear_arp_cache(1'b0)
);
endmodule
`default_nettype wire
+183
View File
@@ -0,0 +1,183 @@
`timescale 1ns / 1ps
// ZVF1 test-generator commands with an 8-byte UDP session/sequence envelope.
module zynq_pl_vfd #(
parameter integer VFD_CLOCK_HZ = 175000000,
// The lease outlasts the generator's 500 ms watchdog.
parameter integer LEASE_CYCLES = 93750000
)(
input wire clk, reset,
input wire rx_header_valid,
input wire [31:0] rx_ip,
input wire [15:0] rx_port, rx_destination, rx_length,
input wire [7:0] rx_data,
input wire rx_valid, rx_last, rx_error,
output wire tx_header_valid,
input wire tx_header_ready,
output reg [31:0] tx_ip,
output reg [15:0] tx_port,
output wire [7:0] tx_data,
output wire tx_valid, tx_last,
input wire tx_ready,
input wire vfd_clk, vfd_resetn,
output reg vfd_command_valid,
output reg [194:0] vfd_command,
input wire vfd_reply_valid,
input wire [68:0] vfd_reply
);
localparam [31:0] MAGIC = 32'h3146565a;
localparam [1:0] TX_IDLE = 0, TX_HEADER = 1, TX_PAYLOAD = 2;
reg [575:0] request;
reg [6:0] rx_index;
reg rx_accept, request_valid;
reg [31:0] request_ip;
reg [15:0] request_port;
wire [31:0] token = request[31:0], seq = request[63:32];
wire [31:0] op = request[159:128];
wire request_ok = token != 0 && seq != 0 && request[95:64] == MAGIC &&
request[127:96] == 1 && op <= 4 && request[575:352] == 0;
always @(posedge clk) begin
request_valid <= 0;
if (reset) begin
request <= 0;
rx_index <= 0;
rx_accept <= 0;
request_ip <= 0;
request_port <= 0;
end else begin
if (rx_header_valid) begin
rx_index <= 0;
rx_accept <= rx_destination == 37573 && rx_length == 80;
request_ip <= rx_ip;
request_port <= rx_port;
end
if (rx_valid && rx_accept) begin
request[rx_index*8 +: 8] <= rx_data;
rx_index <= rx_index + 1'b1;
if (rx_last || rx_index == 71) begin
rx_accept <= 0;
request_valid <= rx_last && rx_index == 71 && !rx_error;
end
end
end
end
reg [194:0] command_hold;
reg command_send, waiting;
wire command_received, response_valid;
wire command_arrived;
wire [194:0] received_command;
wire [68:0] response;
reg [68:0] response_hold;
reg response_send;
wire response_received;
xpm_cdc_handshake #(
.DEST_EXT_HSK(0), .DEST_SYNC_FF(3), .SRC_SYNC_FF(3),
.INIT_SYNC_FF(1), .WIDTH(195)
) to_vfd (
.src_clk(clk), .src_in(command_hold), .src_send(command_send), .src_rcv(command_received),
.dest_clk(vfd_clk), .dest_out(received_command), .dest_req(command_arrived), .dest_ack(1'b0)
);
xpm_cdc_handshake #(
.DEST_EXT_HSK(0), .DEST_SYNC_FF(3), .SRC_SYNC_FF(3),
.INIT_SYNC_FF(1), .WIDTH(69)
) from_vfd (
.src_clk(vfd_clk), .src_in(response_hold), .src_send(response_send), .src_rcv(response_received),
.dest_clk(clk), .dest_out(response), .dest_req(response_valid), .dest_ack(1'b0)
);
always @(posedge vfd_clk) begin
// Keep the handshake registers separate from the generator's decode.
vfd_command <= received_command;
vfd_command_valid <= vfd_resetn && command_arrived;
if (!vfd_resetn) begin
response_hold <= 0;
response_send <= 0;
end else if (vfd_reply_valid) begin
response_hold <= vfd_reply;
response_send <= 1;
end else if (response_received) begin
response_send <= 0;
end
end
reg [31:0] owner_ip, owner_token, last_seq, lease;
reg [15:0] owner_port;
reg [2:0] last_op;
wire owner = owner_token == token && owner_ip == request_ip && owner_port == request_port;
reg [255:0] cached_response;
reg [319:0] tx_payload;
reg [5:0] tx_index;
reg [1:0] tx_state;
wire [255:0] body = {32'd100000, 29'd0, response[66:64], response[63:0],
VFD_CLOCK_HZ[31:0], 30'd0, response[68:67], 32'd1, MAGIC};
assign tx_header_valid = tx_state == TX_HEADER;
assign tx_valid = tx_state == TX_PAYLOAD;
assign tx_data = tx_payload[tx_index*8 +: 8];
assign tx_last = tx_index == 39;
always @(posedge clk) begin
if (reset) begin
command_hold <= 0;
command_send <= 0;
waiting <= 0;
owner_ip <= 0;
owner_port <= 0;
owner_token <= 0;
last_seq <= 0;
last_op <= 0;
lease <= 0;
cached_response <= 0;
tx_ip <= 0;
tx_port <= 0;
tx_state <= TX_IDLE;
tx_payload <= 0;
tx_index <= 0;
end else begin
if (lease != 0) lease <= lease - 1'b1;
if (command_received) command_send <= 0;
if (tx_header_valid && tx_header_ready) tx_state <= TX_PAYLOAD;
if (tx_valid && tx_ready) begin
tx_index <= tx_index + 1'b1;
if (tx_last) tx_state <= TX_IDLE;
end
if (response_valid && waiting) begin
waiting <= 0;
cached_response <= body;
tx_payload <= {body,last_seq,owner_token};
tx_ip <= owner_ip;
tx_port <= owner_port;
tx_state <= TX_HEADER;
tx_index <= 0;
if (last_op == 4) lease <= 0;
end else if (request_valid && request_ok && tx_state == TX_IDLE &&
!waiting && !command_send && !command_received) begin
if (owner && seq == last_seq) begin
// Retry returns the original response without repeating START or OFF.
tx_payload <= {cached_response,seq,token};
tx_ip <= request_ip;
tx_port <= request_port;
tx_state <= TX_HEADER;
tx_index <= 0;
end else if ((owner && seq > last_seq && (lease != 0 || op == 0)) ||
(!owner && lease == 0 && op == 0)) begin
owner_ip <= request_ip;
owner_port <= request_port;
owner_token <= token;
last_seq <= seq;
last_op <= op[2:0];
lease <= LEASE_CYCLES;
command_hold <= {op[2:0],request[351:160]};
command_send <= 1;
waiting <= 1;
end else if (!owner && op == 0) begin
tx_payload <= {32'd100000,96'd0,VFD_CLOCK_HZ[31:0],32'd2,32'd1,MAGIC,seq,token};
tx_ip <= request_ip;
tx_port <= request_port;
tx_state <= TX_HEADER;
tx_index <= 0;
end
end
end
end
endmodule
@@ -0,0 +1,144 @@
"""Check the FPGA RJ2 UDP endpoint and measure ordered sample throughput."""
import argparse
import secrets
import socket
import struct
import time
HEADER = struct.Struct('<4I')
MAGIC = 0x31504C5A
PACKED_MAGIC = 0x32504C5A
PORT = 37575
BLOCK = 1024
REORDER_BLOCKS = 4096
def capture(address, divider, count, channels=0xff):
nonce = secrets.randbits(32) or 1
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16 * 1024 * 1024)
sock.connect((address, PORT))
sock.settimeout(.35)
protocol = PACKED_MAGIC
def send(operation, value=0):
sock.send(HEADER.pack(protocol, operation, nonce, value))
clock = 0
for protocol in (PACKED_MAGIC, MAGIC):
for _ in range(4):
send(0)
try:
data = sock.recv(64)
except TimeoutError:
continue
if len(data) == HEADER.size:
magic, kind, session, value = HEADER.unpack(data)
if (magic, kind, session) == (protocol, 0, nonce):
clock = value
break
if clock:
break
valid_clocks = (200_000_000,) if protocol == PACKED_MAGIC else (100_000_000, 125_000_000)
if clock not in valid_clocks:
raise RuntimeError(f'RJ2 did not report a supported sample clock: {clock}')
packed = protocol == PACKED_MAGIC and channels.bit_count() <= 4
if protocol == MAGIC and channels != 0xff:
raise ValueError('Channel selection needs ZLP2 firmware')
if divider == 1 and (clock == 125_000_000 or (protocol == PACKED_MAGIC and not packed)):
raise ValueError('This channel configuration requires a divider of at least 2')
samples_per_byte = 2 if packed else 1
start_value = divider | (channels << 16 if protocol == PACKED_MAGIC else 0)
samples = bytearray()
sequence = 0
pending = {}
deadline = time.monotonic() + 4
heartbeat = 0
started = None
try:
while len(samples) * samples_per_byte < count:
now = time.monotonic()
if now > deadline:
raise TimeoutError('RJ2 stopped sending samples')
if now >= heartbeat:
if started is None:
send(1, start_value)
else:
send(3)
heartbeat = now + .2
try:
data = sock.recv(BLOCK + HEADER.size + 1)
except TimeoutError:
continue
if len(data) < HEADER.size:
raise RuntimeError('Truncated RJ2 header')
magic, kind, session, value = HEADER.unpack_from(data)
if (magic, session) != (protocol, nonce):
continue
if kind == 2:
if value == 2 and started is None:
continue
raise RuntimeError(f'RJ2 capture error {value} at packet {sequence}')
if kind != 1 or len(data) != BLOCK + HEADER.size:
raise RuntimeError(f'Invalid sample packet: type {kind}, length {len(data)}')
ahead = (value - sequence) & 0xffffffff
if ahead >= 0x80000000 or value in pending:
continue
if ahead >= REORDER_BLOCKS:
raise RuntimeError(f'Gap exceeded the reorder window: expected {sequence}, got {value}')
now = time.monotonic()
if started is None:
started = now
pending[value] = data[HEADER.size:]
while sequence in pending and len(samples) * samples_per_byte < count:
samples.extend(pending.pop(sequence))
sequence = (sequence + 1) & 0xffffffff
deadline = now + 2
elapsed = time.monotonic() - started
finally:
send(2)
changed = 0
if packed:
first = samples[0] & 15
dense = 0
for pair in samples:
dense |= ((pair & 15) ^ first) | ((pair >> 4) ^ first)
lane = 0
for channel in range(8):
if channels & (1 << channel):
changed |= ((dense >> lane) & 1) << channel
lane += 1
else:
first = samples[0]
for sample in samples:
changed |= sample ^ first
changed &= channels
expected = clock / divider
received = max(0, len(samples) - BLOCK) * samples_per_byte / max(elapsed, 1e-6)
return len(samples) * samples_per_byte, expected, received, changed
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('address')
parser.add_argument('--divider', type=int, default=10)
parser.add_argument('--mask', type=lambda value: int(value, 0), default=0xff)
parser.add_argument('--samples', type=int, default=4 * 1024 * 1024)
args = parser.parse_args()
if not 1 <= args.divider <= 65535 or not BLOCK <= args.samples <= 64 * 1024 * 1024:
parser.error('divider 1..65535; samples 1024..67108864')
if not 1 <= args.mask <= 0xff:
parser.error('mask must select at least one channel in 0x01..0xff')
count, expected, received, changed = capture(args.address, args.divider, args.samples, args.mask)
print(f'{count:,} samples; FPGA {expected/1e6:.3f} MS/s; '
f'received {received/1e6:.3f} MS/s; toggling channels mask 0x{changed:02x}')
if __name__ == '__main__':
main()
@@ -0,0 +1,81 @@
`timescale 1ns / 1ps
module tb_rtl8211f_init;
reg clk = 0;
reg reset = 1;
always #4 clk = ~clk;
tri1 mdio;
wire mdc, ready, mdio_o, mdio_t;
assign mdio = mdio_t ? 1'bz : mdio_o;
rtl8211f_init dut (
.clk(clk), .reset(reset), .phy_reset_n(), .mdc(mdc),
.mdio_i(mdio), .mdio_o(mdio_o), .mdio_t(mdio_t), .ready(ready)
);
reg [63:0] frame = 0;
reg [15:0] page = 0;
reg [15:0] tx_control = 16'h0040;
reg [15:0] rx_control = 16'h5000;
reg [15:0] reply;
reg [4:0] register_number;
reg reading;
reg phy_mdio = 1'bz;
integer bit_number = 0;
integer transactions = 0;
assign mdio = phy_mdio;
always @(posedge mdc) begin
frame = {frame[62:0], mdio};
if (bit_number == 45) begin
if (frame[45:14] != 32'hffffffff || frame[13:12] != 2'b01 ||
frame[9:5] != 1 || (frame[11:10] != 2'b10 && frame[11:10] != 2'b01))
$fatal(1, "Invalid MDIO frame header: %h", frame[45:0]);
register_number = frame[4:0];
reading = frame[11:10] == 2'b10;
reply = register_number == 17 ? tx_control : rx_control;
end
// Realtek specifies up to 300 ns from the rising edge to valid PHY data.
if (reading && bit_number >= 46 && !mdio_t)
$fatal(1, "FPGA drove MDIO during the PHY reply");
if (reading && bit_number == 46)
phy_mdio <= #300 1'b0;
else if (reading && bit_number >= 47 && bit_number <= 62)
phy_mdio <= #300 reply[62 - bit_number];
if (bit_number == 63) begin
if (reading) begin
phy_mdio <= #300 1'bz;
end else begin
if (frame[17:16] != 2'b10)
$fatal(1, "Invalid MDIO write turnaround");
case (register_number)
31: page = frame[15:0];
17: if (page == 16'h0d08) tx_control = frame[15:0];
21: if (page == 16'h0d08) rx_control = frame[15:0];
endcase
end
transactions = transactions + 1;
bit_number = 0;
reading = 0;
end else begin
bit_number = bit_number + 1;
end
end
initial begin
#80 reset = 0;
force dut.boot_wait = 7500000;
wait (ready);
if (page != 0 || tx_control != 16'h0140 || rx_control != 16'h5008 || transactions != 8)
$fatal(1, "Unexpected PHY configuration: %h %h %h in %d transactions",
page, tx_control, rx_control, transactions);
$display("PASS tb_rtl8211f_init");
$finish;
end
initial begin
#500000;
$fatal(1, "PHY initialization timed out at step %d", dut.step);
end
endmodule
@@ -0,0 +1,267 @@
`timescale 1ns/1ps
module tb_zynq_pl_capture;
reg clk = 0;
always #4 clk = !clk;
reg sample_clk = 0;
always #2.5 sample_clk = !sample_clk;
reg reset = 1;
reg [7:0] input_sample = 0;
reg stimulus = 1;
reg slow_pattern = 0;
reg [9:0] phase = 0;
always @(posedge sample_clk) begin
phase <= phase + 1'b1;
input_sample <= slow_pattern ? (phase < 384 ? 8'h88 : 8'h04) :
stimulus ? input_sample + 1 : 0;
end
reg rx_header_valid = 0, rx_valid = 0, rx_last = 0;
reg [15:0] rx_length = 24;
reg [7:0] rx_data = 0;
wire tx_header_valid, tx_valid, tx_last;
wire [31:0] tx_ip;
wire [15:0] tx_port, tx_length;
wire [7:0] tx_data;
wire preview_valid;
wire [7:0] preview_sample;
localparam [31:0] MAGIC = 32'h32504c5a;
localparam [31:0] BUFFER_MAGIC = 32'h31424c5a;
localparam [31:0] RLE_MAGIC = 32'h31524c5a;
localparam [31:0] TRIGGER_MAGIC = 32'h31544c5a;
localparam [31:0] CLIENT = 32'hc0a84d01;
localparam [31:0] NONCE = 32'h90123456;
localparam integer BLOCK = 1024;
integer wire_overhead = 0;
reg snapshot_mode = 0, rle_mode = 0, advanced_mode = 0, alternate = 0;
always @(posedge clk) alternate <= !alternate;
wire data_ready = !snapshot_mode || alternate;
wire header_ready = wire_overhead == 0;
always @(posedge clk) begin
if (reset)
wire_overhead <= 0;
else if (tx_valid && data_ready && tx_last)
wire_overhead <= 66;
else if (wire_overhead != 0)
wire_overhead <= wire_overhead - 1;
end
zynq_pl_capture #(.CLOCK_HZ(125000), .BLOCK(BLOCK),
.BUFFER_ENABLED(1), .RLE_ENABLED(1)) dut (
.clk(clk), .reset(reset), .sample_clk(sample_clk), .sample_reset(reset),
.digital_inputs(input_sample),
.rx_header_valid(rx_header_valid), .rx_ip(CLIENT),
.rx_port(16'd32100), .rx_destination(16'd37575), .rx_length(rx_length),
.rx_data(rx_data), .rx_valid(rx_valid), .rx_last(rx_last), .rx_error(1'b0),
.tx_header_valid(tx_header_valid), .tx_header_ready(header_ready), .tx_ip(tx_ip),
.tx_port(tx_port), .tx_length(tx_length), .tx_data(tx_data),
.tx_valid(tx_valid), .tx_ready(data_ready), .tx_last(tx_last),
.preview_valid(preview_valid), .preview_sample(preview_sample)
);
task send_command;
input [223:0] request;
input integer bytes;
integer i;
begin
@(negedge clk);
rx_length = bytes + 8;
rx_header_valid = 1;
@(negedge clk);
rx_header_valid = 0;
for (i = 0; i < bytes; i = i + 1) begin
rx_valid = 1;
rx_last = i == bytes - 1;
rx_data = request[i*8 +: 8];
@(negedge clk);
end
rx_valid = 0;
rx_last = 0;
end
endtask
task command;
input [31:0] op, nonce, divider;
send_command({96'd0, divider, nonce, op, MAGIC}, 16);
endtask
integer infos = 0, frames = 0, errors = 0, configs = 0, index = 0;
reg [127:0] prefix = 0;
reg [7:0] sample_order [0:300000];
reg raw_mode = 0;
reg [7:0] run_value;
integer written = 0, read_index = 0, run_sample;
always @(posedge sample_clk) if (preview_valid) begin
if (raw_mode && !rle_mode && written != 0 &&
preview_sample !== (sample_order[written - 1] + ((snapshot_mode || advanced_mode) ? 8'd1 : 8'd2)))
$fatal(1, "raw mode sample interval is incorrect");
sample_order[written] = raw_mode ? preview_sample :
{4'b0, preview_sample[7], preview_sample[5], preview_sample[2], preview_sample[0]};
written = written + 1;
end
reg [15:0] length;
always @(posedge clk) begin
if (tx_header_valid && header_ready) begin
if (tx_ip != CLIENT || tx_port != 32100) $fatal(1, "wrong response address");
length = tx_length;
end
if (tx_valid && data_ready) begin
if (index < 16) begin
prefix[index*8 +: 8] = tx_data;
end else begin
if (advanced_mode) begin
if (tx_data !== read_index[7:0])
$fatal(1, "bad ordered sample or pretrigger position at %d", read_index);
read_index = read_index + 1;
end else if (rle_mode) begin
if ((index & 1) == 0)
run_value = tx_data;
else for (run_sample = 0; run_sample <= tx_data; run_sample = run_sample + 1) begin
if (run_value !== sample_order[read_index])
$fatal(1, "wrong RLE sample %d", read_index);
read_index = read_index + 1;
end
end else if (raw_mode) begin
if (snapshot_mode && written != 65536)
$fatal(1, "snapshot transmitted before the buffer was frozen");
if (tx_data !== sample_order[read_index])
$fatal(1, "incorrect raw sample at index %d", read_index);
read_index = read_index + 1;
end else begin
if (tx_data !== {sample_order[read_index + 1][3:0], sample_order[read_index][3:0]})
$fatal(1, "incorrect packed channels or sample order at index %d", read_index);
read_index = read_index + 2;
end
end
if (tx_last) begin
if (prefix[31:0] != (advanced_mode ? TRIGGER_MAGIC :
rle_mode ? RLE_MAGIC : snapshot_mode ? BUFFER_MAGIC : MAGIC) ||
prefix[95:64] != NONCE || length != index + 9)
$fatal(1, "malformed response");
case (prefix[63:32])
0: begin
if (index != 15 || prefix[127:96] != 200000000) $fatal(1, "bad INFO");
infos = infos + 1;
end
1: begin
if (index != BLOCK + 15 || prefix[127:96] != frames)
$fatal(1, "bad sequence");
frames = frames + 1;
end
2: begin
if (index != 15 || prefix[127:96] != (rle_mode ? 3 : 1))
$fatal(1, "bad error");
errors = errors + 1;
end
3: begin
if (!advanced_mode || index != 15 || prefix[127:96] != configs)
$fatal(1, "bad configuration acknowledgement");
configs = configs + 1;
end
default: $fatal(1, "unknown response");
endcase
index = 0;
end else index = index + 1;
end
end
integer after_stop;
initial begin
repeat (20) @(negedge clk);
reset = 0;
repeat (30) @(negedge clk);
command(0, NONCE, 0);
wait (infos == 1);
command(1, NONCE, 32'h00ff0001);
wait (errors == 1);
command(1, NONCE, 32'h00a50001);
wait (frames == 2);
command(2, NONCE ^ 1, 0);
wait (frames == 3);
command(3, NONCE, 0);
wait (frames == 128);
command(2, NONCE, 0);
repeat (180) @(negedge clk);
after_stop = frames;
repeat (300) @(negedge clk);
if (frames != after_stop || dut.active) $fatal(1, "STOP failed");
wait (dut.fifo_empty && dut.command_idle && !dut.sending_samples);
raw_mode = 1;
frames = 0;
written = 0;
read_index = 0;
command(1, NONCE, 32'h00ff0002);
wait (frames == 8);
command(2, NONCE, 0);
repeat (200) @(negedge clk);
if (dut.active || dut.sampling) $fatal(1, "raw capture STOP failed");
$display("ZLP2 PASS: 128 packed frames at 200 MS/s, 8 raw frames at 100 MS/s, divider limit, ownership and STOP");
wait (dut.fifo_empty && dut.command_idle && !dut.sending_samples);
stimulus = 0;
repeat (20) @(negedge clk);
snapshot_mode = 1;
frames = 0;
written = 0;
read_index = 0;
send_command({96'd0, 32'd0, NONCE, 32'd0, BUFFER_MAGIC}, 16);
wait (infos == 2);
// D3 rising edge, full buffer, 200 MHz acquisition.
send_command({32'd0, 32'h08080000, 32'd65536, 32'd1, NONCE, 32'd1, BUFFER_MAGIC}, 28);
wait (dut.buffer_active);
repeat (120) @(negedge clk);
if (written != 0 || frames != 0) $fatal(1, "snapshot started without its trigger");
stimulus = 1;
wait (frames == 64);
if (written != 65536 || read_index != 65536 || sample_order[0] != 8)
$fatal(1, "incorrect snapshot count or trigger sample");
send_command({96'd0, 32'd0, NONCE, 32'd2, BUFFER_MAGIC}, 16);
$display("ZLB1 PASS: D3 trigger, 65536 ordered samples at 200 MS/s, frozen before readout");
wait (dut.fifo_empty && dut.command_idle && !dut.sending_samples && !dut.active);
stimulus = 0;
repeat (20) @(negedge clk);
snapshot_mode = 0;
rle_mode = 1;
frames = 0;
written = 0;
read_index = 0;
send_command({96'd0, 32'd0, NONCE, 32'd0, RLE_MAGIC}, 16);
wait (infos == 3);
send_command({32'd0, 32'h08080000, 32'd0, 32'h00ff0001, NONCE, 32'd1, RLE_MAGIC}, 28);
wait (dut.rle_mode);
repeat (120) @(negedge clk);
if (written != 0 || frames != 0) $fatal(1, "RLE started without its trigger");
slow_pattern = 1;
wait (frames == 1);
if (read_index <= 65536 || sample_order[0] != 8'h88)
$fatal(1, "RLE did not preserve the trigger or exceed the snapshot size");
// A rapidly changing signal must raise overflow rather than lose edges silently.
slow_pattern = 0;
stimulus = 1;
wait (errors == 2);
if (dut.active) $fatal(1, "RLE overflow did not end capture");
$display("ZLR1 PASS: D3 trigger, %d exact samples at 200 MS/s, overflow detected", read_index);
wait (dut.fifo_empty && dut.command_idle && !dut.sending_samples);
rle_mode = 0;
advanced_mode = 1;
read_index = 0;
written = 0;
frames = 0;
send_command({32'd0, 32'd0, 32'h000034ff, 32'h10, NONCE, 32'd4, TRIGGER_MAGIC}, 28);
wait (configs == 1);
send_command({32'd0, 32'd0, 32'h000080ff, 32'h11, NONCE, 32'd4, TRIGGER_MAGIC}, 28);
wait (configs == 2);
send_command({32'd0, 32'h00800100, 32'd1024, 32'd1, NONCE, 32'd1, TRIGGER_MAGIC}, 28);
wait (frames == 1);
if (read_index != 1024 || sample_order[0] != 8'h80)
$fatal(1, "wrong staged trigger result");
$display("ZLT1 PASS: two stages, 128 pretrigger samples, ordered BRAM window");
$finish;
end
initial begin
#4000000;
$fatal(1, "test timed out");
end
endmodule
@@ -0,0 +1,104 @@
`timescale 1ns / 1ps
module tb_zynq_pl_ethernet;
localparam [31:0] BOARD_IP = 32'hc0a80145;
localparam [31:0] HOST_IP = 32'hc0a80147;
localparam [47:0] HOST_MAC = 48'h021122334455;
localparam [31:0] NONCE = 32'h2a94b7dd;
localparam [479:0] ARP_REQUEST = {
48'hffffffffffff, HOST_MAC, 16'h0806,
16'h0001, 16'h0800, 8'd6, 8'd4, 16'h0001,
HOST_MAC, HOST_IP, 48'd0, BOARD_IP, 144'd0
};
localparam [479:0] HELLO = {
48'h025a4c702002, HOST_MAC, 16'h0800,
16'h4500, 16'd44, 16'h4321, 16'd0, 8'd64, 8'd17, 16'hb3c3, HOST_IP, BOARD_IP,
16'd55003, 16'd37575, 16'd24, 16'd0,
32'h5a4c5032, 32'd0, 32'hddb7942a, 32'd0, 16'd0
};
reg clk = 0, sample_clk = 0, reset = 1;
always #4 clk = ~clk;
always #2.5 sample_clk = ~sample_clk;
zynq_pl_ethernet #(.LOCAL_IP(BOARD_IP)) dut (
.clk(clk), .sample_clk(sample_clk), .sample_reset(reset),
.delay_ref_clk(sample_clk), .reset(reset), .digital_inputs(8'd0),
.phy_rx_clk(clk), .phy_rxd(4'd0), .phy_rx_ctl(1'b0), .phy_mdio_i(1'b1)
);
reg [7:0] receive_data;
reg receive_valid = 0, receive_last = 0;
reg [7:0] response [0:59];
integer replies = 0, pos = 0;
always @(posedge clk) begin
if (dut.tx_axis_tvalid && dut.tx_axis_tready) begin
response[pos] = dut.tx_axis_tdata;
if (dut.tx_axis_tlast) begin
if ({response[0], response[1], response[2], response[3], response[4], response[5]} !== HOST_MAC)
$fatal(1, "Reply did not use the local host MAC");
if (replies == 0) begin
if (pos != 41 || {response[12], response[13]} !== 16'h0806 ||
{response[20], response[21]} !== 16'h0002)
$fatal(1, "Expected an ARP reply");
end else begin
if (pos != 57 || {response[12], response[13]} !== 16'h0800 ||
{response[30], response[31], response[32], response[33]} !== HOST_IP ||
{response[34], response[35]} !== 16'd37575 ||
{response[36], response[37]} !== 16'd55003 ||
{response[42], response[43], response[44], response[45]} !== 32'h5a4c5032 ||
{response[46], response[47], response[48], response[49]} !== 32'd0 ||
{response[53], response[52], response[51], response[50]} !== NONCE ||
{response[57], response[56], response[55], response[54]} !== 32'd200000000)
$fatal(1, "Expected a ZLP2 hello reply to the local host");
end
replies = replies + 1;
pos = 0;
end else begin
pos = pos + 1;
end
end
end
task send_frame;
input [479:0] frame;
begin
for (integer i = 59; i >= 0; i = i - 1) begin
@(negedge clk);
receive_data = frame[8*i +: 8];
receive_valid = 1;
receive_last = i == 0;
@(posedge clk);
while (!dut.rx_axis_tready) @(posedge clk);
end
@(negedge clk);
receive_valid = 0;
receive_last = 0;
end
endtask
initial begin
// The PHY and MAC are exercised on the board. Here frames enter at the MAC's AXI port.
force dut.rst = reset;
force dut.rx_axis_tdata = receive_data;
force dut.rx_axis_tvalid = receive_valid;
force dut.rx_axis_tlast = receive_last;
force dut.rx_axis_tuser = 0;
force dut.tx_axis_tready = 1;
repeat (10) @(negedge clk);
reset = 0;
repeat (600) @(negedge clk);
send_frame(ARP_REQUEST);
wait (replies == 1);
repeat (20) @(negedge clk);
send_frame(HELLO);
wait (replies == 2);
$display("PASS tb_zynq_pl_ethernet");
$finish;
end
initial begin
#100000;
$fatal(1, "Timed out waiting for Ethernet replies");
end
endmodule
+13
View File
@@ -0,0 +1,13 @@
# Ядро захвата для осциллографа
`adc_frontend4` формирует согласованный поток четырёх каналов из двух
ADC-доменов. `sample_pairer` объединяет пары по индексу. `scope_core`
связывает AXI4-Lite, триггер, память окна и выдачу результата. Формат
регистров для C-клиента определён в `include/scope_regs.h`.
Подавайте `sample_data64` и `sample_index64` при подтверждённом
`coherent_epoch_valid`. При сбросе согласованно очистите оба ADC FIFO,
сборщик пар и ядро. Контроллер платы задаёт электрические параметры
входов и проверяет временные ограничения ADC. `tests/tb_scope_core.sv`
проверяет реальный AXI-обмен, сброс эпохи и захват до/после триггера.
Отдельные тесты покрывают ADC, FIFO, пары и буфер.
@@ -0,0 +1,84 @@
#ifndef SCOPE_REGS_H
#define SCOPE_REGS_H
#include <stdint.h>
/* Register ABI v1: four-channel ADC + bounded BRAM acquisition.
* No DMA/DDR, calibration, interleave or DIO register ABI is implemented. */
#define SCOPE_EXPECTED_ID UINT32_C(0x53434f50)
#define SCOPE_ABI_VERSION UINT32_C(0x00010000)
#define SCOPE_DEFAULT_REFERENCE_RATE_HZ UINT32_C(100000000)
#define SCOPE_DEFAULT_CAPTURE_DEPTH UINT32_C(256)
#define SCOPE_FIXED_CHANNEL_ENABLE UINT32_C(0x0000000f)
enum scope_register_offset {
SCOPE_REG_ID = 0x00,
SCOPE_REG_VERSION = 0x04,
SCOPE_REG_CONTROL = 0x08,
SCOPE_REG_STATUS = 0x0c,
SCOPE_REG_REFERENCE_RATE_HZ = 0x10,
SCOPE_REG_CHANNEL_ENABLE = 0x14,
SCOPE_REG_CAPABILITIES = 0x18,
SCOPE_REG_DEPTH = 0x1c,
SCOPE_REG_TRIGGER_SOURCE = 0x20,
SCOPE_REG_TRIGGER_LEVEL = 0x24,
SCOPE_REG_TRIGGER_MODE = 0x28,
SCOPE_REG_TRIGGER_EDGE = 0x2c,
SCOPE_REG_PRE = 0x30,
SCOPE_REG_POST = 0x34,
SCOPE_REG_HYSTERESIS = 0x38,
SCOPE_REG_AUTO_TIMEOUT = 0x3c,
SCOPE_REG_TRIGGER_INDEX_LO = 0x50,
SCOPE_REG_TRIGGER_INDEX_HI = 0x54,
SCOPE_REG_FIRST_INDEX_LO = 0x58,
SCOPE_REG_FIRST_INDEX_HI = 0x5c,
SCOPE_REG_RECORD_LENGTH = 0x60,
SCOPE_REG_SNAPSHOT_STATUS = 0x64,
SCOPE_REG_SAMPLE_COUNT_LO = 0x68,
SCOPE_REG_SAMPLE_COUNT_HI = 0x6c,
SCOPE_REG_FIFO1_OVERFLOW = 0x70,
SCOPE_REG_FIFO2_OVERFLOW = 0x74,
SCOPE_REG_TRIGGER_COUNT = 0x78
};
/* Write exactly one command, then await the write response. Zero is a no-op. */
#define SCOPE_CONTROL_ARM UINT32_C(0x00000001)
#define SCOPE_CONTROL_STOP UINT32_C(0x00000002)
#define SCOPE_CONTROL_SOFT_RESET UINT32_C(0x00000004)
#define SCOPE_CONTROL_SNAPSHOT UINT32_C(0x00000008)
#define SCOPE_CAP_ADC4 UINT32_C(0x00000001)
#define SCOPE_CAP_BRAM_CAPTURE UINT32_C(0x00000002)
#define SCOPE_CAP_EXTERNAL_EPOCH UINT32_C(0x00000004)
#define SCOPE_CAPABILITIES_V1 UINT32_C(0x00000007)
/* scope_core STATUS/SNAPSHOT_STATUS bits; all other bits are reserved zero. */
#define SCOPE_STATUS_ACTIVE UINT32_C(0x00000001)
#define SCOPE_STATUS_DONE UINT32_C(0x00000002)
#define SCOPE_STATUS_FAULT UINT32_C(0x00000004)
#define SCOPE_STATUS_EPOCH_VALID UINT32_C(0x00000008)
#define SCOPE_STATUS_STREAM_VALID UINT32_C(0x00000010)
#define SCOPE_STATUS_FRONTEND_FAULT UINT32_C(0x00000020)
#define SCOPE_STATUS_CORE_RESET UINT32_C(0x00000040)
enum scope_trigger_source {
SCOPE_TRIGGER_CH1 = 0,
SCOPE_TRIGGER_CH2 = 1,
SCOPE_TRIGGER_CH3 = 2,
SCOPE_TRIGGER_CH4 = 3,
SCOPE_TRIGGER_EXTERNAL = 4
};
enum scope_trigger_mode {
SCOPE_TRIGGER_AUTO = 0,
SCOPE_TRIGGER_NORMAL = 1,
SCOPE_TRIGGER_SINGLE = 2
};
enum scope_trigger_edge {
SCOPE_TRIGGER_RISING = 0,
SCOPE_TRIGGER_FALLING = 1
};
/* These constants are defaults, not substitutes for capability/depth queries.
* 64-bit counters/indices are read only from the completed SNAPSHOT bank.
* Platform code supplies MMIO accessors, barriers and the peripheral base. */
#endif
@@ -0,0 +1,99 @@
`timescale 1ns/1ps
// Bounded BRAM reference acquisition: pre + trigger + post, then frozen drain.
// This is not the later continuously DMA-written DDR ring.
module acquisition_buffer #(
parameter integer ADDR_BITS = 8
) (
input wire clk, rst, arm, stop, session_fault,
input wire [31:0] cfg_pre, cfg_post,
input wire [63:0] sample_data, sample_index,
input wire sample_valid,
input wire trigger_hit,
output wire eligible,
output reg active, done, fault,
output reg [63:0] trigger_index, first_index,
output reg [31:0] record_length,
output reg [63:0] m_data, m_index,
output wire m_valid,
input wire m_ready,
output wire m_last
);
localparam integer DEPTH = 1 << ADDR_BITS;
(* ram_style = "block" *) reg [63:0] ram [0:DEPTH-1];
reg [ADDR_BITS-1:0] write_ptr, read_ptr;
reg [31:0] pre_length, post_length, history_count, post_remaining, fetch_count;
reg [63:0] expected_index;
reg seen_sample, triggered, output_valid, output_last;
wire [32:0] cfg_total = {1'b0,cfg_pre}+{1'b0,cfg_post}+33'd1;
wire bad_index = seen_sample && sample_index != expected_index;
wire take_sample = active && sample_valid && !arm && !stop && !session_fault &&
!fault && !bad_index;
wire fetch = done && !fault && !arm && !stop && !session_fault &&
(!output_valid || m_ready) && fetch_count < record_length;
assign eligible = active && !fault && history_count >= pre_length;
assign m_valid = output_valid;
assign m_last = output_last && output_valid;
initial if (ADDR_BITS < 2 || ADDR_BITS > 20) $error("acquisition_buffer ADDR_BITS 2..20 required");
// RAM has no reset. Valid/state owns initialized payload.
always @(posedge clk) begin
if (take_sample) ram[write_ptr] <= sample_data;
if (fetch) m_data <= ram[read_ptr];
end
always @(posedge clk) begin
if (rst) begin
write_ptr<=0; read_ptr<=0; pre_length<=0; post_length<=0;
history_count<=0; post_remaining<=0; fetch_count<=0;
expected_index<=0; seen_sample<=0; triggered<=0;
active<=0; done<=0; fault<=0;
trigger_index<=0; first_index<=0; record_length<=0;
output_valid<=0; output_last<=0; m_index<=0;
end else begin
if (output_valid && m_ready) output_valid<=0;
// Once stalled, preserve the pending output even through faults.
if (fetch) begin
read_ptr<=read_ptr+1'b1;
m_index<=first_index+fetch_count;
output_last<=fetch_count+1 == record_length;
output_valid<=1;
fetch_count<=fetch_count+1'b1;
end
if (session_fault) begin fault<=1; active<=0; done<=0; end
else if (stop) begin active<=0; done<=0; end
else if (arm) begin
// A new ARM is rejected until the old output has drained.
if (active || output_valid || (done && fetch_count < record_length)) begin
fault<=1; active<=0; done<=0;
end else begin
write_ptr<=0; read_ptr<=0; history_count<=0;
seen_sample<=0; triggered<=0; fetch_count<=0; post_remaining<=0;
trigger_index<=0; first_index<=0;
pre_length<=cfg_pre; post_length<=cfg_post;
record_length<=cfg_total[31:0]; done<=0;
if (cfg_total > DEPTH) begin fault<=1; active<=0; end
else if (!fault) active<=1;
end
end else if (active && sample_valid) begin
if (bad_index || (trigger_hit && (!eligible || sample_index < pre_length))) begin
fault<=1; active<=0; done<=0;
end else begin
write_ptr<=write_ptr+1'b1;
expected_index<=sample_index+1'b1;
seen_sample<=1;
if (history_count < pre_length) history_count<=history_count+1'b1;
if (!triggered && trigger_hit) begin
triggered<=1; trigger_index<=sample_index;
first_index<=sample_index-pre_length;
read_ptr<=write_ptr-pre_length;
post_remaining<=post_length;
if (post_length==0) begin active<=0; done<=1; end
end else if (triggered) begin
post_remaining<=post_remaining-1'b1;
if (post_remaining==1) begin active<=0; done<=1; end
end
end
end
end
end
endmodule
+48
View File
@@ -0,0 +1,48 @@
`timescale 1ns/1ps
// One physical AD9216, cores A and B in one forwarded-clock domain.
// Raw pad registers have no reset or clock enable to permit IOB placement.
// Offset binary, right aligned: [15:10]=0, [31:26]=0.
// ADC wake-up/reference settling is external; ready must mean already settled.
module adc_capture (
input wire adc_clk,
input wire arst_n,
input wire ready_async,
input wire [9:0] adc_data_a,
input wire [9:0] adc_data_b,
output reg [31:0] sample_word,
output reg sample_valid,
output reg [63:0] sample_index
);
wire rst;
reset_sync reset_i (.clk(adc_clk), .arst_n(arst_n), .rst(rst));
(* IOB = "TRUE" *) reg [9:0] input_a, input_b;
(* ASYNC_REG = "TRUE", SHREG_EXTRACT = "NO" *) reg [1:0] ready_pipe;
reg warm;
reg [63:0] next_index;
always @(posedge adc_clk) begin
input_a <= adc_data_a;
input_b <= adc_data_b;
end
always @(posedge adc_clk or negedge arst_n) begin
if (!arst_n) ready_pipe <= 0;
else if (rst) ready_pipe <= 0;
else ready_pipe <= {ready_pipe[0], ready_async};
end
always @(posedge adc_clk) begin
if (rst) begin
sample_word <= 0;
sample_valid <= 0;
sample_index <= 0;
next_index <= 0;
warm <= 0;
end else begin
sample_word <= {6'b0, input_b, 6'b0, input_a};
warm <= ready_pipe[1];
sample_valid <= ready_pipe[1] && warm;
if (ready_pipe[1] && warm) begin
sample_index <= next_index;
next_index <= next_index + 1'b1;
end
end
end
endmodule
@@ -0,0 +1,19 @@
`timescale 1ns/1ps
// Four channels; two outputs remain in two different clock domains.
// No pairing or direct multi-bit transfer between these outputs is permitted.
module adc_capture_dual (
input wire arst_n,
input wire mezz_ready,
input wire adc1_clk, adc2_clk,
input wire [9:0] adc1_a, adc1_b, adc2_a, adc2_b,
output wire [31:0] chip1_word, chip2_word,
output wire chip1_valid, chip2_valid,
output wire [63:0] chip1_index, chip2_index
);
adc_capture chip1(.adc_clk(adc1_clk), .arst_n(arst_n),
.ready_async(mezz_ready), .adc_data_a(adc1_a), .adc_data_b(adc1_b),
.sample_word(chip1_word), .sample_valid(chip1_valid), .sample_index(chip1_index));
adc_capture chip2(.adc_clk(adc2_clk), .arst_n(arst_n),
.ready_async(mezz_ready), .adc_data_a(adc2_a), .adc_data_b(adc2_b),
.sample_word(chip2_word), .sample_valid(chip2_valid), .sample_index(chip2_index));
endmodule
@@ -0,0 +1,42 @@
`timescale 1ns/1ps
// Provisional four-channel front end; caller must establish physical epoch.
module adc_frontend4 #(
parameter integer FIFO_ADDR_BITS = 9
) (
input wire arst_n,
input wire mezz_ready,
input wire adc1_clk, adc2_clk,
input wire [9:0] adc1_a, adc1_b, adc2_a, adc2_b,
input wire stream_clk,
input wire coherent_epoch_valid,
output wire [63:0] sample_data, sample_index,
output wire sample_valid,
input wire sample_ready,
output wire session_fault,
output wire [31:0] adc1_overflow_count, adc2_overflow_count
);
wire [95:0] data1, data2;
wire valid1, valid2, ready1, ready2, fault1, fault2, pair_fault, stream_rst;
capture_stream #(.FIFO_ADDR_BITS(FIFO_ADDR_BITS)) stream1 (
.arst_n(arst_n), .adc_clk(adc1_clk), .ready_async(mezz_ready),
.adc_data_a(adc1_a), .adc_data_b(adc1_b), .stream_clk(stream_clk),
.stream_data(data1), .stream_valid(valid1), .stream_ready(ready1),
.session_fault(fault1), .overflow_count_adc(adc1_overflow_count)
);
capture_stream #(.FIFO_ADDR_BITS(FIFO_ADDR_BITS)) stream2 (
.arst_n(arst_n), .adc_clk(adc2_clk), .ready_async(mezz_ready),
.adc_data_a(adc2_a), .adc_data_b(adc2_b), .stream_clk(stream_clk),
.stream_data(data2), .stream_valid(valid2), .stream_ready(ready2),
.session_fault(fault2), .overflow_count_adc(adc2_overflow_count)
);
reset_sync stream_reset_i (.clk(stream_clk), .arst_n(arst_n), .rst(stream_rst));
sample_pairer pair_i (
.clk(stream_clk), .rst(stream_rst), .coherent_epoch_valid(coherent_epoch_valid),
.session_fault(fault1 || fault2),
.a_data(data1), .a_valid(valid1), .a_ready(ready1),
.b_data(data2), .b_valid(valid2), .b_ready(ready2),
.m_data(sample_data), .m_index(sample_index),
.m_valid(sample_valid), .m_ready(sample_ready), .fault(pair_fault)
);
assign session_fault = fault1 || fault2 || pair_fault;
endmodule
@@ -0,0 +1,115 @@
`timescale 1ns/1ps
// Portable dual-clock FIFO. RAM words cross domains only through a clocked
// read port; Gray pointers cross through independent two-stage synchronizers.
// Reset is shared by both domains. The output register adds one holding slot
// beyond the 2**ADDR_BITS RAM locations after the read clock starts.
module async_sample_fifo #(
parameter integer WIDTH = 96,
parameter integer ADDR_BITS = 9
) (
input wire arst_n,
input wire wr_clk,
input wire wr_valid,
input wire [WIDTH-1:0] wr_data,
output wire wr_ready,
output reg wr_overflow,
output reg wr_overflow_sticky,
output reg [31:0] wr_overflow_count,
input wire rd_clk,
output wire rd_valid,
output reg [WIDTH-1:0] rd_data,
input wire rd_ready
);
localparam integer POINTER_BITS = ADDR_BITS + 1;
localparam integer DEPTH = 1 << ADDR_BITS;
wire wr_rst, rd_rst;
reset_sync wr_reset (.clk(wr_clk), .arst_n(arst_n), .rst(wr_rst));
reset_sync rd_reset (.clk(rd_clk), .arst_n(arst_n), .rst(rd_rst));
(* ram_style = "block" *) reg [WIDTH-1:0] ram [0:DEPTH-1];
reg [POINTER_BITS-1:0] wr_binary, wr_gray;
reg [POINTER_BITS-1:0] rd_binary, rd_gray;
(* ASYNC_REG = "TRUE", SHREG_EXTRACT = "NO" *)
reg [POINTER_BITS-1:0] rd_gray_w1, rd_gray_w2;
(* ASYNC_REG = "TRUE", SHREG_EXTRACT = "NO" *)
reg [POINTER_BITS-1:0] wr_gray_r1, wr_gray_r2;
reg wr_full, rd_empty, output_valid;
assign wr_ready = arst_n && !wr_rst && !wr_full;
assign rd_valid = arst_n && !rd_rst && output_valid;
wire wr_accept = wr_valid && wr_ready;
wire rd_fetch = !rd_rst && arst_n && (!output_valid || rd_ready) && !rd_empty;
wire [POINTER_BITS-1:0] wr_binary_next = wr_binary + wr_accept;
wire [POINTER_BITS-1:0] rd_binary_next = rd_binary + rd_fetch;
wire [POINTER_BITS-1:0] wr_gray_next = (wr_binary_next >> 1) ^ wr_binary_next;
wire [POINTER_BITS-1:0] rd_gray_next = (rd_binary_next >> 1) ^ rd_binary_next;
wire wr_full_next = wr_gray_next ==
{~rd_gray_w2[ADDR_BITS:ADDR_BITS-1], rd_gray_w2[ADDR_BITS-2:0]};
wire rd_empty_next = rd_gray_next == wr_gray_r2;
initial begin
if (WIDTH < 1 || ADDR_BITS < 2)
$error("async_sample_fifo requires WIDTH >= 1 and ADDR_BITS >= 2");
end
// No RAM/output-data reset: the valid flags own initialized state, and
// keeping these ports clocked without reset permits dual-port BRAM inference.
always @(posedge wr_clk)
if (wr_accept) ram[wr_binary[ADDR_BITS-1:0]] <= wr_data;
always @(posedge rd_clk)
if (rd_fetch) rd_data <= ram[rd_binary[ADDR_BITS-1:0]];
always @(posedge wr_clk or posedge wr_rst) begin
if (wr_rst) begin
rd_gray_w1 <= 0;
rd_gray_w2 <= 0;
end else begin
rd_gray_w1 <= rd_gray;
rd_gray_w2 <= rd_gray_w1;
end
end
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
wr_gray_r1 <= 0;
wr_gray_r2 <= 0;
end else begin
wr_gray_r1 <= wr_gray;
wr_gray_r2 <= wr_gray_r1;
end
end
always @(posedge wr_clk or posedge wr_rst) begin
if (wr_rst) begin
wr_binary <= 0;
wr_gray <= 0;
wr_full <= 0;
wr_overflow <= 0;
wr_overflow_sticky <= 0;
wr_overflow_count <= 0;
end else begin
wr_binary <= wr_binary_next;
wr_gray <= wr_gray_next;
wr_full <= wr_full_next;
wr_overflow <= wr_valid && !wr_ready;
if (wr_valid && !wr_ready) begin
wr_overflow_sticky <= 1'b1;
if (wr_overflow_count != 32'hffffffff)
wr_overflow_count <= wr_overflow_count + 1'b1;
end
end
end
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
rd_binary <= 0;
rd_gray <= 0;
rd_empty <= 1;
output_valid <= 0;
end else begin
rd_binary <= rd_binary_next;
rd_gray <= rd_gray_next;
rd_empty <= rd_empty_next;
if (!output_valid || rd_ready)
output_valid <= rd_fetch;
end
end
endmodule
+247
View File
@@ -0,0 +1,247 @@
`timescale 1ns/1ps
// Single-clock AXI4-Lite register bank. All *_live inputs must already be
// coherent in clk; this block does not synchronize ADC-domain status buses.
module axi_scope_regs #(
parameter integer ADDR_WIDTH = 12,
parameter [31:0] REFERENCE_RATE_HZ = 32'd100000000,
parameter integer CAPTURE_ADDR_BITS = 8
) (
input wire clk,
input wire aresetn,
input wire [ADDR_WIDTH-1:0] s_axi_awaddr,
input wire [2:0] s_axi_awprot,
input wire s_axi_awvalid,
output wire s_axi_awready,
input wire [31:0] s_axi_wdata,
input wire [3:0] s_axi_wstrb,
input wire s_axi_wvalid,
output wire s_axi_wready,
output reg [1:0] s_axi_bresp,
output reg s_axi_bvalid,
input wire s_axi_bready,
input wire [ADDR_WIDTH-1:0] s_axi_araddr,
input wire [2:0] s_axi_arprot,
input wire s_axi_arvalid,
output wire s_axi_arready,
output reg [31:0] s_axi_rdata,
output reg [1:0] s_axi_rresp,
output reg s_axi_rvalid,
input wire s_axi_rready,
output reg [2:0] cfg_source,
output reg [1:0] cfg_mode,
output reg cfg_falling,
output reg [9:0] cfg_level,
output reg [9:0] cfg_hysteresis,
output reg [31:0] cfg_pre,
output reg [31:0] cfg_post,
output reg [31:0] cfg_auto_timeout,
output reg arm_pulse,
output reg stop_pulse,
output reg soft_reset_pulse,
output reg snapshot_pulse,
input wire [31:0] status_live,
input wire [63:0] trigger_index_live,
input wire [63:0] first_index_live,
input wire [31:0] record_length_live,
input wire [63:0] sample_count_live,
input wire [31:0] fifo1_overflow_live,
input wire [31:0] fifo2_overflow_live,
input wire [31:0] trigger_count_live
);
localparam [1:0] OKAY = 2'b00, SLVERR = 2'b10;
localparam [31:0] CAPTURE_DEPTH = 32'd1 << CAPTURE_ADDR_BITS;
reg aw_pending, w_pending;
reg [ADDR_WIDTH-1:0] write_address;
reg [31:0] write_data;
reg [3:0] write_strobes;
reg [63:0] trigger_snapshot, first_snapshot, samples_snapshot;
reg [31:0] length_snapshot, status_snapshot;
reg [31:0] fifo1_snapshot, fifo2_snapshot, trigger_count_snapshot;
reg [31:0] merged_value;
assign s_axi_awready = aresetn && !aw_pending && !s_axi_bvalid;
assign s_axi_wready = aresetn && !w_pending && !s_axi_bvalid;
assign s_axi_arready = aresetn && !s_axi_rvalid;
initial begin
if (ADDR_WIDTH < 8 || CAPTURE_ADDR_BITS < 1 || CAPTURE_ADDR_BITS > 30)
$error("axi_scope_regs requires ADDR_WIDTH >= 8 and CAPTURE_ADDR_BITS in 1..30");
end
function automatic [31:0] merge_bytes(input [31:0] old_value,
input [31:0] new_value, input [3:0] byte_strobes);
integer byte_number;
begin
merge_bytes = old_value;
for (byte_number = 0; byte_number < 4; byte_number = byte_number + 1)
if (byte_strobes[byte_number])
merge_bytes[byte_number*8 +: 8] = new_value[byte_number*8 +: 8];
end
endfunction
// Read responses capture the addressed value at the AR handshake and
// retain data/response until RREADY. Snapshot words are a separate bank.
function automatic [33:0] read_value(input [ADDR_WIDTH-1:0] address);
begin
read_value = {SLVERR, 32'd0};
if (address[1:0] == 0) begin
case (address)
'h00: read_value = {OKAY, 32'h53434f50};
'h04: read_value = {OKAY, 32'h00010000};
'h08: read_value = {OKAY, 32'd0};
'h0c: read_value = {OKAY, status_live};
'h10: read_value = {OKAY, REFERENCE_RATE_HZ};
'h14: read_value = {OKAY, 32'h0000000f};
'h18: read_value = {OKAY, 32'h00000007};
'h1c: read_value = {OKAY, CAPTURE_DEPTH};
'h20: read_value = {OKAY, 29'd0, cfg_source};
'h24: read_value = {OKAY, 22'd0, cfg_level};
'h28: read_value = {OKAY, 30'd0, cfg_mode};
'h2c: read_value = {OKAY, 31'd0, cfg_falling};
'h30: read_value = {OKAY, cfg_pre};
'h34: read_value = {OKAY, cfg_post};
'h38: read_value = {OKAY, 22'd0, cfg_hysteresis};
'h3c: read_value = {OKAY, cfg_auto_timeout};
'h50: read_value = {OKAY, trigger_snapshot[31:0]};
'h54: read_value = {OKAY, trigger_snapshot[63:32]};
'h58: read_value = {OKAY, first_snapshot[31:0]};
'h5c: read_value = {OKAY, first_snapshot[63:32]};
'h60: read_value = {OKAY, length_snapshot};
'h64: read_value = {OKAY, status_snapshot};
'h68: read_value = {OKAY, samples_snapshot[31:0]};
'h6c: read_value = {OKAY, samples_snapshot[63:32]};
'h70: read_value = {OKAY, fifo1_snapshot};
'h74: read_value = {OKAY, fifo2_snapshot};
'h78: read_value = {OKAY, trigger_count_snapshot};
default: read_value = {SLVERR, 32'd0};
endcase
end
end
endfunction
always @(posedge clk) begin
if (!aresetn) begin
aw_pending <= 0;
w_pending <= 0;
write_address <= 0;
write_data <= 0;
write_strobes <= 0;
s_axi_bvalid <= 0;
s_axi_bresp <= OKAY;
cfg_source <= 0;
cfg_level <= 512;
cfg_mode <= 1;
cfg_falling <= 0;
cfg_pre <= 64;
cfg_post <= 64;
cfg_hysteresis <= 4;
cfg_auto_timeout <= 1000000;
arm_pulse <= 0;
stop_pulse <= 0;
soft_reset_pulse <= 0;
snapshot_pulse <= 0;
trigger_snapshot <= 0;
first_snapshot <= 0;
samples_snapshot <= 0;
length_snapshot <= 0;
status_snapshot <= 0;
fifo1_snapshot <= 0;
fifo2_snapshot <= 0;
trigger_count_snapshot <= 0;
end else begin
arm_pulse <= 0;
stop_pulse <= 0;
soft_reset_pulse <= 0;
snapshot_pulse <= 0;
if (s_axi_bvalid && s_axi_bready) s_axi_bvalid <= 0;
if (s_axi_awvalid && s_axi_awready) begin
write_address <= s_axi_awaddr;
aw_pending <= 1;
end
if (s_axi_wvalid && s_axi_wready) begin
write_data <= s_axi_wdata;
write_strobes <= s_axi_wstrb;
w_pending <= 1;
end
if (aw_pending && w_pending && !s_axi_bvalid) begin
aw_pending <= 0;
w_pending <= 0;
s_axi_bvalid <= 1;
s_axi_bresp <= OKAY;
if (write_address[1:0] != 0) begin
s_axi_bresp <= SLVERR;
end else begin
case (write_address)
'h08: begin
// Without low-byte enable this command is a no-op.
if (write_strobes[0]) begin
merged_value = merge_bytes(0, write_data, write_strobes);
case (merged_value)
32'd0: begin end
32'd1: arm_pulse <= 1;
32'd2: stop_pulse <= 1;
32'd4: soft_reset_pulse <= 1;
32'd8: begin
snapshot_pulse <= 1;
trigger_snapshot <= trigger_index_live;
first_snapshot <= first_index_live;
samples_snapshot <= sample_count_live;
length_snapshot <= record_length_live;
status_snapshot <= status_live;
fifo1_snapshot <= fifo1_overflow_live;
fifo2_snapshot <= fifo2_overflow_live;
trigger_count_snapshot <= trigger_count_live;
end
default: s_axi_bresp <= SLVERR;
endcase
end
end
'h20: begin
merged_value = merge_bytes({29'd0, cfg_source}, write_data, write_strobes);
if (merged_value <= 4) cfg_source <= merged_value[2:0];
else s_axi_bresp <= SLVERR;
end
'h24: begin
merged_value = merge_bytes({22'd0, cfg_level}, write_data, write_strobes);
if (merged_value <= 1023) cfg_level <= merged_value[9:0];
else s_axi_bresp <= SLVERR;
end
'h28: begin
merged_value = merge_bytes({30'd0, cfg_mode}, write_data, write_strobes);
if (merged_value <= 2) cfg_mode <= merged_value[1:0];
else s_axi_bresp <= SLVERR;
end
'h2c: begin
merged_value = merge_bytes({31'd0, cfg_falling}, write_data, write_strobes);
if (merged_value <= 1) cfg_falling <= merged_value[0];
else s_axi_bresp <= SLVERR;
end
'h30: cfg_pre <= merge_bytes(cfg_pre, write_data, write_strobes);
'h34: cfg_post <= merge_bytes(cfg_post, write_data, write_strobes);
'h38: begin
merged_value = merge_bytes({22'd0, cfg_hysteresis}, write_data, write_strobes);
if (merged_value <= 1023) cfg_hysteresis <= merged_value[9:0];
else s_axi_bresp <= SLVERR;
end
'h3c: cfg_auto_timeout <= merge_bytes(cfg_auto_timeout, write_data, write_strobes);
default: s_axi_bresp <= SLVERR;
endcase
end
end
end
end
always @(posedge clk) begin
if (!aresetn) begin
s_axi_rvalid <= 0;
s_axi_rdata <= 0;
s_axi_rresp <= OKAY;
end else begin
if (s_axi_rvalid && s_axi_rready) s_axi_rvalid <= 0;
if (s_axi_arvalid && s_axi_arready) begin
{s_axi_rresp, s_axi_rdata} <= read_value(s_axi_araddr);
s_axi_rvalid <= 1;
end
end
end
endmodule
@@ -0,0 +1,57 @@
`timescale 1ns/1ps
// One ADC chip -> indexed asynchronous stream. Fault is sticky in rd_clk.
module capture_stream #(
parameter integer FIFO_ADDR_BITS = 9
) (
input wire arst_n,
input wire adc_clk,
input wire ready_async,
input wire [9:0] adc_data_a, adc_data_b,
input wire stream_clk,
output wire [95:0] stream_data,
output wire stream_valid,
input wire stream_ready,
output wire session_fault,
output wire [31:0] overflow_count_adc
);
wire [31:0] sample_word;
wire [63:0] sample_index;
wire sample_valid, adc_rst;
wire fifo_ready, overflow_pulse, overflow_sticky;
reg seen_valid, ready_lost;
(* ASYNC_REG = "TRUE", SHREG_EXTRACT = "NO" *) reg [1:0] fault_sync;
wire stream_rst;
adc_capture capture_i (
.adc_clk(adc_clk), .arst_n(arst_n), .ready_async(ready_async),
.adc_data_a(adc_data_a), .adc_data_b(adc_data_b),
.sample_word(sample_word), .sample_valid(sample_valid),
.sample_index(sample_index)
);
reset_sync adc_reset_i (.clk(adc_clk), .arst_n(arst_n), .rst(adc_rst));
reset_sync stream_reset_i (.clk(stream_clk), .arst_n(arst_n), .rst(stream_rst));
async_sample_fifo #(.WIDTH(96), .ADDR_BITS(FIFO_ADDR_BITS)) fifo_i (
.arst_n(arst_n), .wr_clk(adc_clk), .wr_valid(sample_valid),
.wr_data({sample_index, sample_word}), .wr_ready(fifo_ready),
.wr_overflow(overflow_pulse), .wr_overflow_sticky(overflow_sticky),
.wr_overflow_count(overflow_count_adc),
.rd_clk(stream_clk), .rd_valid(stream_valid),
.rd_data(stream_data), .rd_ready(stream_ready)
);
always @(posedge adc_clk or posedge adc_rst) begin
if (adc_rst) begin seen_valid <= 0; ready_lost <= 0; end
else begin
if (sample_valid) seen_valid <= 1;
if (seen_valid && !sample_valid) ready_lost <= 1;
end
end
always @(posedge stream_clk or posedge stream_rst) begin
if (stream_rst) fault_sync <= 0;
else fault_sync <= {fault_sync[0], ready_lost || overflow_sticky};
end
assign session_fault = fault_sync[1];
// overflow_count_adc belongs to adc_clk. ARM must use a coherent snapshot,
// not independent per-bit synchronizers. A stopped ADC clock needs an
// external watchdog: this block cannot detect absence of its own clock.
endmodule
+13
View File
@@ -0,0 +1,13 @@
`timescale 1ns/1ps
// Asynchronous assertion, synchronous release in each destination clock.
module reset_sync (
input wire clk,
input wire arst_n,
output wire rst
);
(* ASYNC_REG = "TRUE", SHREG_EXTRACT = "NO" *) reg [1:0] release_pipe;
always @(posedge clk or negedge arst_n)
if (!arst_n) release_pipe <= 2'b00;
else release_pipe <= {release_pipe[0], 1'b1};
assign rst = ~release_pipe[1];
endmodule
@@ -0,0 +1,64 @@
`timescale 1ns/1ps
// Fixed four-channel pairing in the FIFO read/AXIS clock domain.
// Inputs are stable FIFO heads: {local_index[63:0], chip_word[31:0]}.
// Equality of local indices does not establish a common physical ADC epoch.
module sample_pairer (
input wire clk,
input wire rst,
input wire coherent_epoch_valid,
input wire session_fault,
input wire [95:0] a_data,
input wire a_valid,
output wire a_ready,
input wire [95:0] b_data,
input wire b_valid,
output wire b_ready,
output reg [63:0] m_data,
output reg [63:0] m_index,
output reg m_valid,
input wire m_ready,
output reg fault
);
reg [63:0] expected_index;
reg epoch_seen;
wire both_valid = a_valid && b_valid;
wire index_bad = (a_data[95:32] != b_data[95:32]) ||
(a_data[95:32] != expected_index);
wire epoch_lost = epoch_seen && !coherent_epoch_valid;
wire fault_event = session_fault || epoch_lost ||
(coherent_epoch_valid && both_valid && index_bad);
wire slot_available = !m_valid || m_ready;
wire pair_fire = !rst && !fault && !fault_event &&
coherent_epoch_valid && both_valid && slot_available;
// A pair is consumed atomically. Neither FIFO may discard/rebase a head.
assign a_ready = pair_fire;
assign b_ready = pair_fire;
always @(posedge clk) begin
if (rst) begin
m_data <= 0;
m_index <= 0;
m_valid <= 0;
fault <= 0;
expected_index <= 0;
epoch_seen <= 0;
end else begin
// A previously accepted word remains valid through a later fault.
// Once stalled, AXIS-like valid/payload may change only on transfer
// or an explicit interface reset; acquisition validity is external.
if (m_valid && m_ready)
m_valid <= 0;
if (fault_event)
fault <= 1;
if (pair_fire) begin
m_data <= {b_data[31:0], a_data[31:0]};
m_index <= a_data[95:32];
m_valid <= 1;
expected_index <= expected_index + 1'b1;
epoch_seen <= 1;
end
end
end
endmodule
+91
View File
@@ -0,0 +1,91 @@
`timescale 1ns/1ps
// AXI control + trigger/window reference, in one processing clock domain.
// An adc_frontend4 instance supplies the indexed stream; platform glue must
// route frontend_reset_request to the joint frontend reset and establish epoch.
module scope_core #(
parameter integer CAPTURE_ADDR_BITS = 8
) (
input wire clk, aresetn,
input wire [11:0] s_axi_awaddr,
input wire [2:0] s_axi_awprot,
input wire s_axi_awvalid,
output wire s_axi_awready,
input wire [31:0] s_axi_wdata,
input wire [3:0] s_axi_wstrb,
input wire s_axi_wvalid,
output wire s_axi_wready,
output wire [1:0] s_axi_bresp,
output wire s_axi_bvalid,
input wire s_axi_bready,
input wire [11:0] s_axi_araddr,
input wire [2:0] s_axi_arprot,
input wire s_axi_arvalid,
output wire s_axi_arready,
output wire [31:0] s_axi_rdata,
output wire [1:0] s_axi_rresp,
output wire s_axi_rvalid,
input wire s_axi_rready,
input wire [63:0] sample_data, sample_index,
input wire sample_valid,
output wire sample_ready,
input wire coherent_epoch_valid, frontend_fault, ext_level,
// These must already be coherent clk-domain snapshots, not ADC buses.
input wire [31:0] fifo1_overflow_live, fifo2_overflow_live,
output wire frontend_reset_request,
output wire [63:0] m_data, m_index,
output wire m_valid, m_last,
input wire m_ready,
output wire irq
);
wire [2:0] cfg_source;
wire [1:0] cfg_mode;
wire cfg_falling;
wire [9:0] cfg_level, cfg_hysteresis;
wire [31:0] cfg_pre, cfg_post, cfg_auto_timeout;
wire arm_pulse, stop_pulse, soft_reset_pulse, snapshot_pulse;
wire active, done, fault, core_rst, trigger_event;
wire [63:0] trigger_index_live, first_index_live;
wire [31:0] record_length_live;
reg [63:0] sample_count_live;
reg [31:0] trigger_count_live;
wire session_fault = frontend_fault || (!coherent_epoch_valid && (arm_pulse || active));
wire [31:0] status_live = {25'b0, core_rst, frontend_fault, m_valid,
coherent_epoch_valid, (fault || frontend_fault), done, active};
assign frontend_reset_request = soft_reset_pulse;
reset_sync core_reset_i (.clk(clk), .arst_n(aresetn && !soft_reset_pulse), .rst(core_rst));
assign sample_ready = !core_rst;
assign irq = done || fault || frontend_fault;
axi_scope_regs #(.CAPTURE_ADDR_BITS(CAPTURE_ADDR_BITS)) registers_i (
.clk(clk), .aresetn(aresetn),
.s_axi_awaddr(s_axi_awaddr), .s_axi_awprot(s_axi_awprot), .s_axi_awvalid(s_axi_awvalid), .s_axi_awready(s_axi_awready),
.s_axi_wdata(s_axi_wdata), .s_axi_wstrb(s_axi_wstrb), .s_axi_wvalid(s_axi_wvalid), .s_axi_wready(s_axi_wready),
.s_axi_bresp(s_axi_bresp), .s_axi_bvalid(s_axi_bvalid), .s_axi_bready(s_axi_bready),
.s_axi_araddr(s_axi_araddr), .s_axi_arprot(s_axi_arprot), .s_axi_arvalid(s_axi_arvalid), .s_axi_arready(s_axi_arready),
.s_axi_rdata(s_axi_rdata), .s_axi_rresp(s_axi_rresp), .s_axi_rvalid(s_axi_rvalid), .s_axi_rready(s_axi_rready),
.cfg_source(cfg_source), .cfg_mode(cfg_mode), .cfg_falling(cfg_falling),
.cfg_level(cfg_level), .cfg_hysteresis(cfg_hysteresis), .cfg_pre(cfg_pre), .cfg_post(cfg_post), .cfg_auto_timeout(cfg_auto_timeout),
.arm_pulse(arm_pulse), .stop_pulse(stop_pulse), .soft_reset_pulse(soft_reset_pulse), .snapshot_pulse(snapshot_pulse),
.status_live(status_live), .trigger_index_live(trigger_index_live), .first_index_live(first_index_live),
.record_length_live(record_length_live), .sample_count_live(sample_count_live),
.fifo1_overflow_live(fifo1_overflow_live), .fifo2_overflow_live(fifo2_overflow_live), .trigger_count_live(trigger_count_live)
);
triggered_acquisition #(.ADDR_BITS(CAPTURE_ADDR_BITS)) acquisition_i (
.clk(clk), .rst(core_rst), .arm(arm_pulse), .stop(stop_pulse), .session_fault(session_fault),
.cfg_pre(cfg_pre), .cfg_post(cfg_post), .cfg_source(cfg_source), .cfg_mode(cfg_mode),
.cfg_falling(cfg_falling), .cfg_level(cfg_level), .cfg_hysteresis(cfg_hysteresis), .cfg_auto_timeout(cfg_auto_timeout),
.sample_data(sample_data), .sample_index(sample_index), .sample_valid(sample_valid && sample_ready),
.ext_level(ext_level), .active(active), .done(done), .fault(fault),
.trigger_index(trigger_index_live), .first_index(first_index_live), .record_length(record_length_live),
.trigger_event(trigger_event), .m_data(m_data), .m_index(m_index), .m_valid(m_valid), .m_ready(m_ready), .m_last(m_last)
);
always @(posedge clk) begin
if (core_rst) begin sample_count_live<=0; trigger_count_live<=0; end
else begin
if (sample_valid && sample_ready && sample_count_live != 64'hffffffffffffffff)
sample_count_live<=sample_count_live+1'b1;
if (trigger_event && trigger_count_live != 32'hffffffff)
trigger_count_live<=trigger_count_live+1'b1;
end
end
endmodule
+121
View File
@@ -0,0 +1,121 @@
`timescale 1ns/1ps
// Trigger decision is combinational for the currently accepted sample; the
// acquisition buffer can write that same sample as its trigger record.
module trigger_engine (
input wire clk,
input wire rst,
input wire arm,
input wire stop,
input wire session_fault,
input wire [2:0] cfg_source,
input wire [1:0] cfg_mode,
input wire cfg_falling,
input wire [9:0] cfg_level,
input wire [9:0] cfg_hysteresis,
input wire [31:0] cfg_auto_timeout,
input wire [63:0] sample_data,
input wire [63:0] sample_index,
input wire sample_valid,
input wire ext_level,
input wire eligible,
output wire trigger_hit,
output wire auto_hit,
output reg armed,
output reg [63:0] trigger_index,
output reg config_error
);
reg [2:0] active_source;
reg [1:0] active_mode;
reg active_falling;
reg [9:0] active_level, active_hysteresis;
reg [31:0] active_timeout, accepted_count;
reg crossing_armed;
reg ext_seen, ext_previous;
reg [9:0] selected_sample;
wire [10:0] high_sum = {1'b0, active_level} + {1'b0, active_hysteresis};
wire [9:0] high_threshold = high_sum > 11'd1023 ? 10'd1023 : high_sum[9:0];
wire [9:0] low_threshold = active_level > active_hysteresis ?
active_level - active_hysteresis : 10'd0;
always @* begin
case (active_source)
3'd0: selected_sample = sample_data[9:0];
3'd1: selected_sample = sample_data[25:16];
3'd2: selected_sample = sample_data[41:32];
3'd3: selected_sample = sample_data[57:48];
default: selected_sample = 10'd0;
endcase
end
wire analog_arm_condition = active_falling ?
(active_hysteresis == 0 ? selected_sample > active_level : selected_sample >= high_threshold) :
(active_hysteresis == 0 ? selected_sample < active_level : selected_sample <= low_threshold);
wire analog_cross_condition = active_falling ?
selected_sample <= low_threshold : selected_sample >= high_threshold;
wire analog_event = crossing_armed && analog_cross_condition;
wire external_event = ext_seen && (active_falling ?
(ext_previous && !ext_level) : (!ext_previous && ext_level));
wire edge_event = active_source == 3'd4 ? external_event : analog_event;
// Counter describes preceding accepted samples. N expires on sample N;
// zero expires immediately, but fallback still waits for eligibility.
wire auto_expired = active_timeout == 0 || accepted_count >= active_timeout - 1'b1;
wire auto_event = active_mode == 2'd0 && auto_expired;
assign trigger_hit = !rst && !arm && !stop && !session_fault && armed &&
sample_valid && eligible && (edge_event || auto_event);
assign auto_hit = trigger_hit && !edge_event && auto_event;
always @(posedge clk) begin
if (rst) begin
armed <= 0;
trigger_index <= 0;
config_error <= 0;
active_source <= 0;
active_mode <= 0;
active_falling <= 0;
active_level <= 0;
active_hysteresis <= 0;
active_timeout <= 0;
accepted_count <= 0;
crossing_armed <= 0;
ext_seen <= 0;
ext_previous <= 0;
end else if (stop || session_fault) begin
armed <= 0;
accepted_count <= 0;
crossing_armed <= 0;
ext_seen <= 0;
end else if (arm) begin
active_source <= cfg_source;
active_mode <= cfg_mode;
active_falling <= cfg_falling;
active_level <= cfg_level;
active_hysteresis <= cfg_hysteresis;
active_timeout <= cfg_auto_timeout;
armed <= (cfg_source <= 3'd4) && (cfg_mode <= 2'd2);
if (cfg_source > 3'd4 || cfg_mode > 2'd2)
config_error <= 1'b1;
trigger_index <= 0;
accepted_count <= 0;
crossing_armed <= 0;
ext_seen <= 0;
ext_previous <= 0;
end else if (armed && sample_valid) begin
if (accepted_count != 32'hffffffff)
accepted_count <= accepted_count + 1'b1;
if (active_source == 3'd4) begin
ext_previous <= ext_level;
ext_seen <= 1'b1;
end else if (analog_event) begin
// Consume the crossing even before sufficient prehistory;
// eligibility becoming true later must not replay this edge.
crossing_armed <= 1'b0;
end else if (analog_arm_condition) begin
crossing_armed <= 1'b1;
end
if (trigger_hit) begin
armed <= 1'b0;
trigger_index <= sample_index;
end
end
end
endmodule
@@ -0,0 +1,49 @@
`timescale 1ns/1ps
// Stream-domain composition; configuration applies atomically on ARM.
module triggered_acquisition #(
parameter integer ADDR_BITS = 8
) (
input wire clk, rst, arm, stop, session_fault,
input wire [31:0] cfg_pre, cfg_post,
input wire [2:0] cfg_source,
input wire [1:0] cfg_mode,
input wire cfg_falling,
input wire [9:0] cfg_level, cfg_hysteresis,
input wire [31:0] cfg_auto_timeout,
input wire [63:0] sample_data, sample_index,
input wire sample_valid, ext_level,
output wire active, done, fault,
output wire trigger_event,
output wire [63:0] trigger_index, first_index,
output wire [31:0] record_length,
output wire [63:0] m_data, m_index,
output wire m_valid,
input wire m_ready,
output wire m_last
);
wire eligible, hit, auto_hit, armed, config_error, buffer_fault;
wire [63:0] engine_trigger_index;
trigger_engine trigger_i (
.clk(clk), .rst(rst), .arm(arm), .stop(stop),
.session_fault(session_fault || buffer_fault),
.cfg_source(cfg_source), .cfg_mode(cfg_mode), .cfg_falling(cfg_falling),
.cfg_level(cfg_level), .cfg_hysteresis(cfg_hysteresis),
.cfg_auto_timeout(cfg_auto_timeout),
.sample_data(sample_data), .sample_index(sample_index),
.sample_valid(sample_valid), .ext_level(ext_level), .eligible(eligible),
.trigger_hit(hit), .auto_hit(auto_hit), .armed(armed),
.trigger_index(engine_trigger_index), .config_error(config_error)
);
acquisition_buffer #(.ADDR_BITS(ADDR_BITS)) buffer_i (
.clk(clk), .rst(rst), .arm(arm), .stop(stop),
.session_fault(session_fault || config_error),
.cfg_pre(cfg_pre), .cfg_post(cfg_post),
.sample_data(sample_data), .sample_index(sample_index),
.sample_valid(sample_valid), .trigger_hit(hit), .eligible(eligible),
.active(active), .done(done), .fault(buffer_fault),
.trigger_index(trigger_index), .first_index(first_index), .record_length(record_length),
.m_data(m_data), .m_index(m_index), .m_valid(m_valid), .m_ready(m_ready), .m_last(m_last)
);
assign fault = buffer_fault || config_error;
assign trigger_event = hit;
endmodule
@@ -0,0 +1,130 @@
`timescale 1ns/1ps
module tb_acquisition_buffer;
reg clk=0,rst=1,arm=0,stop=0,session_fault=0;
reg [31:0] cfg_pre=0,cfg_post=0;
reg [63:0] sample_data=0,sample_index=0;
reg sample_valid=0,trigger_hit=0,m_ready=0;
wire eligible,active,done,fault,m_valid,m_last;
wire [63:0] trigger_index,first_index,m_data,m_index;
wire [31:0] record_length;
integer checked=0,cases=0;
reg [63:0] stall_data,stall_index;
reg stall_last;
always #5 clk=~clk;
acquisition_buffer #(.ADDR_BITS(4)) dut(.*);
function automatic [63:0] pattern(input integer n);
pattern=64'h1234000000000000 | (n*64'h01000100010001);
endfunction
task reset_all;
begin
@(negedge clk); rst=1; arm=0; stop=0; session_fault=0;
sample_valid=0; trigger_hit=0; m_ready=0;
repeat(2) @(negedge clk);
rst=0;
end
endtask
task configure(input integer p,input integer q);
begin
@(negedge clk); cfg_pre=p;cfg_post=q; arm=1;
@(negedge clk); arm=0;
end
endtask
task send(input integer n,input reg trig);
begin
@(negedge clk);
sample_valid=1; sample_index=n;sample_data=pattern(n);trigger_hit=trig;
@(negedge clk); sample_valid=0;trigger_hit=0;
end
endtask
task drain(input integer first,input integer length);
integer got,c; reg was_stalled;
begin
got=0;c=0;was_stalled=0;
while(got<length) begin
@(negedge clk);
m_ready=(c%4 != 0); c=c+1;
@(posedge clk);
if (was_stalled && (!m_valid || m_data!==stall_data || m_index!==stall_index || m_last!==stall_last))
$fatal(1,"output changed while stalled");
was_stalled=m_valid&&!m_ready;
stall_data=m_data;stall_index=m_index;stall_last=m_last;
if(m_valid && m_ready) begin
if(m_index !== (first+got) || m_data!==pattern(first+got) || m_last !== (got==length-1))
$fatal(1,"incorrect pre/trigger/post record first=%0d got=%0d data=%h",first,got,m_data);
got=got+1;checked=checked+1;
end
end
@(negedge clk); m_ready=1;
repeat(3) @(negedge clk);
if(m_valid) $fatal(1,"extra record data");
cases=cases+1;
end
endtask
task window_case(input integer p,input integer q,input integer t);
integer n;
begin
reset_all();configure(p,q);
for(n=0;n<=t+q;n=n+1) send(n,n==t);
if(!done||active||fault||trigger_index!=t||first_index!=t-p||record_length!=p+q+1)
$fatal(1,"incorrect completion metadata");
// Extra live samples cannot overwrite a frozen record.
for(n=100;n<110;n=n+1) send(n,0);
drain(t-p,p+q+1);
end
endtask
initial begin
reset_all();
window_case(0,0,0);
window_case(0,8,6);
window_case(15,0,40);
window_case(7,8,45); // full RAM window spans ring wrap
window_case(3,4,19);
reset_all();configure(2,1);send(0,0);send(2,0);
if(!fault||active||done) $fatal(1,"sample gap accepted");
reset_all();configure(3,0);send(0,1);
if(!fault) $fatal(1,"early trigger accepted");
reset_all();configure(16,0);
if(!fault||active) $fatal(1,"oversize configuration accepted");
reset_all();configure(32'hffffffff,32'hffffffff);
if(!fault) $fatal(1,"configuration sum overflow accepted");
reset_all();configure(2,2);send(0,0);
@(negedge clk); stop=1;
@(negedge clk); stop=0;
if(active||done) $fatal(1,"STOP failed");
reset_all();configure(0,0);send(0,1);
repeat(2) @(negedge clk);
if(!m_valid) $fatal(1,"pending output absent");
stall_data=m_data;stall_index=m_index;stall_last=m_last;
session_fault=1;
repeat(3) @(negedge clk);
if(!fault||done||active||!m_valid||m_data!==stall_data||m_index!==stall_index||m_last!==stall_last)
$fatal(1,"fault corrupts held output");
session_fault=0;m_ready=1;
repeat(2) @(negedge clk);
if(m_valid) $fatal(1,"fault output did not drain");
reset_all();configure(0,1);send(0,0);
configure(0,0);
if(!fault||active) $fatal(1,"ARM restarted active acquisition");
reset_all();configure(0,0);send(0,1);
repeat(2) @(negedge clk);
stall_data=m_data;stall_index=m_index;stall_last=m_last;
stop=1;
repeat(2) @(negedge clk);
if(done||active||!m_valid||m_data!==stall_data||m_index!==stall_index||m_last!==stall_last)
$fatal(1,"STOP corrupts stalled output");
stop=0;m_ready=1;
repeat(2) @(negedge clk);
reset_all();configure(0,0);send(0,1);
repeat(2) @(negedge clk);
stall_data=m_data;stall_index=m_index;stall_last=m_last;
arm=1;
repeat(2) @(negedge clk);
if(!fault||active||!m_valid||m_data!==stall_data||m_index!==stall_index||m_last!==stall_last)
$fatal(1,"ARM replaces stalled output");
arm=0;m_ready=1;
repeat(2) @(negedge clk);
$display("PASS tb_acquisition_buffer cases=%0d samples=%0d pre_post_wrap_bounds_gap_stop_fault",cases,checked);
$finish;
end
initial begin #30000; $fatal(1,"timeout"); end
endmodule
@@ -0,0 +1,277 @@
`timescale 1ns/1ps
// Functional ADC pin model. Six conversion pipeline stages are followed by
// nonzero output propagation delay and bit-to-bit skew. These deliberately
// avoid a same-edge RTL stimulus; they are not a characterized PVT/SI model.
module adc_pin_model #(
parameter integer CHANNEL_BASE = 0
) (
input wire clk,
input wire pins_connected,
output reg [9:0] data_a,
output reg [9:0] data_b
);
reg [9:0] pipeline_a [0:5];
reg [9:0] pipeline_b [0:5];
reg [9:0] launched_a, launched_b;
integer conversion = 0;
integer i;
function automatic [9:0] pattern(input integer channel, input integer n);
integer phase, bit_number;
begin
phase = n % 120;
if (phase < 80)
pattern = (n * (2 * channel + 3) + channel * 137) & 1023;
else if (phase < 90) begin
bit_number = (phase - 80 + channel) % 10;
pattern = 10'b1 << bit_number;
end else if (phase < 100) begin
bit_number = (phase - 90 + channel) % 10;
pattern = ~(10'b1 << bit_number);
end else begin
case ((phase + channel) % 6)
0: pattern = 10'd0;
1: pattern = 10'd1023;
2: pattern = 10'd1;
3: pattern = 10'd1022;
4: pattern = 10'd511;
5: pattern = 10'd512;
endcase
end
end
endfunction
initial begin
data_a = 10'bx;
data_b = 10'bx;
for (i = 0; i < 6; i = i + 1) begin
pipeline_a[i] = 10'bx;
pipeline_b[i] = 10'bx;
end
end
always @(posedge clk) begin
launched_a = pins_connected ? pipeline_a[5] : 10'bx;
launched_b = pins_connected ? pipeline_b[5] : 10'bx;
for (i = 5; i > 0; i = i - 1) begin
pipeline_a[i] = pipeline_a[i-1];
pipeline_b[i] = pipeline_b[i-1];
end
pipeline_a[0] = pattern(CHANNEL_BASE, conversion);
pipeline_b[0] = pattern(CHANNEL_BASE + 1, conversion);
conversion = conversion + 1;
for (i = 0; i < 10; i = i + 1) begin
data_a[i] <= #(4.20 + i * 0.06) launched_a[i];
data_b[i] <= #(4.30 + (9-i) * 0.06) launched_b[i];
end
end
endmodule
// External-interface reference: pad sampling followed by one fabric stage;
// two reset-release stages, two READY stages and one READY warmup stage.
// Reference state is independent of DUT hierarchy and is updated before NBA.
module adc_capture_scoreboard #(
parameter integer CHIP = 1
) (
input wire clk, arst_n, ready_async,
input wire [9:0] data_a, data_b,
input wire [31:0] word_value,
input wire valid,
input wire [63:0] sample_index,
output integer checked_samples,
output reg [9:0] walking_one_a, walking_one_b,
output reg [9:0] walking_zero_a, walking_zero_b,
output reg [3:0] boundaries_a, boundaries_b
);
reg [1:0] reset_release = 0, ready_history = 0;
reg ready_warm = 0;
reg [19:0] pad_history = 20'bx;
reg [31:0] expected_word;
reg expected_valid;
reg [63:0] expected_next_index = 0, expected_index = 0;
reg local_reset;
integer bit_number;
initial begin
checked_samples = 0;
walking_one_a = 0; walking_one_b = 0;
walking_zero_a = 0; walking_zero_b = 0;
boundaries_a = 0; boundaries_b = 0;
end
always @(negedge arst_n) begin
reset_release = 0;
ready_history = 0;
end
always @(posedge clk) begin
local_reset = !reset_release[1];
expected_word = local_reset ? 32'd0 :
{6'b0, pad_history[19:10], 6'b0, pad_history[9:0]};
expected_valid = !local_reset && ready_history[1] && ready_warm;
if (local_reset) begin
expected_next_index = 0;
expected_index = 0;
ready_warm = 0;
end else begin
ready_warm = ready_history[1];
if (expected_valid) begin
expected_index = expected_next_index;
expected_next_index = expected_next_index + 1'b1;
end
end
ready_history = (!arst_n || local_reset) ? 2'b00 :
{ready_history[0], ready_async};
reset_release = !arst_n ? 2'b00 : {reset_release[0], 1'b1};
pad_history = {data_b, data_a};
#0.1;
if (valid !== expected_valid)
$fatal(1, "chip%0d valid/reset/READY latency at t=%0t: got %b expected %b",
CHIP, $time, valid, expected_valid);
if (sample_index !== expected_index)
$fatal(1, "chip%0d index/reset/retention at t=%0t: got %0d expected %0d",
CHIP, $time, sample_index, expected_index);
// The data stage is updated even while invalid. Checking every edge
// detects an unintended CE, an extra stage or a stale restart sample.
if (word_value !== expected_word)
$fatal(1, "chip%0d data/pipeline alignment at t=%0t: got %h expected %h",
CHIP, $time, word_value, expected_word);
if (valid) begin
if ($isunknown(word_value))
$fatal(1, "chip%0d unknown data was marked valid", CHIP);
if (word_value[15:10] !== 6'b0 || word_value[31:26] !== 6'b0)
$fatal(1, "chip%0d nonzero sample padding", CHIP);
checked_samples = checked_samples + 1;
for (bit_number = 0; bit_number < 10; bit_number = bit_number + 1) begin
if (word_value[9:0] == (10'b1 << bit_number))
walking_one_a[bit_number] = 1;
if (word_value[25:16] == (10'b1 << bit_number))
walking_one_b[bit_number] = 1;
if (word_value[9:0] == ~(10'b1 << bit_number))
walking_zero_a[bit_number] = 1;
if (word_value[25:16] == ~(10'b1 << bit_number))
walking_zero_b[bit_number] = 1;
end
case (word_value[9:0])
10'd0: boundaries_a[0] = 1;
10'd1023: boundaries_a[1] = 1;
10'd511: boundaries_a[2] = 1;
10'd512: boundaries_a[3] = 1;
endcase
case (word_value[25:16])
10'd0: boundaries_b[0] = 1;
10'd1023: boundaries_b[1] = 1;
10'd511: boundaries_b[2] = 1;
10'd512: boundaries_b[3] = 1;
endcase
end
end
endmodule
module tb_adc_capture;
reg arst_n = 1, mezz_ready = 0;
reg adc1_clk = 0, adc2_clk = 0;
reg pins_connected = 1;
wire [9:0] adc1_a, adc1_b, adc2_a, adc2_b;
wire [31:0] chip1_word, chip2_word;
wire chip1_valid, chip2_valid;
wire [63:0] chip1_index, chip2_index;
wire [31:0] count1, count2;
wire [9:0] ones1a, ones1b, zeros1a, zeros1b;
wire [9:0] ones2a, ones2b, zeros2a, zeros2b;
wire [3:0] bounds1a, bounds1b, bounds2a, bounds2b;
integer stopped_count1, stopped_count2;
reg [63:0] stopped_index1, stopped_index2;
always #5 adc1_clk = ~adc1_clk;
initial begin
#1.37;
forever #5.3 adc2_clk = ~adc2_clk;
end
adc_capture_dual dut(.*);
adc_pin_model #(.CHANNEL_BASE(0)) source1(
.clk(adc1_clk), .pins_connected(pins_connected), .data_a(adc1_a), .data_b(adc1_b));
adc_pin_model #(.CHANNEL_BASE(2)) source2(
.clk(adc2_clk), .pins_connected(pins_connected), .data_a(adc2_a), .data_b(adc2_b));
adc_capture_scoreboard #(.CHIP(1)) check1(
.clk(adc1_clk), .arst_n(arst_n), .ready_async(mezz_ready),
.data_a(adc1_a), .data_b(adc1_b), .word_value(chip1_word),
.valid(chip1_valid), .sample_index(chip1_index), .checked_samples(count1),
.walking_one_a(ones1a), .walking_one_b(ones1b),
.walking_zero_a(zeros1a), .walking_zero_b(zeros1b),
.boundaries_a(bounds1a), .boundaries_b(bounds1b));
adc_capture_scoreboard #(.CHIP(2)) check2(
.clk(adc2_clk), .arst_n(arst_n), .ready_async(mezz_ready),
.data_a(adc2_a), .data_b(adc2_b), .word_value(chip2_word),
.valid(chip2_valid), .sample_index(chip2_index), .checked_samples(count2),
.walking_one_a(ones2a), .walking_one_b(ones2b),
.walking_zero_a(zeros2a), .walking_zero_b(zeros2b),
.boundaries_a(bounds2a), .boundaries_b(bounds2b));
task automatic require_idle;
begin
if (chip1_valid !== 1'b0 || chip2_valid !== 1'b0)
$fatal(1, "valid outside a settled READY interval");
end
endtask
initial begin
// Reset release is deliberately unrelated to either forwarded clock.
#0.2 arst_n = 0;
#31.1 arst_n = 1;
#100 require_idle();
mezz_ready = 1;
#14000;
// READY loss permits only the defined synchronizer tail. Disconnect
// the bus after that tail: high-Z/unpowered data is modeled as X.
mezz_ready = 0;
#100 require_idle();
stopped_count1 = count1; stopped_count2 = count2;
stopped_index1 = chip1_index; stopped_index2 = chip2_index;
pins_connected = 0;
#200 require_idle();
if (count1 != stopped_count1 || count2 != stopped_count2 ||
chip1_index !== stopped_index1 || chip2_index !== stopped_index2)
$fatal(1, "READY-low interval advanced stream/index");
pins_connected = 1;
#140 mezz_ready = 1;
#500;
if (count1 <= stopped_count1 || count2 <= stopped_count2)
$fatal(1, "capture did not resume after READY restoration");
// Reset while disconnected must not release stale IOB contents.
mezz_ready = 0;
#83.7 require_idle();
pins_connected = 0;
#17.2 arst_n = 0;
#31.9 arst_n = 1;
#150 require_idle();
if (chip1_index !== 64'd0 || chip2_index !== 64'd0)
$fatal(1, "reset did not clear sample indices");
pins_connected = 1;
#140 mezz_ready = 1;
#2000;
// Also reset during active capture with READY already asserted.
#13.1 arst_n = 0;
#27.3 arst_n = 1;
#400;
if (chip1_valid !== 1'b1 || chip2_valid !== 1'b1)
$fatal(1, "active-READY reset recovery failed");
if (count1 < 1600 || count2 < 1500)
$fatal(1, "insufficient independent samples: %0d/%0d", count1, count2);
if ({ones1a, ones1b, ones2a, ones2b, zeros1a, zeros1b, zeros2a, zeros2b}
!== {80{1'b1}})
$fatal(1, "incomplete walking-one/zero coverage");
if ({bounds1a, bounds1b, bounds2a, bounds2b} !== 16'hffff)
$fatal(1, "incomplete boundary-code coverage");
$display("PASS tb_adc_capture samples=%0d/%0d delayed/skewed pins, ramps, walking bits, boundaries, reset and READY", count1, count2);
$finish;
end
initial begin
#30000;
$fatal(1, "tb_adc_capture timeout");
end
endmodule
@@ -0,0 +1,61 @@
`timescale 1ns/1ps
module tb_adc_frontend4;
reg arst_n=1, mezz_ready=0, adc1_clk=0, adc2_clk=0, stream_clk=0;
reg coherent_epoch_valid=0, sample_ready=1;
reg [9:0] adc1_a=0, adc1_b=200, adc2_a=400, adc2_b=600;
wire [63:0] sample_data,sample_index;
wire sample_valid,session_fault;
wire [31:0] adc1_overflow_count,adc2_overflow_count;
reg [63:0] next_index=0;
reg [9:0] first_code;
integer received=0, cycles=0;
reg checking=1;
always #5 adc1_clk=~adc1_clk;
initial begin #0.8; forever #5 adc2_clk=~adc2_clk; end
always #3 stream_clk=~stream_clk;
// Model a known common epoch only in this test. Both domains see the
// same conversion before each capture edge; hardware still needs proof.
always @(negedge adc1_clk) begin
adc1_a<=adc1_a+1'b1; adc1_b<=adc1_b+1'b1;
adc2_a<=adc2_a+1'b1; adc2_b<=adc2_b+1'b1;
end
adc_frontend4 #(.FIFO_ADDR_BITS(4)) dut (.*);
always @(negedge stream_clk) begin
cycles=cycles+1;
if (checking) sample_ready=(cycles%7 != 0);
end
always @(posedge stream_clk) begin
if (checking && sample_valid && sample_ready) begin
if (sample_index!==next_index) $fatal(1,"frontend index gap");
if (received==0) first_code=sample_data[9:0];
if (sample_data[9:0] !== ((first_code+next_index)&1023) ||
sample_data[25:16] !== ((first_code+next_index+200)&1023) ||
sample_data[41:32] !== ((first_code+next_index+400)&1023) ||
sample_data[57:48] !== ((first_code+next_index+600)&1023))
$fatal(1,"four-channel conversion association");
if ((sample_data & 64'hfc00fc00fc00fc00) !== 0) $fatal(1,"zero alignment");
received=received+1; next_index=next_index+1;
end
end
initial begin
#1 arst_n=0;
#30 arst_n=1;
#40 mezz_ready=1;
#70;
if (sample_valid) $fatal(1,"pairing without epoch permission");
coherent_epoch_valid=1;
#7000;
if (session_fault || adc1_overflow_count || adc2_overflow_count || received<690)
$fatal(1,"continuous four-channel frontend");
checking=0; sample_ready=0;
#500;
if (!session_fault) $fatal(1,"backpressure overflow not invalidated");
arst_n=0; mezz_ready=0; coherent_epoch_valid=0;
#40 arst_n=1;
#40;
if (session_fault || sample_valid) $fatal(1,"joint reset not clearing frontend");
$display("PASS tb_adc_frontend4 samples=%0d four_channels CDC_stalls_epoch_guard_overflow",received);
$finish;
end
initial begin #15000; $fatal(1,"timeout"); end
endmodule
@@ -0,0 +1,253 @@
`timescale 1ns/1ps
module tb_async_sample_fifo;
localparam integer WIDTH = 96;
localparam integer ADDR_BITS = 4;
localparam integer DEPTH = 1 << ADDR_BITS;
reg arst_n = 1;
reg wr_clk = 0, rd_clk = 0;
reg wr_clock_running = 1, rd_clock_running = 1;
real wr_half_period = 5.0, rd_half_period = 7.1;
reg wr_valid = 0, rd_ready = 0;
reg [WIDTH-1:0] wr_data = 0;
wire wr_ready, wr_overflow, wr_overflow_sticky, rd_valid;
wire [31:0] wr_overflow_count;
wire [WIDTH-1:0] rd_data;
integer writer_mode = 0, reader_mode = 0;
reg [31:0] wr_random = 32'h87293a11, rd_random = 32'h171395ba;
reg [63:0] offered_index = 0;
// External accepted-word scoreboard; rejected offers never enter it.
reg [WIDTH-1:0] expected_words [0:32767];
integer expected_head = 0, expected_tail = 0;
integer accepted_total = 0, read_total = 0, overflow_total = 0;
integer stalls_checked = 0, full_cycles = 0;
reg [31:0] expected_overflows = 0;
reg expected_overflow_pulse;
reg previous_stall = 0;
reg check_burst = 0, burst_started = 0;
integer burst_reads = 0;
reg [WIDTH-1:0] stalled_payload;
integer accepted_before, dropped_before, queued_before_reset;
async_sample_fifo #(.WIDTH(WIDTH), .ADDR_BITS(ADDR_BITS)) dut(.*);
always begin
#(wr_half_period);
if (wr_clock_running) wr_clk = ~wr_clk;
else wr_clk = 0;
end
initial begin
// Fractional phase avoids coincident monitor edges at the initial rates.
#1.371;
forever begin
#(rd_half_period);
if (rd_clock_running) rd_clk = ~rd_clk;
else rd_clk = 0;
end
end
always @(negedge wr_clk) begin
wr_random = wr_random * 32'd1664525 + 32'd1013904223;
wr_valid = arst_n && ((writer_mode == 2) ||
((writer_mode == 1) && wr_random[31:24] < 8'd190));
if (wr_valid) begin
wr_data = {offered_index, (32'ha59c3721 ^ offered_index[31:0])};
offered_index = offered_index + 1'b1;
end
end
always @(negedge rd_clk) begin
rd_random = rd_random * 32'd1664525 + 32'd1013904223;
rd_ready = arst_n && ((reader_mode == 2) ||
((reader_mode == 1) && rd_random[31:24] < 8'd137));
end
always @(negedge arst_n) begin
expected_head = 0;
expected_tail = 0;
expected_overflows = 0;
previous_stall = 0;
end
always @(posedge wr_clk) begin
expected_overflow_pulse = 0;
if (arst_n) begin
if (wr_valid && wr_ready) begin
if (expected_tail >= 32768)
$fatal(1, "scoreboard capacity exceeded");
expected_words[expected_tail] = wr_data;
expected_tail = expected_tail + 1;
accepted_total = accepted_total + 1;
end else if (wr_valid && !wr_ready) begin
// Offers are only enabled after reset-release warmup below.
expected_overflow_pulse = 1;
if (expected_overflows != 32'hffffffff)
expected_overflows = expected_overflows + 1'b1;
overflow_total = overflow_total + 1;
end
if (!wr_ready) full_cycles = full_cycles + 1;
end
#0.1;
if (wr_overflow !== expected_overflow_pulse)
$fatal(1, "wrong overflow pulse at t=%0t", $time);
if (wr_overflow_count !== expected_overflows ||
wr_overflow_sticky !== (expected_overflows != 0))
$fatal(1, "wrong overflow counter/sticky: got %0d expected %0d at t=%0t",
wr_overflow_count, expected_overflows, $time);
end
always @(posedge rd_clk) begin
if (arst_n) begin
if (check_burst && burst_started && expected_head < expected_tail &&
!(rd_valid && rd_ready))
$fatal(1, "bubble in sustained ready/read burst at t=%0t", $time);
if (check_burst && rd_valid && rd_ready) begin
burst_started = 1;
burst_reads = burst_reads + 1;
end
if (previous_stall && (rd_valid !== 1'b1 || rd_data !== stalled_payload))
$fatal(1, "read payload/valid changed through backpressure at t=%0t", $time);
if (rd_valid && rd_ready) begin
if (expected_head >= expected_tail)
$fatal(1, "read from empty scoreboard at t=%0t", $time);
if (rd_data !== expected_words[expected_head])
$fatal(1, "read order/data mismatch at %0d: got %h expected %h",
expected_head, rd_data, expected_words[expected_head]);
expected_head = expected_head + 1;
read_total = read_total + 1;
end
previous_stall = rd_valid && !rd_ready;
if (previous_stall) begin
stalled_payload = rd_data;
stalls_checked = stalls_checked + 1;
end
end
#0.1;
if (!arst_n && rd_valid !== 1'b0)
$fatal(1, "read remained valid during shared reset");
if (rd_valid) begin
if (expected_head >= expected_tail || rd_data !== expected_words[expected_head])
$fatal(1, "prefetched output is stale/out of order at t=%0t", $time);
end
end
task automatic wait_empty;
integer attempt;
begin : drain_wait
for (attempt = 0; attempt < 1000; attempt = attempt + 1) begin
@(posedge rd_clk);
#0.3;
if (expected_head == expected_tail && rd_valid === 1'b0)
disable drain_wait;
end
$fatal(1, "FIFO failed to drain: pending=%0d", expected_tail - expected_head);
end
endtask
task automatic stop_writer;
begin
writer_mode = 0;
repeat (3) @(posedge wr_clk);
#0.3;
end
endtask
initial begin
#0.2 arst_n = 0;
#29.3 arst_n = 1;
#100;
if (rd_valid !== 1'b0 || wr_ready !== 1'b1)
$fatal(1, "incorrect initial empty/ready state");
// Stalled reader lets RAM fill; the prefetched output adds one slot.
writer_mode = 2;
repeat (80) @(posedge wr_clk);
#0.3;
stop_writer();
if (expected_tail - expected_head != DEPTH + 1 || wr_ready !== 1'b0)
$fatal(1, "incorrect full capacity: got %0d expected %0d",
expected_tail - expected_head, DEPTH + 1);
if (expected_overflows == 0 || rd_valid !== 1'b1)
$fatal(1, "full/overflow/output-prefetch was not exercised");
check_burst = 1;
reader_mode = 2;
wait_empty();
check_burst = 0;
if (burst_reads != DEPTH + 1)
$fatal(1, "sustained burst did not consume full capacity" );
repeat (6) @(posedge wr_clk);
#0.3;
if (wr_ready !== 1'b1)
$fatal(1, "full flag failed to clear after drain");
// Fast writer, slower reader, randomized gaps and backpressure.
writer_mode = 1; reader_mode = 1;
repeat (5000) @(posedge wr_clk);
#0.233;
stop_writer();
reader_mode = 2;
wait_empty();
// Reverse relative clock rates to exercise sustained empty transitions.
wr_half_period = 8.7; rd_half_period = 3.5;
writer_mode = 1; reader_mode = 1;
repeat (3000) @(posedge wr_clk);
#0.233;
stop_writer();
reader_mode = 2;
wait_empty();
if (read_total < DEPTH * 100 || stalls_checked < 100)
$fatal(1, "insufficient wrap/backpressure coverage");
// Populate again, pause both clocks, then jointly reset. Valid/ready
// must drop without an edge, and queued data must be discarded.
reader_mode = 0;
repeat (4) @(posedge rd_clk);
#0.233 writer_mode = 2;
repeat (80) @(posedge wr_clk);
#0.233;
stop_writer();
queued_before_reset = expected_tail - expected_head;
if (queued_before_reset != DEPTH + 1)
$fatal(1, "queued-reset setup did not fill FIFO");
wr_clock_running = 0; rd_clock_running = 0;
#40;
arst_n = 0;
#3;
if (rd_valid !== 1'b0 || wr_ready !== 1'b0 || wr_overflow !== 1'b0 ||
wr_overflow_sticky !== 1'b0 || wr_overflow_count !== 32'd0)
$fatal(1, "paused-clock shared reset did not clear flags");
#17 arst_n = 1;
#80;
if (rd_valid !== 1'b0 || wr_ready !== 1'b0)
$fatal(1, "reset released without local clock edges");
// Writer may recover first; reader remains reset while its clock is
// paused. Only the RAM slots are available until read prefetch resumes.
wr_clock_running = 1;
repeat (5) @(posedge wr_clk);
#0.233;
accepted_before = accepted_total;
dropped_before = overflow_total;
writer_mode = 2;
repeat (80) @(posedge wr_clk);
#0.233;
stop_writer();
if (accepted_total - accepted_before != DEPTH ||
overflow_total <= dropped_before || rd_valid !== 1'b0)
$fatal(1, "writer-first recovery/paused-reader capacity failed");
reader_mode = 2;
rd_clock_running = 1;
wait_empty();
repeat (6) @(posedge wr_clk);
#0.3;
if (wr_ready !== 1'b1 || rd_valid !== 1'b0)
$fatal(1, "incorrect final empty/ready state");
$display("PASS tb_async_sample_fifo accepted=%0d read=%0d rejected=%0d stalls=%0d discarded_on_reset=%0d",
accepted_total, read_total, overflow_total, stalls_checked, queued_before_reset);
$finish;
end
initial begin
#300000;
$fatal(1, "tb_async_sample_fifo timeout");
end
endmodule

Some files were not shown because too many files have changed in this diff Show More