1 Commits

Author SHA1 Message Date
17748a71b1 infra: add libs 2026-05-28 16:59:57 +03:00
13 changed files with 110 additions and 1626 deletions

1
.gitignore vendored
View File

@ -16,7 +16,6 @@
.Xil
xvlog.pb
*vivado_pid*
**/work/*
# some generated files (they annoy me)
update_config.tcl

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "libs/rtl_libs"]
path = libs/rtl_libs
url = https://git.radiophotonics.ru/baulin.fa/rtl_libs.git
[submodule "libs/verilog-axi"]
path = libs/verilog-axi
url = https://github.com/alexforencich/verilog-axi.git

1
libs/rtl_libs Submodule

Submodule libs/rtl_libs added at 338f30c0d7

1
libs/verilog-axi Submodule

Submodule libs/verilog-axi added at 516bd5dadc

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,7 +17,6 @@ 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,
@ -26,7 +26,6 @@ module accumulator
);
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;
@ -40,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,
@ -55,85 +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_done;
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;
cnt_addr <= cnt_addr + 1;
cnt_smp_num <= cnt_smp_num + WINDOW_SIZE;
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;
end
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;
end else begin
cnt_seq_num <= cnt_seq_num + 1;
end
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_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;

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,
@ -42,67 +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)
.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),
@ -118,6 +87,6 @@ module accumulator_top
.send_req (send_req),
.batch_req (batch_req),
.finish (finish_int)
.finish (finish)
);
endmodule
endmodule

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,32 +28,19 @@ 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
res <= accum + axis_data;
res_valid <= 1;
accum <= '0;
cnt <= '0;
end else begin
accum <= accum + axis_data;
cnt <= cnt + 1;
end
axis_data <= s_axis_tdata;
axis_valid <= s_axis_tvalid;
if ( axis_valid) begin
if (cnt == WINDOW_SIZE-1) begin
res <= accum + axis_data;
res_valid <= 1;
accum <= '0;
cnt <= '0;
end else begin
accum <= accum + axis_data;
cnt <= cnt + 1;
end
end
end

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

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

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; // приемник готов заранее
smp_num = smp_num_i;
seq_num = seq_num_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");

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)

View File

@ -1,736 +0,0 @@
# shitpost
import sys
import math
import socket
import platform
from PyQt6 import uic
from dataclasses import dataclass
from PyQt6.QtCore import QProcess, QTimer
from PyQt6.QtCore import QObject, QThread, pyqtSignal
from PyQt6.QtCore import Qt
import pyqtgraph as pg
from PyQt6.QtWidgets import QApplication, QMainWindow
@dataclass
class ReflectometerConfig:
ip: str
send_port: int
recv_port: int
dac_bits: int
data_width: int
window_size: int
packet_size: int
pulse_width: int
pulse_period: int
pulse_height: int
pulse_num: int
adc_dac_ratio: float = 0.52
socket_timeout_sec: float = 2.0
class ReflectometerWorker(QObject):
data_ready = pyqtSignal(list)
status = pyqtSignal(str)
error = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, config: ReflectometerConfig):
super().__init__()
self.config = config
self._stop_requested = False
self._sock = None
def stop(self):
self._stop_requested = True
if self._sock is not None:
try:
self._sock.close()
except OSError:
pass
def run(self):
try:
self._validate_config()
self.status.emit("Открытие UDP-сокета...")
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.bind(("0.0.0.0", self.config.recv_port))
dest = (self.config.ip, self.config.send_port)
self.status.emit("Отправка soft reset...")
self._sock.sendto((0x0F00).to_bytes(2, "big"), dest)
self.status.emit("Отправка параметров...")
ctrl_data = self._format_ctrl_data()
self._sock.sendto(ctrl_data, dest)
self.status.emit("Отправка start...")
self._sock.sendto((0xF000).to_bytes(2, "big"), dest)
self.status.emit("Приём данных...")
data = self._recv_data()
if self._stop_requested:
self.status.emit("Операция остановлена")
return
self.data_ready.emit(data)
self.status.emit(f"Получено samples: {len(data)}")
except Exception as e:
if not self._stop_requested:
self.error.emit(str(e))
finally:
if self._sock is not None:
try:
self._sock.close()
except OSError:
pass
self.finished.emit()
def _format_ctrl_data(self) -> bytes:
output = bytearray()
output += 0b10001000.to_bytes(1, "little")
pulse_period_adc = (
int(self.config.pulse_period * self.config.adc_dac_ratio)
// self.config.window_size
) * self.config.window_size
output += self.config.pulse_width.to_bytes(4, "little")
output += self.config.pulse_period.to_bytes(4, "little")
output += self.config.pulse_num.to_bytes(2, "little")
output += self.config.pulse_height.to_bytes(2, "little")
output += pulse_period_adc.to_bytes(4, "little")
if len(output) != 17:
raise ValueError("Config data should be 128 bits + 8 bit header")
return bytes(output)
def _recv_data(self) -> list[int]:
packet_count = math.ceil(
(
self.config.adc_dac_ratio
* self.config.pulse_period
/ self.config.window_size
* self.config.data_width
)
/ self.config.packet_size
)
expected_length = math.ceil(
self.config.adc_dac_ratio
* self.config.pulse_period
/ self.config.window_size
)
recv_buf = []
for pkt_cnt in range(packet_count):
if self._stop_requested:
break
try:
packet, _ = self._sock.recvfrom(65536)
except socket.timeout:
raise TimeoutError(f"Таймаут приёма UDP-пакета #{pkt_cnt + 1}")
if len(packet) % self.config.data_width != 0:
raise ValueError(
f"Некорректный размер UDP-пакета: {len(packet)} байт"
)
for i in range(0, len(packet), self.config.data_width):
sample = int.from_bytes(
packet[i:i + self.config.data_width],
"little",
)
recv_buf.append(sample)
if len(recv_buf) < expected_length:
raise ValueError(
f"Data underflow: получено {len(recv_buf)}, ожидалось {expected_length}"
)
return recv_buf[:expected_length - 1]
def _validate_config(self):
if self.config.pulse_period <= 0:
raise ValueError("pulse_period должен быть больше 0")
if self.config.pulse_num <= 0:
raise ValueError("pulse_num должен быть больше 0")
if self.config.window_size <= 0:
raise ValueError("window_size должен быть больше 0")
if self.config.packet_size <= 0:
raise ValueError("packet_size должен быть больше 0")
if self.config.data_width <= 0:
raise ValueError("data_width должен быть больше 0")
if self.config.pulse_period % self.config.window_size != 0:
raise ValueError("pulse_period должен быть кратен window_size")
if self.config.pulse_width >= 2**32 - 1:
raise ValueError("pulse_width слишком большой")
if self.config.pulse_period >= 2**32 - 1:
raise ValueError("pulse_period слишком большой")
if self.config.pulse_num >= 2**16 - 1:
raise ValueError("pulse_num слишком большой")
if self.config.pulse_height > 2**self.config.dac_bits - 1:
raise ValueError("pulse_height слишком большой")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("reflectometer.ui", self)
self.ping_process = None
self.ping_timeout_timer = QTimer(self)
self.ping_timeout_timer.setSingleShot(True)
self.ping_timeout_timer.timeout.connect(self.on_ping_timeout)
self.button_ping.clicked.connect(self.check_ping)
# settings
self.pulse_period = 0
self.pulse_height = 0
self.pulse_width = 0
self.pulse_num = 0
self.dac_dw = 14
self.adc_dw = 12
self.nmax = 4096
self.packet_size = 1024
self.window_size = 65
self.adc_dac_ration = 0.52
self.accum_width = 32
# setup
self.setup_pulse_controls()
self.setup_global_settings()
self.update_pulse_limits()
self.data = []
self.adc_dac_ratio = 0.52
self.measurement_thread = None
self.measurement_worker = None
self.setup_graph()
self.setup_network_settings()
self.button_start.clicked.connect(self.run_measurement)
self.button_graph_autoscale.clicked.connect(self.reset_graph_autoscale)
# ping utils
def check_ping(self):
ip = self.line_ip.text().strip()
if not ip:
self.label_ping_status.setText("set ip!!")
return
if "_" in self.line_ip.displayText():
self.label_ping_status.setText("IP invalid")
return
if self.ping_process is not None:
if self.ping_process.state() != QProcess.ProcessState.NotRunning:
self.label_ping_status.setText("Ping inflight")
return
self.label_ping_status.setText("ping...")
self.button_ping.setEnabled(False)
self.ping_process = QProcess(self)
self.ping_process.finished.connect(self.on_ping_finished)
self.ping_process.errorOccurred.connect(self.on_ping_error)
system_name = platform.system().lower()
if system_name == "windows":
program = "ping"
arguments = ["-n", "1", "-w", "2000", ip]
else:
program = "ping"
arguments = ["-c", "1", "-W", "2", ip]
self.ping_process.start(program, arguments)
# fallback
self.ping_timeout_timer.start(2000)
def on_ping_finished(self, exit_code, exit_status):
self.ping_timeout_timer.stop()
self.button_ping.setEnabled(True)
if exit_code == 0:
self.label_ping_status.setText("алё✅")
else:
self.label_ping_status.setText("не алё❌")
def on_ping_error(self):
self.ping_timeout_timer.stop()
self.button_ping.setEnabled(True)
self.label_ping_status.setText("ping unavail")
def on_ping_timeout(self):
if self.ping_process is not None:
if self.ping_process.state() != QProcess.ProcessState.NotRunning:
self.ping_process.kill()
self.button_ping.setEnabled(True)
# pulse controls
def setup_pulse_controls(self):
self._bind_slider_and_spinbox(
name="pulse_period",
slider=self.slider_pulse_period,
box=self.box_pulse_period,
normalize_value=self.normalize_pulse_period,
)
self._bind_slider_and_spinbox(
name="pulse_height",
slider=self.slider_pulse_height,
box=self.box_pulse_height,
)
self._bind_slider_and_spinbox(
name="pulse_width",
slider=self.slider_pulse_width,
box=self.box_pulse_width,
)
self._bind_slider_and_spinbox(
name="pulse_num",
slider=self.slider_pulse_num,
box=self.box_pulse_num,
)
def _bind_slider_and_spinbox(self, name, slider, box, normalize_value=None):
"""
Связывает QSlider и QSpinBox по значению.
Значение автоматически записывается в self.<name>.
"""
minimum = min(slider.minimum(), box.minimum())
maximum = max(slider.maximum(), box.maximum())
slider.setRange(minimum, maximum)
box.setRange(minimum, maximum)
def normalize(value):
if normalize_value is None:
return value
return normalize_value(value)
value = normalize(box.value())
slider.setValue(value)
box.setValue(value)
setattr(self, name, value)
def update_value(new_value):
new_value = normalize(new_value)
if slider.value() != new_value:
slider.setValue(new_value)
if box.value() != new_value:
box.setValue(new_value)
setattr(self, name, new_value)
slider.valueChanged.connect(update_value)
box.valueChanged.connect(update_value)
def normalize_pulse_period(self, value):
step = max(1, getattr(self, "window_size",
self.box_window_size.value()))
snapped_value = round(value / step) * step
minimum = self.box_pulse_period.minimum()
maximum = self.box_pulse_period.maximum()
return max(minimum, min(snapped_value, maximum))
def _set_max_for_pair(self, slider, box, maximum):
slider.setMaximum(maximum)
box.setMaximum(maximum)
value = min(box.value(), maximum)
box.setValue(value)
slider.setValue(value)
def set_max_pulse_period(self, maximum):
self._set_max_for_pair(
slider=self.slider_pulse_period,
box=self.box_pulse_period,
maximum=maximum,
)
self.pulse_period = self.box_pulse_period.value()
def set_max_pulse_height(self, maximum):
self._set_max_for_pair(
slider=self.slider_pulse_height,
box=self.box_pulse_height,
maximum=maximum,
)
self.pulse_height = self.box_pulse_height.value()
def set_max_pulse_width(self, maximum):
self._set_max_for_pair(
slider=self.slider_pulse_width,
box=self.box_pulse_width,
maximum=maximum,
)
self.pulse_width = self.box_pulse_width.value()
def set_max_pulse_num(self, maximum):
self._set_max_for_pair(
slider=self.slider_pulse_num,
box=self.box_pulse_num,
maximum=maximum,
)
self.pulse_num = self.box_pulse_num.value()
# settings
def setup_global_settings(self):
self._bind_spinbox_setting(
name="dac_dw",
box=self.box_dac_dw,
)
self._bind_spinbox_setting(
name="adc_dw",
box=self.box_adc_dw,
)
self._bind_spinbox_setting(
name="nmax",
box=self.box_nmax,
)
self._bind_spinbox_setting(
name="window_size",
box=self.box_window_size,
after_change=self.on_window_size_changed,
)
self._bind_spinbox_setting(
name="packet_size",
box=self.box_packet_size,
)
self._bind_spinbox_setting(
name="adc_dac_ratio",
box=self.box_adc_dac_ratio,
)
self._bind_spinbox_setting(
name="accum_width",
box=self.box_accum_width,
)
self._bind_spinbox_setting(
name="recv_port",
box=self.box_recv_port,
)
self._bind_spinbox_setting(
name="send_port",
box=self.box_send_port,
)
# применяем шаг для pulse_period сразу при старте
self.update_pulse_period_step()
def _bind_spinbox_setting(self, name, box, after_change=None):
"""
Связывает QSpinBox с полем self.<name>.
Например:
box_dac_dw -> self.dac_dw
box_window_size -> self.window_size
"""
value = box.value()
setattr(self, name, value)
def on_value_changed(new_value):
setattr(self, name, new_value)
self.update_pulse_limits()
if after_change is not None:
after_change(new_value)
box.valueChanged.connect(on_value_changed)
def update_pulse_limits(self):
# re-calc limits
# nmax -> pulse_period limit
self.set_max_pulse_period(self.nmax * self.window_size)
self.set_max_pulse_width(self.nmax * self.window_size)
# accum_width + adc_width -> max pulse num
self.set_max_pulse_num(
2 ** (self.accum_width - self.adc_dw - math.ceil(math.log2(self.window_size))) - 1)
# dac_width -> max pulse height
self.set_max_pulse_height(2 ** self.dac_dw - 1)
self.slider_pulse_period.setMinimum(self.window_size)
self.box_pulse_period.setMinimum(self.window_size)
def on_window_size_changed(self, new_value):
self.update_pulse_period_step()
def update_pulse_period_step(self):
# set window_size step
step = max(1, self.window_size)
self.box_pulse_period.setSingleStep(step)
self.slider_pulse_period.setSingleStep(step)
self.slider_pulse_period.setPageStep(step)
self.snap_pulse_period_to_step(step)
def snap_pulse_period_to_step(self, step):
"""
Подгоняет текущее значение pulse_period к ближайшему кратному window_size.
Это нужно потому, что QSlider при перетаскивании мышкой
всё равно может дать любое промежуточное значение.
"""
current_value = self.box_pulse_period.value()
snapped_value = round(current_value / step) * step
minimum = self.box_pulse_period.minimum()
maximum = self.box_pulse_period.maximum()
snapped_value = max(minimum, min(snapped_value, maximum))
self.box_pulse_period.setValue(snapped_value)
self.slider_pulse_period.setValue(snapped_value)
self.pulse_period = snapped_value
# graph
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.showGrid(x=True, y=True)
self.graph_curve = self.graph_widget.plot(
[],
name="Data",
)
self.reference_curve = self.graph_widget.plot(
[],
name="Reference",
)
self.graph_layout.addWidget(self.graph_widget)
self.graph_curve = self.graph_widget.plot(
[], pen=pg.mkPen(width=2, color="b"))
self.reference_curve = self.graph_widget.plot(
[], pen=pg.mkPen(style=Qt.PenStyle.DashLine, color="g"))
self.checkbox_draw_reference.stateChanged.connect(
self.update_reference_graph)
def setup_network_settings(self):
self._bind_spinbox_setting(
name="recv_port",
box=self.box_recv_port,
)
self._bind_spinbox_setting(
name="send_port",
box=self.box_send_port,
)
def run_measurement(self):
if self.measurement_thread is not None:
if self.measurement_thread.isRunning():
self.set_measurement_status("Измерение выполняется")
return
config = self.build_reflectometer_config()
self.data = []
self.graph_curve.setData([])
self.measurement_thread = QThread(self)
self.measurement_worker = ReflectometerWorker(config)
self.measurement_worker.moveToThread(self.measurement_thread)
self.measurement_thread.started.connect(self.measurement_worker.run)
self.measurement_worker.status.connect(self.set_measurement_status)
self.measurement_worker.error.connect(self.on_measurement_error)
self.measurement_worker.data_ready.connect(self.on_data_received)
self.measurement_worker.finished.connect(self.measurement_thread.quit)
self.measurement_worker.finished.connect(
self.measurement_worker.deleteLater)
self.measurement_thread.finished.connect(
self.measurement_thread.deleteLater)
self.measurement_thread.finished.connect(self.on_measurement_finished)
self.measurement_thread.start()
def build_reflectometer_config(self) -> ReflectometerConfig:
ip = self.line_ip.text().strip()
if not ip:
raise ValueError("IP адрес не задан")
data_width = self.accum_width // 8
return ReflectometerConfig(
ip=ip,
send_port=self.send_port,
recv_port=self.recv_port,
dac_bits=self.dac_dw,
data_width=data_width,
window_size=self.window_size,
packet_size=self.packet_size,
pulse_width=self.pulse_width,
pulse_period=self.pulse_period,
pulse_height=self.pulse_height,
pulse_num=self.pulse_num,
adc_dac_ratio=self.adc_dac_ratio,
)
def on_data_received(self, data: list[int]):
self.data = data
# normalize
for i in range(len(data)):
self.data[i] /= (self.window_size * self.pulse_num)
self.data[i] -= 2 ** (self.adc_dw - 1) + 1
self.draw_main_graph()
self.update_reference_graph()
if data:
self.set_measurement_status(
f"Готово. smp: {len(data)}, min: {min(data)}, max: {max(data)}"
)
else:
self.set_measurement_status("Данные пустые")
def on_measurement_error(self, message: str):
self.set_measurement_status(f"Ошибка: {message}")
def on_measurement_finished(self):
self.measurement_worker = None
self.measurement_thread = None
def stop_measurement(self):
if self.measurement_worker is not None:
self.measurement_worker.stop()
def set_measurement_status(self, text: str):
self.label_status.setText(text)
def draw_main_graph(self):
if not self.data:
self.graph_curve.setData([])
return
x = list(range(len(self.data)))
self.graph_curve.setData(x, self.data)
def update_reference_graph(self):
"""
Рисует или очищает эталонный график.
Вызывается после получения данных и при переключении checkbox_draw_reference.
"""
if not self.checkbox_draw_reference.isChecked():
self.reference_curve.setData([])
return
if not self.data:
self.reference_curve.setData([])
return
reference_data = self.build_reference_data(len(self.data))
if not reference_data:
self.reference_curve.setData([])
return
x = list(range(len(reference_data)))
self.reference_curve.setData(x, reference_data)
def build_reference_data(self, length: int) -> list[int]:
reference = [0] * length
actual_pulse_width = round(
(self.pulse_width * self.adc_dac_ratio) / 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)
return reference
def reset_graph_autoscale(self):
self.graph_widget.enableAutoRange(axis="xy", enable=True)
self.graph_widget.autoRange()
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

View File

@ -1,505 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1023</width>
<height>708</height>
</rect>
</property>
<property name="windowTitle">
<string>Reflectometer PREMIUM</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="4,2">
<item>
<layout class="QVBoxLayout" name="graph_layout"/>
</item>
<item>
<layout class="QVBoxLayout" name="settings_layout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Настройки</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>294</width>
<height>621</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Аппаратные параметры</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_dac_dw">
<property name="suffix">
<string> bits</string>
</property>
<property name="prefix">
<string>DAC data width: </string>
</property>
<property name="minimum">
<number>8</number>
</property>
<property name="maximum">
<number>32</number>
</property>
<property name="value">
<number>14</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_adc_dw">
<property name="suffix">
<string> bits</string>
</property>
<property name="prefix">
<string>ADC data width: </string>
</property>
<property name="minimum">
<number>8</number>
</property>
<property name="maximum">
<number>32</number>
</property>
<property name="value">
<number>12</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_accum_width">
<property name="suffix">
<string> bits</string>
</property>
<property name="prefix">
<string>Accum width: </string>
</property>
<property name="minimum">
<number>16</number>
</property>
<property name="maximum">
<number>64</number>
</property>
<property name="singleStep">
<number>8</number>
</property>
<property name="value">
<number>32</number>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="box_adc_dac_ratio">
<property name="prefix">
<string>ADC:DAC clk ratio: </string>
</property>
<property name="minimum">
<double>0.200000000000000</double>
</property>
<property name="maximum">
<double>3.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.520000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_nmax">
<property name="prefix">
<string>N Max: </string>
</property>
<property name="minimum">
<number>512</number>
</property>
<property name="maximum">
<number>65536</number>
</property>
<property name="value">
<number>4096</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_window_size">
<property name="prefix">
<string>Window size: </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1024</number>
</property>
<property name="value">
<number>65</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_packet_size">
<property name="suffix">
<string> bytes</string>
</property>
<property name="prefix">
<string>Packet size: </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1572</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Подключение</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>IP устройства:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_ip">
<property name="inputMask">
<string>999.999.999.999</string>
</property>
<property name="text">
<string>192.168.0.2</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Порт отправки:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_send_port">
<property name="minimum">
<number>80</number>
</property>
<property name="maximum">
<number>65536</number>
</property>
<property name="value">
<number>8080</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Порт приёма:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_recv_port">
<property name="minimum">
<number>80</number>
</property>
<property name="maximum">
<number>65536</number>
</property>
<property name="value">
<number>8080</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Тест</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_ping">
<property name="text">
<string>алё</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_ping_status">
<property name="text">
<string>...</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Управление</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_7">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Импульс</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,1,2">
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Период</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_pulse_period">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="slider_pulse_period">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,1,2">
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>Ширина</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_pulse_width"/>
</item>
<item>
<widget class="QSlider" name="slider_pulse_width">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,1,2">
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>Высота</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_pulse_height"/>
</item>
<item>
<widget class="QSlider" name="slider_pulse_height">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,1,2">
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>Количество</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_pulse_num">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="slider_pulse_num">
<property name="minimum">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="button_start">
<property name="text">
<string>start!</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="1,3">
<item>
<widget class="QLabel" name="label_13">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Статус:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_status">
<property name="text">
<string>-</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkbox_draw_reference">
<property name="text">
<string>Отрисовка эталона</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_graph_autoscale">
<property name="text">
<string>Сброс масштаба</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1023</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>