Compare commits

..
Author SHA1 Message Date
Phil 2d4a5af40a sw: update UI 2026-09-07 18:55:42 +03:00
Phil 0526157bfc sw: change units 2026-09-07 18:54:01 +03:00
Phil 09e8ade74c sw: more information + auto-update feature 2026-09-07 18:48:20 +03:00
Phil 8d8b409807 sw: add load/save options, re-work adc/dac ratio setting 2026-09-07 17:41:23 +03:00
10 changed files with 395 additions and 420 deletions
-1
View File
@@ -16,7 +16,6 @@
.Xil
xvlog.pb
*vivado_pid*
**/work/*
# some generated files (they annoy me)
update_config.tcl
+46 -169
View File
@@ -5,6 +5,7 @@ module accumulator
parameter DATA_WIDTH = 12,
parameter ACCUM_WIDTH = 32,
parameter N_MAX = 4096,
parameter WINDOW_SIZE = 4,
parameter PACKET_SIZE = 8,
parameter READ_BATCH_SIZE =(PACKET_SIZE*8)/(ACCUM_WIDTH)
)
@@ -16,18 +17,15 @@ module accumulator
input start,
input [31:0] smp_num,
input [15:0] seq_num,
input [31:0] window_size,
output [ACCUM_WIDTH-1:0] out_data,
output out_valid,
output readout_begin,
input batch_req,
input finish,
output logic accum_done
input finish
);
logic [31:0] smp_num_reg, cnt_smp_num;
logic [31:0] window_size_reg;
logic [15:0] seq_num_reg, cnt_seq_num;
logic [15:0] cnt_addr, addra, addrb;
@@ -41,6 +39,10 @@ module accumulator
logic out_valid_reg;
logic finish_reg, finish_buf;
// registers for port b data request
reg req_data_b;
reg [15:0] req_addr_b;
typedef enum logic [3:0] {
IDLE,
INIT_MEM,
@@ -56,84 +58,18 @@ module accumulator
} wr_state_t;
(* MARK_DEBUG="true" *) wr_state_t wr_state;
// One word per clock accumulation pipeline
// On every sum_valid in ACCUM we launch a BRAM read for cnt_addr
// On the next clock the saved sum_data is added to doutb and written back
logic accum_pipe_valid;
logic [15:0] accum_pipe_addr;
logic [ACCUM_WIDTH-1:0] accum_pipe_data;
// case smp_num // window_size == 1
// Then the next sequence can read the same address it is written
logic accum_pipe_bypass_valid;
logic [ACCUM_WIDTH-1:0] accum_pipe_bypass_data;
logic accum_accept_last;
logic accum_accept_last_all;
logic [ACCUM_WIDTH-1:0] accum_write_base;
logic [ACCUM_WIDTH-1:0] accum_write_value;
wire [31:0] window_size_safe = (window_size == 32'd0) ? 32'd1 : window_size;
wire start_accept = start && (wr_state == IDLE);
assign accum_accept_last = (cnt_smp_num + window_size_reg >= smp_num_reg);
assign accum_accept_last_all = accum_accept_last && (cnt_seq_num == seq_num_reg - 1);
assign accum_write_base = accum_pipe_bypass_valid ? accum_pipe_bypass_data : data_bram_out;
assign accum_write_value = accum_pipe_data + accum_write_base;
// Memory controls to XPM
// In accumulation/init states they are driven directly from the current
// state and pipeline registers. That avoids an extra register stage
logic mem_wea;
logic mem_enb;
logic [15:0] mem_addra;
logic [15:0] mem_addrb;
logic [ACCUM_WIDTH-1:0] mem_dina;
assign mem_wea = (wr_state == INIT_MEM) ? valid_data :
(wr_state == ACCUM) ? accum_pipe_valid :
1'b0;
assign mem_addra = (wr_state == INIT_MEM) ? cnt_addr :
(wr_state == ACCUM) ? accum_pipe_addr :
addra;
assign mem_dina = (wr_state == INIT_MEM) ? data :
(wr_state == ACCUM) ? accum_write_value :
data_bram_in;
assign mem_enb = (wr_state == ACCUM) ? valid_data : enb;
assign mem_addrb = (wr_state == ACCUM) ? cnt_addr : addrb;
// registers for port b data request
reg req_data_b;
reg [15:0] req_addr_b;
always @(posedge clk_in) begin
if (rst) begin
smp_num_reg <= '0;
cnt_smp_num <= '0;
window_size_reg <= 32'd1;
seq_num_reg <= '0;
cnt_seq_num <= '0;
cnt_addr <= '0;
addra <= '0;
addrb <= '0;
data_bram_in <= '0;
wea <= 0;
enb <= 0;
wr_state <= IDLE;
finish_reg <= 0;
finish_buf <= 0;
readout_begin_reg <= 0;
out_data_reg <= '0;
out_valid_reg <= 0;
accum_pipe_valid <= 0;
accum_pipe_addr <= '0;
accum_pipe_data <= '0;
accum_pipe_bypass_valid <= 0;
accum_pipe_bypass_data <= '0;
accum_done <= 0;
end else begin
finish_buf <= finish;
@@ -147,137 +83,83 @@ module accumulator
readout_begin_reg <= 0;
finish_reg <= 0;
out_valid_reg <= 0;
accum_pipe_valid <= 0;
accum_pipe_bypass_valid <= 0;
accum_done <= 0;
cnt_smp_num <= '0;
cnt_seq_num <= '0;
cnt_addr <= '0;
addrb <= '0;
if (start) begin
smp_num_reg <= smp_num;
seq_num_reg <= seq_num;
window_size_reg <= window_size_safe;
wr_state <= INIT_MEM;
end
end
INIT_MEM: begin
// First sequence
// first run to initialize memory with first batch of values
wea <= 0;
enb <= 0;
out_valid_reg <= 0;
accum_pipe_valid <= 0;
accum_pipe_bypass_valid <= 0;
accum_done <= 0;
if (valid_data) begin
// mem_wea/mem_addra/mem_dina do the actual write in this clock
data_bram_in <= data;
addra <= cnt_addr;
wea <= 1;
if (cnt_smp_num + window_size_reg >= smp_num_reg) begin
cnt_smp_num <= '0;
cnt_addr <= '0;
if (seq_num_reg <= 16'd1) begin
cnt_seq_num <= '0;
addrb <= '0;
wr_state <= READOUT_START;
end else begin
// start further accumulation
cnt_seq_num <= 16'd1;
wr_state <= ACCUM;
end
end else begin
cnt_smp_num <= cnt_smp_num + window_size_reg;
cnt_addr <= cnt_addr + 1;
cnt_smp_num <= cnt_smp_num + WINDOW_SIZE;
end
if (cnt_smp_num >= smp_num_reg) begin
wr_state <= BEGIN_SEQ;
end
end
BEGIN_SEQ: begin
// FIXME: unused
// start new acc seq
wea <= 0;
enb <= 0;
wr_state <= ACCUM;
if (cnt_seq_num == seq_num_reg - 1) begin
cnt_seq_num <= '0;
cnt_smp_num <= '0;
cnt_addr <= '0;
wr_state <= READOUT_START;
addrb <= '0;
enb <= 0;
end else begin
// beginning of new data sequence
cnt_seq_num <= cnt_seq_num + 1;
cnt_smp_num <= '0;
cnt_addr <= '0;
wea <= 0;
addrb <= 0;
wr_state <= REQ_WORD_B;
end
end
REQ_WORD_B: begin
// FIXME: depr
// pre-request data for port b
wea <= 0;
enb <= 0;
enb <= 1;
addrb <= cnt_addr;
wr_state <= ACCUM;
end
ACCUM: begin
// accum pipeline
wea <= 0;
// sum mem+input
enb <= 0;
out_valid_reg <= 0;
if (accum_pipe_valid) begin
// mem_wea/mem_addra/mem_dina do the actual write this clock
addra <= accum_pipe_addr;
data_bram_in <= accum_write_value;
if (valid_data) begin
addra <= cnt_addr;
wea <= 1;
end
if (accum_done) begin
// Last input word was accepted on the previous clk
accum_pipe_valid <= 0;
accum_pipe_bypass_valid <= 0;
cnt_smp_num <= '0;
cnt_seq_num <= '0;
cnt_addr <= '0;
addrb <= '0;
enb <= 0;
wr_state <= READOUT_START;
end else if (valid_data) begin
// mem_enb/mem_addrb launch the actual read this clock
enb <= 1;
addrb <= cnt_addr;
accum_pipe_valid <= 1;
accum_pipe_addr <= cnt_addr;
accum_pipe_data <= data;
// case window_size=1 && smp_num is small
accum_pipe_bypass_valid <= accum_pipe_valid && (accum_pipe_addr == cnt_addr);
accum_pipe_bypass_data <= accum_write_value;
if (accum_accept_last) begin
cnt_smp_num <= '0;
cnt_addr <= '0;
if (cnt_seq_num == seq_num_reg - 1) begin
accum_done <= 1;
data_bram_in <= data + data_bram_out;
cnt_smp_num <= cnt_smp_num + WINDOW_SIZE;
if (cnt_smp_num + WINDOW_SIZE >= smp_num_reg) begin
wr_state <= BEGIN_SEQ;
end else begin
cnt_seq_num <= cnt_seq_num + 1;
end
end else begin
cnt_smp_num <= cnt_smp_num + window_size_reg;
cnt_addr <= cnt_addr + 1;
wr_state <= REQ_WORD_B;
end
end else begin
accum_pipe_valid <= 0;
accum_pipe_bypass_valid <= 0;
end
end
READOUT_START: begin
readout_begin_reg <= 1'b1;
wr_state <= READOUT_AWAIT;
enb <= 0;
wea <= 0;
end
READOUT_AWAIT: begin
// req await + delay for every-clock readout
wea <= 0;
// req await + delay for every-clock readout.
if (batch_req) begin
enb <= 1;
wr_state <= READOUT_DELAY;
@@ -291,14 +173,12 @@ module accumulator
READOUT_DELAY: begin
// wait for mem latency
wea <= 0;
addrb <= addrb + 1;
wr_state <= READOUT_PUT;
end
READOUT_PUT: begin
// main data output
wea <= 0;
if ((addrb % READ_BATCH_SIZE) == 0) begin
wr_state <= READOUT_LAST;
enb <= 0;
@@ -310,7 +190,6 @@ module accumulator
READOUT_LAST: begin
// last word of packet
wea <= 0;
out_valid_reg <= 0;
out_data_reg <= data_bram_out;
wr_state <= READOUT_START;
@@ -319,7 +198,6 @@ module accumulator
FINISH: begin
out_valid_reg <= 0;
enb <= 0;
wea <= 0;
wr_state <= IDLE;
end
@@ -332,13 +210,12 @@ module accumulator
adder
#(
.DATA_WIDTH(DATA_WIDTH),
.WINDOW_SIZE(WINDOW_SIZE),
.ACCUM_WIDTH(ACCUM_WIDTH)
) adder_dut
(
.clk_in(clk_in),
.rst(rst),
.start(start_accept),
.window_size(window_size),
.s_axis_tdata(s_axis_tdata),
.s_axis_tvalid(s_axis_tvalid),
.sum_data(data),
@@ -377,14 +254,14 @@ module accumulator
.doutb(data_bram_out),
.addra(mem_addra),
.addrb(mem_addrb),
.addra(addra),
.addrb(addrb),
.clka(clk_in),
.clkb(clk_in),
.dina(mem_dina),
.dina(data_bram_in),
.ena(1'b1),
.enb(mem_enb),
.wea(mem_wea)
.enb(enb),
.wea(wea)
);
assign readout_begin = readout_begin_reg;
+7 -40
View File
@@ -5,6 +5,7 @@ module accumulator_top
parameter DATA_WIDTH = 12,
parameter ACCUM_WIDTH = 32,
parameter N_MAX = 4096,
parameter WINDOW_SIZE = 65,
parameter PACKET_SIZE = 1024,
parameter READ_BATCH_SIZE =(PACKET_SIZE*8)/(ACCUM_WIDTH)
)
@@ -21,7 +22,6 @@ module accumulator_top
input start,
input [31:0] smp_num,
input [15:0] seq_num,
input [31:0] window_size,
// eth signals
input eth_clk_in,
@@ -34,8 +34,7 @@ module accumulator_top
input logic m_axis_tready,
output logic m_axis_tlast,
output logic finish,
output logic accum_done
output logic finish
);
wire [ACCUM_WIDTH-1:0] out_data;
@@ -43,68 +42,36 @@ module accumulator_top
wire readout_begin;
wire batch_req;
logic finish_int;
logic finish_int_d;
logic finish_pulse;
logic calc_active;
logic start_accept;
logic [31:0] window_size_reg;
wire [31:0] window_size_safe = (window_size == 32'd0) ? 32'd1 : window_size;
assign finish_pulse = finish_int && !finish_int_d;
assign start_accept = start && !calc_active;
assign finish = finish_int;
// Keep the top copy stable for blocks that start later than the accum itself
always_ff @(posedge clk_in) begin
if (rst) begin
calc_active <= 1'b0;
finish_int_d <= 1'b0;
window_size_reg <= 32'd1;
end else begin
finish_int_d <= finish_int;
if (start_accept) begin
calc_active <= 1'b1;
window_size_reg <= window_size_safe;
end else if (finish_pulse) begin
calc_active <= 1'b0;
end
end
end
accumulator #(
.DATA_WIDTH(DATA_WIDTH),
.ACCUM_WIDTH(ACCUM_WIDTH),
.N_MAX(N_MAX),
.WINDOW_SIZE(WINDOW_SIZE),
.PACKET_SIZE(PACKET_SIZE)
) accum_main (
.clk_in(clk_in),
.rst(rst),
.s_axis_tdata(s_axis_tdata),
.s_axis_tvalid(s_axis_tvalid),
.start(start_accept),
.start(start),
.smp_num(smp_num),
.seq_num(seq_num),
.window_size(window_size),
.out_data(out_data),
.out_valid(out_valid),
.readout_begin(readout_begin),
.batch_req(batch_req),
.finish(finish_int),
.accum_done(accum_done)
.finish(finish)
);
out_axis_fifo #(
.ACCUM_WIDTH(ACCUM_WIDTH),
.WINDOW_SIZE(WINDOW_SIZE),
.PACKET_SIZE(PACKET_SIZE)
) output_async_fifo (
.eth_clk_in (eth_clk_in),
.acc_clk_in (clk_in),
.rst (rst),
.smp_num (smp_num),
.window_size (window_size_reg),
.m_axis_tdata (m_axis_tdata),
.m_axis_tvalid (m_axis_tvalid),
@@ -120,6 +87,6 @@ module accumulator_top
.send_req (send_req),
.batch_req (batch_req),
.finish (finish_int)
.finish (finish)
);
endmodule
+3 -20
View File
@@ -4,13 +4,12 @@
module adder
#(
parameter DATA_WIDTH = 12,
parameter WINDOW_SIZE = 4,
parameter ACCUM_WIDTH = 32
)
(
input clk_in,
input rst,
input start,
input [31:0] window_size,
input [DATA_WIDTH-1:0] s_axis_tdata,
input s_axis_tvalid,
@@ -21,10 +20,7 @@ module adder
logic [ACCUM_WIDTH-1:0] accum, res;
logic [DATA_WIDTH-1:0] axis_data;
logic res_valid, axis_valid;
(* MARK_DEBUG = "TRUE" *) logic [31:0] cnt;
logic [31:0] window_size_reg;
wire [31:0] window_size_safe = (window_size == 32'd0) ? 32'd1 : window_size;
(* MARK_DEBUG = "TRUE" *) logic [15:0] cnt;
always @(posedge clk_in) begin
if (rst) begin
@@ -32,24 +28,12 @@ module adder
cnt <= '0;
res <= '0;
res_valid <= 0;
axis_data <= '0;
axis_valid <= 0;
window_size_reg <= 32'd1;
end else begin
res_valid <= 0;
if (start) begin
accum <= '0;
cnt <= '0;
res <= '0;
axis_data <= '0;
axis_valid <= 0;
window_size_reg <= window_size_safe;
end else begin
axis_data <= s_axis_tdata;
axis_valid <= s_axis_tvalid;
if ( axis_valid) begin
if (cnt == window_size_reg - 1) begin
if (cnt == WINDOW_SIZE-1) begin
res <= accum + axis_data;
res_valid <= 1;
accum <= '0;
@@ -61,7 +45,6 @@ module adder
end
end
end
end
assign sum_valid = res_valid;
assign sum_data = res;
+7 -11
View File
@@ -1,12 +1,12 @@
module out_axis_fifo #(
parameter ACCUM_WIDTH = 32,
parameter WINDOW_SIZE = 65,
parameter PACKET_SIZE = 1024
) (
input logic eth_clk_in,
input logic acc_clk_in,
input logic rst,
input logic [31:0] smp_num,
input logic [31:0] window_size,
// AXI stream master for output, eth_clk_in domain
output logic [7:0] m_axis_tdata,
@@ -85,8 +85,6 @@ module out_axis_fifo #(
reg [31:0] wr_cnt; // current BIT mem ptr
reg [31:0] wr_batch_tgt; // next 'target' that should be written from batch
reg [31:0] wr_total; // total BITS to be sent!
logic [31:0] window_size_reg;
wire [31:0] window_size_safe = (window_size == 32'd0) ? 32'd1 : window_size;
wire empty;
@@ -94,7 +92,7 @@ module out_axis_fifo #(
// NOTE:
// each written "acc_din" ACCUM_WIDTH word
// is counted as window_size samples actually
// is counted as WINDOWS_SIZE samples actually
// because hw division for counters is painful
// so we just increased the counter sizes
@@ -104,7 +102,6 @@ module out_axis_fifo #(
wr_cnt <= 32'b0;
wr_batch_tgt <= 32'b0;
wr_total <= 32'b0;
window_size_reg <= 32'd1;
batch_req <= 0;
finish <= 0;
@@ -118,7 +115,6 @@ module out_axis_fifo #(
wr_state <= WR_CHECK;
wr_total <= smp_num * ACCUM_WIDTH;
wr_batch_tgt <= 32'b0;
window_size_reg <= window_size_safe;
batch_req <= 0;
finish <= 0;
end
@@ -130,9 +126,9 @@ module out_axis_fifo #(
if ((wr_data_count < (FIFO_WDEPTH - (PACKET_SIZE / (ACCUM_WIDTH / 8)))) && ~wr_rst_busy) begin
batch_req <= 1;
// should give us exactly PACKET_SIZE * 8 bits
// multiplied by window_size, because we count
// each given ACCUM_WIDTH word as window_size samples !!!
wr_batch_tgt <= wr_batch_tgt + (8 * window_size_reg * PACKET_SIZE);
// multiplied by WINDOW_SIZE, because we count
// each given ACCUM_WIDTH word as WINDOWS_SIZE samples !!!
wr_batch_tgt <= wr_batch_tgt + (8 * WINDOW_SIZE * PACKET_SIZE);
wr_state <= WR_RUN;
end else begin
batch_req <= 0;
@@ -154,8 +150,8 @@ module out_axis_fifo #(
if (din_valid) begin
// data supplied
// count as we got window_size samples
wr_cnt <= wr_cnt + ACCUM_WIDTH * window_size_reg;
// count as we got WINDOW_SIZE samples
wr_cnt <= wr_cnt + ACCUM_WIDTH * WINDOW_SIZE;
end
end
+7 -11
View File
@@ -13,24 +13,20 @@ FPGA_ARCH = artix7
RTL_DIR = ../src
# Simulation settings
SIM_TOP = tb_accumulator_top
SIM_RUNTIME ?= 10000 us
# Design sources only
SYN_FILES += $(filter-out %_tb.sv,$(sort $(shell find $(RTL_DIR) -type f -name '*.v' -o -type f -name '*.sv')))
include ../../../scripts/vivado.mk
# Testbench sources. Vivado puts these into "sim_1", Questa compiles them into "work".
TB_FILES += out_axis_fifo_tb.sv
TB_FILES += accum_full_tb.sv
SYN_FILES += $(sort $(shell find ../src -type f \( -name '*.v' -o -name '*.sv' \)))
XCI_FILES = $(sort $(shell find $(RTL_DIR) -type f -name '*.xci'))
XCI_FILES = $(sort $(shell find ../src -type f -name '*.xci'))
XDC_FILES += ../../../constraints/ax7a035b.xdc
XDC_FILES += test_timing.xdc
include ../../../scripts/vivado.mk
include ../../../scripts/questa.mk
SYN_FILES += out_axis_fifo_tb.sv
SYN_FILES += accum_full_tb.sv
SIM_TOP = tb_accumulator_top
program: $(PROJECT).bit
echo "open_hw_manager" > program.tcl
+19 -26
View File
@@ -5,7 +5,7 @@ module tb_accumulator_top;
localparam DATA_WIDTH = 12;
localparam ACCUM_WIDTH = 32;
localparam N_MAX = 4096;
localparam MAX_WINDOW_SIZE = 65;
localparam WINDOW_SIZE = 65;
localparam PACKET_SIZE = 1024;
localparam READ_BATCH_SIZE = (PACKET_SIZE*8)/ACCUM_WIDTH;
localparam MAX_WORDS = N_MAX;
@@ -20,7 +20,6 @@ module tb_accumulator_top;
logic start;
logic [31:0] smp_num;
logic [15:0] seq_num;
logic [31:0] window_size;
logic req_ready;
wire send_req;
@@ -51,6 +50,7 @@ module tb_accumulator_top;
.DATA_WIDTH(DATA_WIDTH),
.ACCUM_WIDTH(ACCUM_WIDTH),
.N_MAX(N_MAX),
.WINDOW_SIZE(WINDOW_SIZE),
.PACKET_SIZE(PACKET_SIZE)
) dut (
.clk_in(clk_in),
@@ -60,7 +60,6 @@ module tb_accumulator_top;
.start(start),
.smp_num(smp_num),
.seq_num(seq_num),
.window_size(window_size),
.eth_clk_in(eth_clk_in),
.req_ready(req_ready),
.send_req(send_req),
@@ -105,7 +104,6 @@ module tb_accumulator_top;
s_axis_tvalid = 1'b0;
smp_num = '0;
seq_num = '0;
window_size = 32'd1;
req_ready = 1'b0;
m_axis_tready = 1'b1;
clear_scoreboard();
@@ -143,14 +141,13 @@ module tb_accumulator_top;
task automatic run_test(
input integer test_id,
input integer window_size_i,
input integer seq_num_i,
input integer smp_num_i,
input bit randomize_data,
input integer base_value,
input string test_name
);
logic [DATA_WIDTH-1:0] sample_mem [0:MAX_SEQ_NUM-1][0:(N_MAX*MAX_WINDOW_SIZE)-1];
logic [DATA_WIDTH-1:0] sample_mem [0:MAX_SEQ_NUM-1][0:(N_MAX*WINDOW_SIZE)-1];
integer seq_idx;
integer sample_idx;
integer word_idx;
@@ -168,25 +165,22 @@ module tb_accumulator_top;
tests_total = tests_total + 1;
errors_before = total_errors;
if (window_size_i <= 0 || window_size_i > MAX_WINDOW_SIZE)
$fatal(1, "[%0s] invalid window_size=%0d", test_name, window_size_i);
if (smp_num_i <= 0 || smp_num_i > N_MAX * window_size_i || (smp_num_i % window_size_i) != 0)
$fatal(1, "[%0s] invalid smp_num=%0d for window_size=%0d", test_name, smp_num_i, window_size_i);
if (smp_num_i <= 0 || smp_num_i > N_MAX * WINDOW_SIZE || (smp_num_i % WINDOW_SIZE) != 0)
$fatal(1, "[%0s] invalid smp_num=%0d", test_name, smp_num_i);
if (seq_num_i <= 0 || seq_num_i > MAX_SEQ_NUM)
$fatal(1, "[%0s] invalid seq_num=%0d", test_name, seq_num_i);
$display("\n========================================");
$display("TEST %0d: %0s", test_id, test_name);
$display("window_size=%0d seq_num=%0d smp_num=%0d randomize=%0d", window_size_i, seq_num_i, smp_num_i, randomize_data);
$display("seq_num=%0d smp_num=%0d randomize=%0d", seq_num_i, smp_num_i, randomize_data);
$display("========================================");
reset_dut();
smp_num = smp_num_i;
seq_num = seq_num_i;
window_size = window_size_i;
req_ready = 1'b1; // приемник готов заранее
exp_word_count = smp_num_i / window_size_i;
exp_word_count = smp_num_i / WINDOW_SIZE;
exp_packet_count = (exp_word_count + READ_BATCH_SIZE - 1) / READ_BATCH_SIZE;
for (seq_idx = 0; seq_idx < seq_num_i; seq_idx = seq_idx + 1) begin
@@ -202,8 +196,8 @@ module tb_accumulator_top;
for (word_idx = 0; word_idx < exp_word_count; word_idx = word_idx + 1) begin
local_sum = 0;
for (seq_idx = 0; seq_idx < seq_num_i; seq_idx = seq_idx + 1) begin
for (k = 0; k < window_size_i; k = k + 1)
local_sum = local_sum + sample_mem[seq_idx][word_idx * window_size_i + k];
for (k = 0; k < WINDOW_SIZE; k = k + 1)
local_sum = local_sum + sample_mem[seq_idx][word_idx * WINDOW_SIZE + k];
end
expected_words[word_idx] = local_sum[ACCUM_WIDTH-1:0];
$display(" expected[%0d] = %0d (0x%08x)", word_idx, expected_words[word_idx], expected_words[word_idx]);
@@ -324,17 +318,16 @@ module tb_accumulator_top;
reset_dut();
run_test(1, 1, 1, 1 * 1, 1'b0, 1, "w1_deterministic_small");
run_test(2, 1, 2, 16 * 1, 1'b1, 0, "w1_random_seq2_smp16");
run_test(3, 2, 2, 16 * 2, 1'b1, 0, "w2_random_seq2_smp32");
run_test(4, 3, 1, 16 * 3, 1'b1, 0, "w3_random_seq1_smp48");
run_test(5, 4, 2, 12 * 4, 1'b1, 0, "w4_random_seq2_smp48");
run_test(6, 65, 4, 256 * 65, 1'b1, 0, "w65_random_seq4_smp16640");
run_test(7, 2, 20, 3 * 2, 1'b1, 0, "w2_random_20seqx3");
run_test(8, 65, 20, 3 * 65, 1'b1, 0, "w65_random_20seqx3");
run_test(9, 1, 200, 1 * 1, 1'b1, 0, "w1_random_200seq");
run_test(10, 2, 200, 2 * 1, 1'b1, 0, "w2_random_200seq");
run_test(11, 65, 200, 65 * 1, 1'b1, 0, "w1_random_200seq");
run_test(1, 1, 1 * WINDOW_SIZE, 1'b0, 1, "deterministic_small");
// $finish;
run_test(2, 2, 1 * WINDOW_SIZE, 1'b1, 0, "random_seq3_smp8");
run_test(3, 1, 16 * WINDOW_SIZE, 1'b1, 0, "random_seq5_smp16_multi_packet");
run_test(4, 2, 12 * WINDOW_SIZE, 1'b1, 0, "random_seq7_smp12");
run_test(5, 4, 256 * WINDOW_SIZE, 1'b1, 0, "random_max_smpnum");
run_test(6, 2, 1500 * WINDOW_SIZE, 1'b1, 0, "random_max_smpnum2");
run_test(7, 20, 1 * WINDOW_SIZE, 1'b1, 0, "random_20seq");
run_test(8, 20, 3 * WINDOW_SIZE, 1'b1, 0, "random_20seqx3");
run_test(9, 200, 1 * WINDOW_SIZE, 1'b1, 0, "random_200seq");
$display("\n========================================");
$display("ALL TESTS COMPLETED");
-96
View File
@@ -1,96 +0,0 @@
# SPDX-License-Identifier: MIT
###################################################################
# Questa/ModelSim simulation helper
#
# Expected variables from the project Makefile:
# SYN_FILES - RTL sources
# TB_FILES - testbench sources
# INC_FILES - include files, optional
# SIM_TOP - simulation top module
# SIM_DEFS - defines, optional
# SIM_RUNTIME - run time, for example "10000 us" or "-all"
#
# Useful overrides:
# make sim-questa XILINX_VIVADO=/opt/Xilinx/Vivado/2024.2
# make sim-questa-gui
###################################################################
.PHONY: sim-questa sim-questa-gui questa-clean questa-xpm-clean
VLIB ?= vlib
VMAP ?= vmap
VLOG ?= vlog
VSIM ?= vsim
QUESTA_WORK_LIB ?= work
QUESTA_XPM_LIB ?= xpm
QUESTA_DIR ?= questa_build
QUESTA_TRANSCRIPT ?= transcript
XILINX_VIVADO=/tools/Xilinx/2025.1/Vivado
SIM_RUNTIME ?= 10000 us
QUESTA_RUN ?= run $(SIM_RUNTIME)
# XPM is needed by this design because accum.sv uses xpm_memory_sdpram and
# out_axis_fifo.sv uses xpm_fifo_async. Point XILINX_VIVADO to your Vivado
# install if it is not already exported by settings64.sh.
QUESTA_USE_XPM ?= 1
QUESTA_XPM_SRC = \
$(XILINX_VIVADO)/data/ip/xpm/xpm_cdc/hdl/xpm_cdc.sv \
$(XILINX_VIVADO)/data/ip/xpm/xpm_memory/hdl/xpm_memory.sv \
$(XILINX_VIVADO)/data/ip/xpm/xpm_fifo/hdl/xpm_fifo.sv
QUESTA_GLBL_SRC = $(XILINX_VIVADO)/data/verilog/src/glbl.v
QUESTA_DEFS = $(foreach d,$(SIM_DEFS),+define+$(d))
QUESTA_INC_DIRS = $(sort $(dir $(SYN_FILES) $(TB_FILES) $(INC_FILES)))
QUESTA_INCS = $(foreach d,$(QUESTA_INC_DIRS),+incdir+$(d))
QUESTA_LIBS = $(if $(filter 1,$(QUESTA_USE_XPM)),-L $(QUESTA_XPM_LIB),)
QUESTA_SOURCES = $(SYN_FILES) $(TB_FILES) $(if $(wildcard $(QUESTA_GLBL_SRC)),$(QUESTA_GLBL_SRC),)
QUESTA_GLBL_TOP = $(if $(wildcard $(QUESTA_GLBL_SRC)),$(QUESTA_WORK_LIB).glbl,)
$(QUESTA_DIR):
mkdir -p $@
$(QUESTA_DIR)/sources.f: Makefile | $(QUESTA_DIR)
@rm -f $@
@for inc in $(QUESTA_INCS); do echo $$inc >> $@; done
@for src in $(QUESTA_SOURCES); do echo $$src >> $@; done
$(QUESTA_DIR)/xpm.stamp: | $(QUESTA_DIR)
@if [ "$(QUESTA_USE_XPM)" = "1" ]; then \
if [ -z "$(XILINX_VIVADO)" ]; then \
echo "ERROR: XILINX_VIVADO is not set. Source Vivado settings64.sh or pass XILINX_VIVADO=/path/to/Vivado/<version>."; \
exit 1; \
fi; \
for src in $(QUESTA_XPM_SRC); do \
if [ ! -f $$src ]; then \
echo "ERROR: XPM source not found: $$src"; \
exit 1; \
fi; \
done; \
$(VLIB) $(QUESTA_XPM_LIB); \
$(VMAP) $(QUESTA_XPM_LIB) $(QUESTA_XPM_LIB); \
$(VLOG) -sv -work $(QUESTA_XPM_LIB) $(QUESTA_XPM_SRC); \
fi
@touch $@
$(QUESTA_DIR)/compile.stamp: $(QUESTA_DIR)/sources.f $(SYN_FILES) $(TB_FILES) $(INC_FILES) $(QUESTA_DIR)/xpm.stamp
$(VLIB) $(QUESTA_WORK_LIB)
$(VMAP) $(QUESTA_WORK_LIB) $(QUESTA_WORK_LIB)
$(VLOG) -sv -work $(QUESTA_WORK_LIB) $(QUESTA_DEFS) -timescale 1ns/1ps $(QUESTA_INCS) -f $(QUESTA_DIR)/sources.f
@touch $@
sim-questa: $(QUESTA_DIR)/compile.stamp
$(VSIM) -c $(QUESTA_LIBS) $(QUESTA_WORK_LIB).$(SIM_TOP) $(QUESTA_GLBL_TOP) -do "$(QUESTA_RUN); quit -f"
sim-questa-gui: $(QUESTA_DIR)/compile.stamp
$(VSIM) $(QUESTA_LIBS) -voptargs="+acc" $(QUESTA_WORK_LIB).$(SIM_TOP) $(QUESTA_GLBL_TOP)
questa-clean:
-rm -rf $(QUESTA_DIR) $(QUESTA_WORK_LIB) $(QUESTA_TRANSCRIPT) vsim.wlf *.wlf
questa-xpm-clean: questa-clean
-rm -rf $(QUESTA_XPM_LIB)
+133 -19
View File
@@ -2,6 +2,7 @@
import sys
import math
import json
import socket
import platform
@@ -14,6 +15,19 @@ from PyQt6.QtCore import Qt
import pyqtgraph as pg
from PyQt6.QtWidgets import QApplication, QMainWindow
SETTINGS_LIST = [
"dac_dw",
"adc_dw",
"nmax",
"window_size",
"packet_size",
"adc_clk",
"dac_clk",
"accum_width",
"recv_port",
"send_port",
]
@dataclass
class ReflectometerConfig:
@@ -31,7 +45,9 @@ class ReflectometerConfig:
pulse_height: int
pulse_num: int
adc_dac_ratio: float = 0.52
# adc_dac_ratio: float = 0.52
adc_clk: float = 65
dac_clk: float = 125
socket_timeout_sec: float = 2.0
@@ -46,6 +62,7 @@ class ReflectometerWorker(QObject):
self.config = config
self._stop_requested = False
self._sock = None
self.measure_timeout = 1.0
def stop(self):
self._stop_requested = True
@@ -64,7 +81,7 @@ class ReflectometerWorker(QObject):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._sock.settimeout(self.config.socket_timeout_sec)
self._sock.settimeout(self.measure_timeout)
self._sock.bind(("0.0.0.0", self.config.recv_port))
dest = (self.config.ip, self.config.send_port)
@@ -108,7 +125,8 @@ class ReflectometerWorker(QObject):
output += 0b10001000.to_bytes(1, "little")
pulse_period_adc = (
int(self.config.pulse_period * self.config.adc_dac_ratio)
int(self.config.pulse_period *
(self.config.adc_clk / self.config.dac_clk))
// self.config.window_size
) * self.config.window_size
@@ -126,7 +144,7 @@ class ReflectometerWorker(QObject):
def _recv_data(self) -> list[int]:
packet_count = math.ceil(
(
self.config.adc_dac_ratio
(self.config.adc_clk / self.config.dac_clk)
* self.config.pulse_period
/ self.config.window_size
* self.config.data_width
@@ -135,7 +153,7 @@ class ReflectometerWorker(QObject):
)
expected_length = math.ceil(
self.config.adc_dac_ratio
(self.config.adc_clk / self.config.dac_clk)
* self.config.pulse_period
/ self.config.window_size
)
@@ -227,7 +245,6 @@ class MainWindow(QMainWindow):
self.nmax = 4096
self.packet_size = 1024
self.window_size = 65
self.adc_dac_ration = 0.52
self.accum_width = 32
# setup
@@ -239,8 +256,6 @@ class MainWindow(QMainWindow):
self.data = []
self.adc_dac_ratio = 0.52
self.measurement_thread = None
self.measurement_worker = None
@@ -250,8 +265,58 @@ class MainWindow(QMainWindow):
self.button_start.clicked.connect(self.run_measurement)
self.button_graph_autoscale.clicked.connect(self.reset_graph_autoscale)
# ping utils
self.button_save.clicked.connect(self.save_config)
self.button_load.clicked.connect(self.load_config)
# timeout progress bar
self.measure_progress_timer = QTimer(self)
self.measure_progress_timer.setInterval(50)
self.measure_progress_timer.timeout.connect(
self.update_measure_progress)
self.measure_progress_elapsed_ms = 0
self.measure_progress_total_ms = 1
self.progress_measure.setRange(0, 100)
self.progress_measure.setValue(0)
def update_measure_progress(self):
self.measure_progress_elapsed_ms += self.measure_progress_timer.interval()
progress = int(
self.measure_progress_elapsed_ms
/ self.measure_progress_total_ms
* 100
)
self.progress_measure.setValue(min(progress, 100))
if progress >= 100:
self.measure_progress_timer.stop()
# load/save
def save_config(self):
conf = {}
for setting in SETTINGS_LIST:
conf[setting] = self.__getattribute__(setting)
with open(".settings.json", "w") as f:
f.write(json.dumps(conf))
self.label_save_status.setText("Настройки сохранены")
def load_config(self):
try:
with open(".settings.json", 'r') as f:
conf = json.loads(f.read())
except FileNotFoundError:
self.label_save_status.setText("Нет сохранненого .setting.json")
return
for setting in conf:
self.__getattribute__(f"box_{setting}").setValue(conf[setting])
self.label_save_status.setText("Настройки загружены")
# ping utils
def check_ping(self):
ip = self.line_ip.text().strip()
@@ -280,14 +345,14 @@ class MainWindow(QMainWindow):
if system_name == "windows":
program = "ping"
arguments = ["-n", "1", "-w", "2000", ip]
arguments = ["-n", "1", "-w", "1000", ip]
else:
program = "ping"
arguments = ["-c", "1", "-W", "2", ip]
arguments = ["-c", "1", "-W", "1", ip]
self.ping_process.start(program, arguments)
# fallback
self.ping_timeout_timer.start(2000)
self.ping_timeout_timer.start(1000)
def on_ping_finished(self, exit_code, exit_status):
self.ping_timeout_timer.stop()
@@ -371,6 +436,7 @@ class MainWindow(QMainWindow):
box.setValue(new_value)
setattr(self, name, new_value)
self.recalculate_times()
slider.valueChanged.connect(update_value)
box.valueChanged.connect(update_value)
@@ -426,6 +492,22 @@ class MainWindow(QMainWindow):
)
self.pulse_num = self.box_pulse_num.value()
def recalculate_times(self):
# пересчет показываемых метрик
LS = 200000 # ~km/s
period_us = (self.pulse_period / self.dac_clk)
self.label_meas_time.setText(
f"{round(period_us, 1)}мкс / {round(LS * period_us / 10e6, 1)}км")
self.label_duty.setText(
f"{round(self.pulse_width / self.pulse_period * 100, 4)}%")
self.label_voltage.setText(
f"{round((self.pulse_height - 8192) * 10 / 16384, 3)}V")
self.label_meas_total.setText(
f"{round(period_us * self.pulse_num / 10e6, 3)}с")
# settings
def setup_global_settings(self):
@@ -456,8 +538,13 @@ class MainWindow(QMainWindow):
)
self._bind_spinbox_setting(
name="adc_dac_ratio",
box=self.box_adc_dac_ratio,
name="adc_clk",
box=self.box_adc_clk,
)
self._bind_spinbox_setting(
name="dac_clk",
box=self.box_dac_clk,
)
self._bind_spinbox_setting(
@@ -554,7 +641,7 @@ class MainWindow(QMainWindow):
def setup_graph(self):
self.graph_widget = pg.PlotWidget()
self.graph_widget.setLabel("left", "ADC value")
self.graph_widget.setLabel("bottom", "Sample")
self.graph_widget.setLabel("bottom", "Distance", units="km")
self.graph_widget.showGrid(x=True, y=True)
self.graph_curve = self.graph_widget.plot(
@@ -596,10 +683,21 @@ class MainWindow(QMainWindow):
config = self.build_reflectometer_config()
self.data = []
self.graph_curve.setData([])
# self.graph_curve.setData([])
self.measurement_thread = QThread(self)
self.measurement_worker = ReflectometerWorker(config)
period_us = (self.pulse_period / self.dac_clk)
self.measurement_worker.measure_timeout = period_us * self.pulse_num / 10e6 + 0.2
self.measure_progress_total_ms = int(
self.measurement_worker.measure_timeout * 1000 - 199
)
self.measure_progress_elapsed_ms = 0
self.progress_measure.setValue(0)
self.measure_progress_timer.start()
self.measurement_worker.moveToThread(self.measurement_thread)
@@ -642,7 +740,8 @@ class MainWindow(QMainWindow):
pulse_height=self.pulse_height,
pulse_num=self.pulse_num,
adc_dac_ratio=self.adc_dac_ratio,
adc_clk=self.adc_clk,
dac_clk=self.dac_clk
)
def on_data_received(self, data: list[int]):
@@ -664,10 +763,14 @@ class MainWindow(QMainWindow):
def on_measurement_error(self, message: str):
self.set_measurement_status(f"Ошибка: {message}")
if self.checkbox_autorestart.isChecked():
self.run_measurement()
def on_measurement_finished(self):
self.measurement_worker = None
self.measurement_thread = None
if self.checkbox_autorestart.isChecked():
self.run_measurement()
def stop_measurement(self):
if self.measurement_worker is not None:
@@ -681,7 +784,18 @@ class MainWindow(QMainWindow):
self.graph_curve.setData([])
return
x = list(range(len(self.data)))
LS = 200000 # km/s
period_us = self.pulse_period / self.dac_clk
max_distance_km = LS * period_us / 10e6
sample_count = len(self.data)
x = [
i * max_distance_km / sample_count
for i in range(sample_count)
]
self.graph_curve.setData(x, self.data)
def update_reference_graph(self):
@@ -711,7 +825,7 @@ class MainWindow(QMainWindow):
reference = [0] * length
actual_pulse_width = round(
(self.pulse_width * self.adc_dac_ratio) / self.window_size)
(self.pulse_width * self.adc_clk / self.dac_clk) / self.window_size)
reference[0:actual_pulse_width] = [
(self.pulse_height / 2 ** (self.dac_dw - self.adc_dw)) - 2 ** (self.adc_dw - 1), ] * (actual_pulse_width - 1)
+158 -12
View File
@@ -7,11 +7,11 @@
<x>0</x>
<y>0</y>
<width>1023</width>
<height>708</height>
<height>844</height>
</rect>
</property>
<property name="windowTitle">
<string>Reflectometer PREMIUM</string>
<string>Reflectometer PRO MAX</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="4,2">
@@ -39,9 +39,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<y>-32</y>
<width>294</width>
<height>621</height>
<height>762</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -118,21 +118,37 @@
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="box_adc_dac_ratio">
<widget class="QSpinBox" name="box_adc_clk">
<property name="suffix">
<string> MHz</string>
</property>
<property name="prefix">
<string>ADC:DAC clk ratio: </string>
<string>ADC CLK: </string>
</property>
<property name="minimum">
<double>0.200000000000000</double>
<number>25</number>
</property>
<property name="maximum">
<double>3.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
<number>350</number>
</property>
<property name="value">
<double>0.520000000000000</double>
<number>65</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_dac_clk">
<property name="suffix">
<string> MHz</string>
</property>
<property name="prefix">
<string>DAC CLK: </string>
</property>
<property name="minimum">
<number>25</number>
</property>
<property name="maximum">
<number>350</number>
</property>
</widget>
</item>
@@ -292,6 +308,50 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_12">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Конфигурация</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_save_status">
<property name="text">
<string>Можно сохранить или загрузить</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QPushButton" name="button_save">
<property name="text">
<string>Сохранить</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_load">
<property name="text">
<string>Загрузить</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@@ -353,6 +413,24 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="label_15">
<property name="text">
<string>Время измерения:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_meas_time">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,1,2">
<item>
@@ -374,6 +452,24 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>Скважность:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_duty">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,1,2">
<item>
@@ -395,6 +491,24 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_17">
<property name="text">
<string>Напряжение:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_voltage">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,1,2">
<item>
@@ -423,6 +537,24 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QLabel" name="label_14">
<property name="text">
<string>Суммарное время:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_meas_total">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="button_start">
<property name="text">
@@ -453,6 +585,20 @@
</item>
</layout>
</item>
<item>
<widget class="QProgressBar" name="progress_measure">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_autorestart">
<property name="text">
<string>Авто-рестарт</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_draw_reference">
<property name="text">