Стенд: ЦАП PCM5102A, кнопка усиления, линия LVDS, UART через BL616
- вторая ветвь тракта: отсчёты ФНЧ -> пересчёт в 48 828 Гц -> I2S на PCM5102A (dac_stream.v, i2s_transmitter.v), тест tb_dac_stream.v; - кнопка S1 (H11): усиление ветви ЦАП ступенями по +6 дБ, показ ступени светодиодом (gain_button.vhd), тест tb_gain_button.vhd; - микрофон через линию SN65LVDS: точка выборки MIC_SMP_CNT = 12 по измерению на плате (pdm_rx.vhd), в tb_top модель линии; - новая распиновка: MIC_CLK K8, MIC_DAT K9, ЦАП G1/G2/L4/L3, UART_TX C3 на встроенный мост BL616; - сборка под GW5A-25 ревизии A, настройки PnR (CPU/SSPI/READY как GPIO); - отладочный выход PLL 80 МГц на E3.
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
`timescale 1ns / 1ps
|
||||
// =============================================================================
|
||||
// Module: tb_dac_stream
|
||||
// Project Name: DigMic_prj
|
||||
// Description:
|
||||
// Тест тракта на ЦАП: отсчёты 12 500 Гц -> dac_stream -> I2S на выводах ->
|
||||
// разбор слов в тестбенче, как это делает PCM5102A (по фронту BCK, MSB
|
||||
// через один BCK после смены LRCK).
|
||||
//
|
||||
// Каждое слово левого канала сверяется с эталоном двумя способами:
|
||||
// точно -- та же целочисленная интерполяция, что должна быть в ПЛИС,
|
||||
// с тем же моментом отсчёта; ловит ошибки разрядности, знака и сдвига;
|
||||
// приближённо -- интерполяция в вещественных числах; ловит ошибку в
|
||||
// самой формуле доли интервала.
|
||||
// Правый канал должен совпасть с левым. Отдельно меряются периоды SCK,
|
||||
// BCK и LRCK.
|
||||
//
|
||||
// WAVE = 0 -- синус AMP на FREQ; WAVE = 1 -- меандр между крайними кодами
|
||||
// шкалы, самые большие разности соседних отсчётов.
|
||||
//
|
||||
// Усиление: GAIN -- постоянное значение на входе gain. GAIN_STEP > 0 --
|
||||
// каждые GAIN_STEP входных отсчётов gain растёт на 1 по кругу 0..15, то есть
|
||||
// заходит и за GAIN_MAX: там dac_stream обязан ограничить сдвиг. Эталон берёт
|
||||
// то усиление, что действовало в такт взятия каждого отсчёта.
|
||||
// Разобранные отсчёты пишутся в OUT_FILE для анализа спектра.
|
||||
//
|
||||
// iverilog -g2012 -o build/tb_dac_stream tb/tb_dac_stream.v src/dac_stream.v src/i2s_transmitter.v
|
||||
// vvp build/tb_dac_stream
|
||||
// =============================================================================
|
||||
|
||||
module tb_dac_stream;
|
||||
|
||||
parameter integer GAIN = 0;
|
||||
parameter integer GAIN_MAX = 8;
|
||||
parameter integer GAIN_STEP = 0;
|
||||
parameter integer WAVE = 0;
|
||||
parameter real AMP = 0.5; // доля шкалы
|
||||
parameter real FREQ = 1000.0;
|
||||
parameter integer N_WORDS = 2500; // сколько слов ЦАП проверить
|
||||
parameter OUT_FILE = "build/dac_stream_out.txt";
|
||||
|
||||
localparam integer CLK_HZ = 50000000;
|
||||
localparam integer IN_HZ = 12500;
|
||||
localparam integer IN_PERIOD = CLK_HZ / IN_HZ;
|
||||
localparam integer RECIP = (1 << 26) / IN_PERIOD;
|
||||
localparam integer FS_MAX = 8388607;
|
||||
localparam integer FS_MIN = -8388608;
|
||||
|
||||
// От фронта clk, на котором сменился LRCK, назад до такта, чьё состояние
|
||||
// попало в слово: 16 тактов от sample_tick до начала кадра и 3 ступени
|
||||
// конвейера интерполяции
|
||||
localparam integer LAT = 19;
|
||||
|
||||
reg clk = 1'b0;
|
||||
always #10 clk = ~clk;
|
||||
|
||||
integer cyc = 0; // номер текущего фронта clk
|
||||
always @(posedge clk) cyc <= cyc + 1;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Источник отсчётов: строб раз в IN_PERIOD тактов, выставляется по спаду
|
||||
// -------------------------------------------------------------------------
|
||||
reg [23:0] din = 24'd0;
|
||||
reg din_valid = 1'b0;
|
||||
integer ph = IN_PERIOD - 1234; // произвольная фаза относительно I2S
|
||||
integer k_in = 0;
|
||||
|
||||
function integer gen(input integer k);
|
||||
real v;
|
||||
begin
|
||||
if (WAVE == 0) begin
|
||||
v = AMP * FS_MAX * $sin(2.0 * 3.14159265358979 * FREQ * k / IN_HZ);
|
||||
gen = (v >= 0.0) ? $rtoi(v + 0.5) : -$rtoi(-v + 0.5);
|
||||
end else begin
|
||||
gen = ((k / 7) % 2) ? FS_MIN : FS_MAX;
|
||||
end
|
||||
end
|
||||
endfunction
|
||||
|
||||
function integer sat_gain(input integer x, input integer g);
|
||||
reg signed [63:0] w;
|
||||
begin
|
||||
w = x;
|
||||
w = w <<< ((g > GAIN_MAX) ? GAIN_MAX : g);
|
||||
if (w > FS_MAX) sat_gain = FS_MAX;
|
||||
else if (w < FS_MIN) sat_gain = FS_MIN;
|
||||
else sat_gain = w;
|
||||
end
|
||||
endfunction
|
||||
|
||||
reg [3:0] gain = GAIN;
|
||||
|
||||
always @(negedge clk) begin
|
||||
din_valid <= 1'b0;
|
||||
if (ph == IN_PERIOD - 1) begin
|
||||
ph <= 0;
|
||||
din <= gen(k_in);
|
||||
din_valid <= 1'b1;
|
||||
k_in <= k_in + 1;
|
||||
end else begin
|
||||
ph <= ph + 1;
|
||||
// Смена усиления в середине интервала между отсчётами
|
||||
if (GAIN_STEP > 0 && ph == IN_PERIOD / 2 && k_in % GAIN_STEP == 0)
|
||||
gain <= gain + 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
// История входа: на каком фронте ПЛИС взяла отсчёт и что получила после усиления
|
||||
integer hist_t [0:8191];
|
||||
integer hist_x [0:8191];
|
||||
integer n_hist = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (din_valid) begin
|
||||
hist_t[n_hist] = cyc;
|
||||
hist_x[n_hist] = sat_gain($signed(din), gain);
|
||||
n_hist = n_hist + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Эталон: что должно быть на выходе по состоянию после фронта s
|
||||
// -------------------------------------------------------------------------
|
||||
// Состояние кладётся в e_cur, e_prev, e_cnt: они же нужны для
|
||||
// вещественного эталона и сообщений об ошибке
|
||||
integer e_cur, e_prev, e_cnt;
|
||||
|
||||
function integer ref_exact(input integer s);
|
||||
integer i;
|
||||
reg signed [63:0] d, f, p;
|
||||
begin
|
||||
i = n_hist - 1;
|
||||
while (i >= 0 && hist_t[i] > s)
|
||||
i = i - 1;
|
||||
e_cur = (i >= 0) ? hist_x[i] : 0;
|
||||
e_prev = (i >= 1) ? hist_x[i - 1] : 0;
|
||||
e_cnt = (i >= 0) ? s - hist_t[i] : s;
|
||||
if (e_cnt > IN_PERIOD - 1)
|
||||
e_cnt = IN_PERIOD - 1;
|
||||
|
||||
d = e_cur - e_prev;
|
||||
f = (e_cnt * RECIP) >>> 10;
|
||||
p = (d * f + 32768) >>> 16;
|
||||
ref_exact = e_prev + p;
|
||||
end
|
||||
endfunction
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Устройство
|
||||
// -------------------------------------------------------------------------
|
||||
wire i2s_bck, i2s_lrck, i2s_din, dac_sck;
|
||||
|
||||
dac_stream #(
|
||||
.CLK_FREQ_HZ (CLK_HZ),
|
||||
.IN_RATE_HZ (IN_HZ),
|
||||
.SAMPLE_RATE_HZ (48000),
|
||||
.GAIN_MAX (GAIN_MAX)
|
||||
) dut (
|
||||
.clk (clk),
|
||||
.din (din),
|
||||
.din_valid (din_valid),
|
||||
.gain (gain),
|
||||
.i2s_bck (i2s_bck),
|
||||
.i2s_lrck (i2s_lrck),
|
||||
.i2s_din (i2s_din),
|
||||
.dac_sck (dac_sck)
|
||||
);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Начало кадра: спад LRCK, в тактах clk
|
||||
// -------------------------------------------------------------------------
|
||||
reg lrck_d = 1'b0;
|
||||
integer p0 = -1; // фронт clk, на котором LRCK ушёл в 0
|
||||
integer p0_prev = -1;
|
||||
integer errors = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
lrck_d <= i2s_lrck;
|
||||
if (lrck_d && !i2s_lrck) begin
|
||||
p0_prev = p0;
|
||||
p0 = cyc - 1;
|
||||
if (p0_prev >= 0 && p0 - p0_prev != 1024) begin
|
||||
$display("ERROR: LRCK period %0d clk, want 1024", p0 - p0_prev);
|
||||
errors = errors + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Периоды SCK и BCK
|
||||
integer t_sck = -1, t_bck = -1;
|
||||
always @(posedge dac_sck) begin
|
||||
if (t_sck >= 0 && cyc - t_sck != 4) begin
|
||||
$display("ERROR: SCK period %0d clk, want 4", cyc - t_sck);
|
||||
errors = errors + 1;
|
||||
end
|
||||
t_sck = cyc;
|
||||
end
|
||||
always @(posedge i2s_bck) begin
|
||||
if (t_bck >= 0 && cyc - t_bck != 16) begin
|
||||
$display("ERROR: BCK period %0d clk, want 16", cyc - t_bck);
|
||||
errors = errors + 1;
|
||||
end
|
||||
t_bck = cyc;
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Приём I2S как в ЦАП
|
||||
// -------------------------------------------------------------------------
|
||||
reg lr_b = 1'b0;
|
||||
integer bitn = 99;
|
||||
reg [23:0] sh = 24'd0;
|
||||
integer left = 0;
|
||||
integer n_w = 0;
|
||||
integer n_chk = 0;
|
||||
integer max_dev = 0;
|
||||
integer fd;
|
||||
|
||||
integer got, want, dev;
|
||||
real ideal, tol;
|
||||
|
||||
always @(posedge i2s_bck) begin
|
||||
if (i2s_lrck !== lr_b) begin
|
||||
lr_b = i2s_lrck;
|
||||
bitn = 0; // бит задержки I2S
|
||||
end else if (bitn < 24) begin
|
||||
sh = {sh[22:0], i2s_din};
|
||||
bitn = bitn + 1;
|
||||
if (bitn == 24) begin
|
||||
got = $signed(sh);
|
||||
if (!lr_b) begin
|
||||
left = got;
|
||||
n_w = n_w + 1;
|
||||
// Первые кадры пропускаем: там ещё нет двух отсчётов в истории
|
||||
if (n_hist >= 3) begin
|
||||
want = ref_exact(p0 - LAT);
|
||||
if (got !== want) begin
|
||||
if (errors < 10)
|
||||
$display("ERROR: word %0d at clk %0d: got %0d, want %0d (cnt %0d)",
|
||||
n_w, p0, got, want, e_cnt);
|
||||
errors = errors + 1;
|
||||
end
|
||||
ideal = e_prev + (e_cur - e_prev) * (e_cnt * 1.0 / IN_PERIOD);
|
||||
tol = ((e_cur > e_prev) ? e_cur - e_prev : e_prev - e_cur) * 3.0e-5 + 1.0;
|
||||
dev = (got > ideal) ? $rtoi(got - ideal + 0.5) : $rtoi(ideal - got + 0.5);
|
||||
if (dev > max_dev)
|
||||
max_dev = dev;
|
||||
if (dev > tol) begin
|
||||
if (errors < 10)
|
||||
$display("ERROR: word %0d: got %0d, ideal %0f, tol %0f",
|
||||
n_w, got, ideal, tol);
|
||||
errors = errors + 1;
|
||||
end
|
||||
n_chk = n_chk + 1;
|
||||
$fdisplay(fd, "%0d %0d", p0, got);
|
||||
end
|
||||
end else if (got !== left) begin
|
||||
$display("ERROR: right %0d != left %0d", got, left);
|
||||
errors = errors + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
initial begin
|
||||
fd = $fopen(OUT_FILE, "w");
|
||||
$display("tb_dac_stream: GAIN=%0d GAIN_MAX=%0d GAIN_STEP=%0d WAVE=%0d AMP=%0f",
|
||||
GAIN, GAIN_MAX, GAIN_STEP, WAVE, AMP);
|
||||
wait (n_chk >= N_WORDS);
|
||||
$fclose(fd);
|
||||
$display("checked %0d words, inputs %0d, max deviation from ideal %0d LSB",
|
||||
n_chk, n_hist, max_dev);
|
||||
if (errors == 0)
|
||||
$display("TEST PASSED");
|
||||
else
|
||||
$display("TEST FAILED: errors = %0d", errors);
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user