Пакеты и идентификаторы
Вы узнаете
Как описывается пакет, зачем нужен ID и что проверяет стенд AXI4.
Пакет — это один адрес и счётчик: AXI_LEN_WIDTH 8 означает до 256 тактов данных под одним адресом, а ID позволяет транзакциям мастера перемежаться и всё же приходить по порядку. Стенд это гоняет: test_reset_state, test_single_write, test_single_read, test_write_read_roundtrip, test_multiple_writes, восемь тестов. Запись опускает тестбенч-спеку AXI4 в Verilog.
Попробовать
В записи найдите тесты стенда; затем в спеке вычислите потолок пакета, который допускает ширина LEN, и скажите, что покупает ID.

The AXI4 testbench spec lowered to Verilog: what a bench looks like when the compiler emits it.
specs/fpga/testbench/axi4_tb.t27
// SPDX-License-Identifier: Apache-2.0
// t27/specs/fpga/testbench/axi4_tb.t27
// AXI4 Bus Testbench Specification
// Tests AXI4 read/write channels, burst support, and protocol compliance
// phi^2 + 1/phi^2 = 3 | TRINITY
module AXI4_Testbench {
use fpga::axi4::Axi4;
const CLK_PERIOD : u32 = 20;
const SIM_TIMEOUT : u32 = 10_000_000;
const ADDR_WIDTH : u32 = 32;
const DATA_WIDTH : u32 = 32;
const ID_WIDTH : u32 = 4;
const MAX_BURST_LEN : u32 = 256;
var clk : bool = false;
var rst_n : bool = false;
// AW channel (write address)
var aw_valid : bool = false;
var aw_ready : bool = false;
var aw_addr : u32 = 0;
var aw_id : u32 = 0;
var aw_len : u32 = 0;
var aw_size : u32 = 2;
var aw_burst : u32 = 1;
// W channel (write data)
var w_valid : bool = false;
var w_ready : bool = false;
var w_data : u32 = 0;
var w_strb : u32 = 0xF;
var w_last : bool = false;
// B channel (write response)
var b_valid : bool = false;
var b_ready : bool = false;
var b_resp : u32 = 0;
// AR channel (read address)
var ar_valid : bool = false;
var ar_ready : bool = false;
var ar_addr : u32 = 0;
var ar_id : u32 = 0;
var ar_len : u32 = 0;
var ar_size : u32 = 2;
var ar_burst : u32 = 1;
// R channel (read data)
var r_valid : bool = false;
var r_ready : bool = false;
var r_data : u32 = 0;
var r_resp : u32 = 0;
var r_last : bool = false;
var test_passed : u32 = 0;
var test_failed : u32 = 0;
fn tick() {
// Toggle clock
clk = !clk;
// Handle write address channel
if aw_valid && aw_ready {
aw_ready = false;
}
// Handle write data channel
if w_valid && w_ready {
w_ready = false;
}
// Handle write response channel
if b_valid && b_ready {
b_valid = false;
b_ready = false;
}
// Handle read address channel
if ar_valid && ar_ready {
ar_ready = false;
}
// Handle read data channel
if r_valid && r_ready {
r_valid = false;
r_ready = false;
}
}
fn reset() {
rst_n = false;
tick();
tick();
rst_n = true;
tick();
}
fn axi_write_single(addr : u32, data : u32) -> u32 {
aw_valid = true;
aw_addr = addr;
aw_len = 0;
aw_size = 2;
aw_burst = 1;
tick();
while !aw_ready { tick(); }
aw_valid = false;
w_valid = true;
w_data = data;
w_strb = 0xF;
w_last = true;
tick();
while !w_ready { tick(); }
w_valid = false;
b_ready = true;
while !b_valid { tick(); }
var resp : u32 = b_resp;
b_ready = false;
return resp;
}
fn axi_read_single(addr : u32) -> u32 {
ar_valid = true;
ar_addr = addr;
ar_len = 0;
ar_size = 2;
ar_burst = 1;
tick();
while !ar_ready { tick(); }
ar_valid = false;
r_ready = true;
while !r_valid { tick(); }
var data : u32 = r_data;
r_ready = false;
return data;
}
test test_reset_state {
reset();
invariant aw_ready == false || aw_ready == true;
invariant ar_ready == false || ar_ready == true;
}
test test_single_write {
reset();
var resp : u32 = axi_write_single(0x1000, 0xDEADBEEF);
invariant resp == 0;
}
test test_single_read {
reset();
var data : u32 = axi_read_single(0x1000);
invariant data == 0xDEADBEEF;
}
test test_write_read_roundtrip {
reset();
axi_write_single(0x2000, 0x12345678);
var data : u32 = axi_read_single(0x2000);
invariant data == 0x12345678;
}
test test_multiple_writes {
reset();
var i : u32 = 0;
while i < 8 {
axi_write_single(0x1000 + i * 4, i);
i = i + 1;
}
i = 0;
while i < 8 {
var data : u32 = axi_read_single(0x1000 + i * 4);
invariant data == i;
i = i + 1;
}
}
test test_aligned_address {
reset();
var resp : u32 = axi_write_single(0x0000, 0xAA);
invariant resp == 0;
}
test test_burst_len_zero_means_single {
invariant MAX_BURST_LEN == 256;
}
test "test_tick_function" {
// Test that tick() function can be called without errors
tick();
}
invariant data_width_power_of_2 : DATA_WIDTH == 32 || DATA_WIDTH == 64;
invariant addr_width_valid : ADDR_WIDTH == 32 || ADDR_WIDTH == 64;
bench bench_axi_throughput {
reset();
var i : u32 = 0;
while i < 64 {
axi_write_single(0x4000 + i * 4, i * i);
i = i + 1;
}
i = 0;
while i < 64 {
axi_read_single(0x4000 + i * 4);
i = i + 1;
}
}
}
Все уроки
Модуль 1 · Что такое шина
Зачем вообще существует шина: разговор по проводам, с кадрами и адресами, и кому позволено говорить.
Модуль 2 · Разговор по UART
Двухпроводная шина без такта: кадр, делитель, задающий скорость, и статус, который опрашивает драйвер.
Модуль 3 · SPI под тактом
Разговор под тактом: четыре режима, лестница предделителей и выбор кристалла на каждого подчинённого.
Модуль 4 · Регистровая шина APB
Шина регистров: PSEL и PENABLE, строобы и ожидания, и сколько адресных бит стоит количество периферии.
Модуль 5 · Пять каналов AXI4
Пять каналов: адрес, данные и отклик в обе стороны, lite или full, пакеты и идентификаторы.
Модуль 6 · Память
Что находится на дальнем конце каждой шины: карты памяти, типы портов и задержка, которую должно покрывать ожидание.
Модуль 7 · Мосты
Почему в дизайне появляется больше одной шины и пакетный мост, переносящий работу между ними.
Модуль 8 · Ethernet: кадры и тайминг
Кадры, контрольная сумма кадра, тайминг RGMII и заранее зарегистрированные шаги настоящего запуска.
Модуль 9 · Стенд
Дисциплина, оберегающая настоящее железо: кто держит IO, как взять и вернуть захват, и что запускается дальше.