Добавить общие блоки Zynq, Ethernet DSView и декодер ШИМ
This commit is contained in:
@@ -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
|
||||
|
||||
| Библиотека | Что делает | Зависимости | Что нужно от платформы |
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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. */
|
||||
@@ -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 не требуется.
|
||||
@@ -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
@@ -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 из одного файла. */
|
||||
@@ -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 и допустимую частоту определяет проект платы.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user