Merge pull request 'dev/accum_rework' (#15) from dev/accum_rework into dev/design
Reviewed-on: #15
This commit was merged in pull request #15.
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
.Xil
|
.Xil
|
||||||
xvlog.pb
|
xvlog.pb
|
||||||
*vivado_pid*
|
*vivado_pid*
|
||||||
|
**/work/*
|
||||||
|
|
||||||
# some generated files (they annoy me)
|
# some generated files (they annoy me)
|
||||||
update_config.tcl
|
update_config.tcl
|
||||||
|
|||||||
+170
-47
@@ -5,7 +5,6 @@ module accumulator
|
|||||||
parameter DATA_WIDTH = 12,
|
parameter DATA_WIDTH = 12,
|
||||||
parameter ACCUM_WIDTH = 32,
|
parameter ACCUM_WIDTH = 32,
|
||||||
parameter N_MAX = 4096,
|
parameter N_MAX = 4096,
|
||||||
parameter WINDOW_SIZE = 4,
|
|
||||||
parameter PACKET_SIZE = 8,
|
parameter PACKET_SIZE = 8,
|
||||||
parameter READ_BATCH_SIZE =(PACKET_SIZE*8)/(ACCUM_WIDTH)
|
parameter READ_BATCH_SIZE =(PACKET_SIZE*8)/(ACCUM_WIDTH)
|
||||||
)
|
)
|
||||||
@@ -17,6 +16,7 @@ module accumulator
|
|||||||
input start,
|
input start,
|
||||||
input [31:0] smp_num,
|
input [31:0] smp_num,
|
||||||
input [15:0] seq_num,
|
input [15:0] seq_num,
|
||||||
|
input [31:0] window_size,
|
||||||
|
|
||||||
output [ACCUM_WIDTH-1:0] out_data,
|
output [ACCUM_WIDTH-1:0] out_data,
|
||||||
output out_valid,
|
output out_valid,
|
||||||
@@ -26,6 +26,7 @@ module accumulator
|
|||||||
);
|
);
|
||||||
|
|
||||||
logic [31:0] smp_num_reg, cnt_smp_num;
|
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] seq_num_reg, cnt_seq_num;
|
||||||
logic [15:0] cnt_addr, addra, addrb;
|
logic [15:0] cnt_addr, addra, addrb;
|
||||||
|
|
||||||
@@ -39,10 +40,6 @@ module accumulator
|
|||||||
logic out_valid_reg;
|
logic out_valid_reg;
|
||||||
logic finish_reg, finish_buf;
|
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] {
|
typedef enum logic [3:0] {
|
||||||
IDLE,
|
IDLE,
|
||||||
INIT_MEM,
|
INIT_MEM,
|
||||||
@@ -58,18 +55,85 @@ module accumulator
|
|||||||
} wr_state_t;
|
} wr_state_t;
|
||||||
(* MARK_DEBUG="true" *) wr_state_t wr_state;
|
(* 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
|
always @(posedge clk_in) begin
|
||||||
if (rst) begin
|
if (rst) begin
|
||||||
smp_num_reg <= '0;
|
smp_num_reg <= '0;
|
||||||
cnt_smp_num <= '0;
|
cnt_smp_num <= '0;
|
||||||
|
window_size_reg <= 32'd1;
|
||||||
seq_num_reg <= '0;
|
seq_num_reg <= '0;
|
||||||
cnt_seq_num <= '0;
|
cnt_seq_num <= '0;
|
||||||
cnt_addr <= '0;
|
cnt_addr <= '0;
|
||||||
|
addra <= '0;
|
||||||
|
addrb <= '0;
|
||||||
|
data_bram_in <= '0;
|
||||||
wea <= 0;
|
wea <= 0;
|
||||||
enb <= 0;
|
enb <= 0;
|
||||||
wr_state <= IDLE;
|
wr_state <= IDLE;
|
||||||
finish_reg <= 0;
|
finish_reg <= 0;
|
||||||
|
finish_buf <= 0;
|
||||||
|
readout_begin_reg <= 0;
|
||||||
|
out_data_reg <= '0;
|
||||||
out_valid_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
|
end else begin
|
||||||
finish_buf <= finish;
|
finish_buf <= finish;
|
||||||
|
|
||||||
@@ -83,83 +147,137 @@ module accumulator
|
|||||||
readout_begin_reg <= 0;
|
readout_begin_reg <= 0;
|
||||||
finish_reg <= 0;
|
finish_reg <= 0;
|
||||||
out_valid_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
|
if (start) begin
|
||||||
smp_num_reg <= smp_num;
|
smp_num_reg <= smp_num;
|
||||||
seq_num_reg <= seq_num;
|
seq_num_reg <= seq_num;
|
||||||
|
window_size_reg <= window_size_safe;
|
||||||
wr_state <= INIT_MEM;
|
wr_state <= INIT_MEM;
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
INIT_MEM: begin
|
INIT_MEM: begin
|
||||||
// first run to initialize memory with first batch of values
|
// First sequence
|
||||||
wea <= 0;
|
wea <= 0;
|
||||||
|
enb <= 0;
|
||||||
|
out_valid_reg <= 0;
|
||||||
|
accum_pipe_valid <= 0;
|
||||||
|
accum_pipe_bypass_valid <= 0;
|
||||||
|
accum_done <= 0;
|
||||||
|
|
||||||
if (valid_data) begin
|
if (valid_data) begin
|
||||||
|
// mem_wea/mem_addra/mem_dina do the actual write in this clock
|
||||||
data_bram_in <= data;
|
data_bram_in <= data;
|
||||||
addra <= cnt_addr;
|
addra <= cnt_addr;
|
||||||
wea <= 1;
|
wea <= 1;
|
||||||
cnt_addr <= cnt_addr + 1;
|
|
||||||
cnt_smp_num <= cnt_smp_num + WINDOW_SIZE;
|
|
||||||
|
|
||||||
end
|
if (cnt_smp_num + window_size_reg >= smp_num_reg) begin
|
||||||
if (cnt_smp_num >= smp_num_reg) begin
|
cnt_smp_num <= '0;
|
||||||
wr_state <= BEGIN_SEQ;
|
cnt_addr <= '0;
|
||||||
end
|
|
||||||
|
|
||||||
end
|
if (seq_num_reg <= 16'd1) begin
|
||||||
BEGIN_SEQ: begin
|
|
||||||
// start new acc seq
|
|
||||||
wea <= 0;
|
|
||||||
enb <= 0;
|
|
||||||
if (cnt_seq_num == seq_num_reg - 1) begin
|
|
||||||
cnt_seq_num <= '0;
|
cnt_seq_num <= '0;
|
||||||
cnt_smp_num <= '0;
|
|
||||||
cnt_addr <= '0;
|
|
||||||
wr_state <= READOUT_START;
|
|
||||||
addrb <= '0;
|
addrb <= '0;
|
||||||
enb <= 0;
|
wr_state <= READOUT_START;
|
||||||
end else begin
|
end else begin
|
||||||
// beginning of new data sequence
|
// start further accumulation
|
||||||
cnt_seq_num <= cnt_seq_num + 1;
|
cnt_seq_num <= 16'd1;
|
||||||
cnt_smp_num <= '0;
|
wr_state <= ACCUM;
|
||||||
cnt_addr <= '0;
|
|
||||||
wea <= 0;
|
|
||||||
addrb <= 0;
|
|
||||||
wr_state <= REQ_WORD_B;
|
|
||||||
end
|
end
|
||||||
|
end else begin
|
||||||
|
cnt_smp_num <= cnt_smp_num + window_size_reg;
|
||||||
|
cnt_addr <= cnt_addr + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
BEGIN_SEQ: begin
|
||||||
|
// FIXME: unused
|
||||||
|
wea <= 0;
|
||||||
|
enb <= 0;
|
||||||
|
wr_state <= ACCUM;
|
||||||
end
|
end
|
||||||
|
|
||||||
REQ_WORD_B: begin
|
REQ_WORD_B: begin
|
||||||
// pre-request data for port b
|
// FIXME: depr
|
||||||
wea <= 0;
|
wea <= 0;
|
||||||
enb <= 1;
|
enb <= 0;
|
||||||
addrb <= cnt_addr;
|
|
||||||
wr_state <= ACCUM;
|
wr_state <= ACCUM;
|
||||||
end
|
end
|
||||||
|
|
||||||
ACCUM: begin
|
ACCUM: begin
|
||||||
// sum mem+input
|
// accum pipeline
|
||||||
|
wea <= 0;
|
||||||
enb <= 0;
|
enb <= 0;
|
||||||
if (valid_data) begin
|
out_valid_reg <= 0;
|
||||||
addra <= cnt_addr;
|
|
||||||
|
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;
|
||||||
wea <= 1;
|
wea <= 1;
|
||||||
data_bram_in <= data + data_bram_out;
|
end
|
||||||
cnt_smp_num <= cnt_smp_num + WINDOW_SIZE;
|
|
||||||
if (cnt_smp_num + WINDOW_SIZE >= smp_num_reg) begin
|
if (accum_done) begin
|
||||||
wr_state <= BEGIN_SEQ;
|
// 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
|
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;
|
cnt_addr <= cnt_addr + 1;
|
||||||
wr_state <= REQ_WORD_B;
|
|
||||||
end
|
end
|
||||||
|
end else begin
|
||||||
|
accum_pipe_valid <= 0;
|
||||||
|
accum_pipe_bypass_valid <= 0;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
READOUT_START: begin
|
READOUT_START: begin
|
||||||
readout_begin_reg <= 1'b1;
|
readout_begin_reg <= 1'b1;
|
||||||
wr_state <= READOUT_AWAIT;
|
wr_state <= READOUT_AWAIT;
|
||||||
enb <= 0;
|
enb <= 0;
|
||||||
|
wea <= 0;
|
||||||
end
|
end
|
||||||
|
|
||||||
READOUT_AWAIT: begin
|
READOUT_AWAIT: begin
|
||||||
// req await + delay for every-clock readout.
|
// req await + delay for every-clock readout
|
||||||
|
wea <= 0;
|
||||||
if (batch_req) begin
|
if (batch_req) begin
|
||||||
enb <= 1;
|
enb <= 1;
|
||||||
wr_state <= READOUT_DELAY;
|
wr_state <= READOUT_DELAY;
|
||||||
@@ -173,12 +291,14 @@ module accumulator
|
|||||||
|
|
||||||
READOUT_DELAY: begin
|
READOUT_DELAY: begin
|
||||||
// wait for mem latency
|
// wait for mem latency
|
||||||
|
wea <= 0;
|
||||||
addrb <= addrb + 1;
|
addrb <= addrb + 1;
|
||||||
wr_state <= READOUT_PUT;
|
wr_state <= READOUT_PUT;
|
||||||
end
|
end
|
||||||
|
|
||||||
READOUT_PUT: begin
|
READOUT_PUT: begin
|
||||||
// main data output
|
// main data output
|
||||||
|
wea <= 0;
|
||||||
if ((addrb % READ_BATCH_SIZE) == 0) begin
|
if ((addrb % READ_BATCH_SIZE) == 0) begin
|
||||||
wr_state <= READOUT_LAST;
|
wr_state <= READOUT_LAST;
|
||||||
enb <= 0;
|
enb <= 0;
|
||||||
@@ -190,6 +310,7 @@ module accumulator
|
|||||||
|
|
||||||
READOUT_LAST: begin
|
READOUT_LAST: begin
|
||||||
// last word of packet
|
// last word of packet
|
||||||
|
wea <= 0;
|
||||||
out_valid_reg <= 0;
|
out_valid_reg <= 0;
|
||||||
out_data_reg <= data_bram_out;
|
out_data_reg <= data_bram_out;
|
||||||
wr_state <= READOUT_START;
|
wr_state <= READOUT_START;
|
||||||
@@ -198,6 +319,7 @@ module accumulator
|
|||||||
FINISH: begin
|
FINISH: begin
|
||||||
out_valid_reg <= 0;
|
out_valid_reg <= 0;
|
||||||
enb <= 0;
|
enb <= 0;
|
||||||
|
wea <= 0;
|
||||||
wr_state <= IDLE;
|
wr_state <= IDLE;
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -210,12 +332,13 @@ module accumulator
|
|||||||
adder
|
adder
|
||||||
#(
|
#(
|
||||||
.DATA_WIDTH(DATA_WIDTH),
|
.DATA_WIDTH(DATA_WIDTH),
|
||||||
.WINDOW_SIZE(WINDOW_SIZE),
|
|
||||||
.ACCUM_WIDTH(ACCUM_WIDTH)
|
.ACCUM_WIDTH(ACCUM_WIDTH)
|
||||||
) adder_dut
|
) adder_dut
|
||||||
(
|
(
|
||||||
.clk_in(clk_in),
|
.clk_in(clk_in),
|
||||||
.rst(rst),
|
.rst(rst),
|
||||||
|
.start(start_accept),
|
||||||
|
.window_size(window_size),
|
||||||
.s_axis_tdata(s_axis_tdata),
|
.s_axis_tdata(s_axis_tdata),
|
||||||
.s_axis_tvalid(s_axis_tvalid),
|
.s_axis_tvalid(s_axis_tvalid),
|
||||||
.sum_data(data),
|
.sum_data(data),
|
||||||
@@ -254,14 +377,14 @@ module accumulator
|
|||||||
|
|
||||||
.doutb(data_bram_out),
|
.doutb(data_bram_out),
|
||||||
|
|
||||||
.addra(addra),
|
.addra(mem_addra),
|
||||||
.addrb(addrb),
|
.addrb(mem_addrb),
|
||||||
.clka(clk_in),
|
.clka(clk_in),
|
||||||
.clkb(clk_in),
|
.clkb(clk_in),
|
||||||
.dina(data_bram_in),
|
.dina(mem_dina),
|
||||||
.ena(1'b1),
|
.ena(1'b1),
|
||||||
.enb(enb),
|
.enb(mem_enb),
|
||||||
.wea(wea)
|
.wea(mem_wea)
|
||||||
);
|
);
|
||||||
|
|
||||||
assign readout_begin = readout_begin_reg;
|
assign readout_begin = readout_begin_reg;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ module accumulator_top
|
|||||||
parameter DATA_WIDTH = 12,
|
parameter DATA_WIDTH = 12,
|
||||||
parameter ACCUM_WIDTH = 32,
|
parameter ACCUM_WIDTH = 32,
|
||||||
parameter N_MAX = 4096,
|
parameter N_MAX = 4096,
|
||||||
parameter WINDOW_SIZE = 65,
|
|
||||||
parameter PACKET_SIZE = 1024,
|
parameter PACKET_SIZE = 1024,
|
||||||
parameter READ_BATCH_SIZE =(PACKET_SIZE*8)/(ACCUM_WIDTH)
|
parameter READ_BATCH_SIZE =(PACKET_SIZE*8)/(ACCUM_WIDTH)
|
||||||
)
|
)
|
||||||
@@ -22,6 +21,7 @@ module accumulator_top
|
|||||||
input start,
|
input start,
|
||||||
input [31:0] smp_num,
|
input [31:0] smp_num,
|
||||||
input [15:0] seq_num,
|
input [15:0] seq_num,
|
||||||
|
input [31:0] window_size,
|
||||||
|
|
||||||
// eth signals
|
// eth signals
|
||||||
input eth_clk_in,
|
input eth_clk_in,
|
||||||
@@ -42,36 +42,67 @@ module accumulator_top
|
|||||||
wire readout_begin;
|
wire readout_begin;
|
||||||
wire batch_req;
|
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 #(
|
accumulator #(
|
||||||
.DATA_WIDTH(DATA_WIDTH),
|
.DATA_WIDTH(DATA_WIDTH),
|
||||||
.ACCUM_WIDTH(ACCUM_WIDTH),
|
.ACCUM_WIDTH(ACCUM_WIDTH),
|
||||||
.N_MAX(N_MAX),
|
.N_MAX(N_MAX),
|
||||||
.WINDOW_SIZE(WINDOW_SIZE),
|
|
||||||
.PACKET_SIZE(PACKET_SIZE)
|
.PACKET_SIZE(PACKET_SIZE)
|
||||||
) accum_main (
|
) accum_main (
|
||||||
.clk_in(clk_in),
|
.clk_in(clk_in),
|
||||||
.rst(rst),
|
.rst(rst),
|
||||||
.s_axis_tdata(s_axis_tdata),
|
.s_axis_tdata(s_axis_tdata),
|
||||||
.s_axis_tvalid(s_axis_tvalid),
|
.s_axis_tvalid(s_axis_tvalid),
|
||||||
.start(start),
|
.start(start_accept),
|
||||||
.smp_num(smp_num),
|
.smp_num(smp_num),
|
||||||
.seq_num(seq_num),
|
.seq_num(seq_num),
|
||||||
|
.window_size(window_size),
|
||||||
.out_data(out_data),
|
.out_data(out_data),
|
||||||
.out_valid(out_valid),
|
.out_valid(out_valid),
|
||||||
.readout_begin(readout_begin),
|
.readout_begin(readout_begin),
|
||||||
.batch_req(batch_req),
|
.batch_req(batch_req),
|
||||||
.finish(finish)
|
.finish(finish_int)
|
||||||
);
|
);
|
||||||
|
|
||||||
out_axis_fifo #(
|
out_axis_fifo #(
|
||||||
.ACCUM_WIDTH(ACCUM_WIDTH),
|
.ACCUM_WIDTH(ACCUM_WIDTH),
|
||||||
.WINDOW_SIZE(WINDOW_SIZE),
|
|
||||||
.PACKET_SIZE(PACKET_SIZE)
|
.PACKET_SIZE(PACKET_SIZE)
|
||||||
) output_async_fifo (
|
) output_async_fifo (
|
||||||
.eth_clk_in (eth_clk_in),
|
.eth_clk_in (eth_clk_in),
|
||||||
.acc_clk_in (clk_in),
|
.acc_clk_in (clk_in),
|
||||||
.rst (rst),
|
.rst (rst),
|
||||||
.smp_num (smp_num),
|
.smp_num (smp_num),
|
||||||
|
.window_size (window_size_reg),
|
||||||
|
|
||||||
.m_axis_tdata (m_axis_tdata),
|
.m_axis_tdata (m_axis_tdata),
|
||||||
.m_axis_tvalid (m_axis_tvalid),
|
.m_axis_tvalid (m_axis_tvalid),
|
||||||
@@ -87,6 +118,6 @@ module accumulator_top
|
|||||||
.send_req (send_req),
|
.send_req (send_req),
|
||||||
|
|
||||||
.batch_req (batch_req),
|
.batch_req (batch_req),
|
||||||
.finish (finish)
|
.finish (finish_int)
|
||||||
);
|
);
|
||||||
endmodule
|
endmodule
|
||||||
+20
-3
@@ -4,12 +4,13 @@
|
|||||||
module adder
|
module adder
|
||||||
#(
|
#(
|
||||||
parameter DATA_WIDTH = 12,
|
parameter DATA_WIDTH = 12,
|
||||||
parameter WINDOW_SIZE = 4,
|
|
||||||
parameter ACCUM_WIDTH = 32
|
parameter ACCUM_WIDTH = 32
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
input clk_in,
|
input clk_in,
|
||||||
input rst,
|
input rst,
|
||||||
|
input start,
|
||||||
|
input [31:0] window_size,
|
||||||
input [DATA_WIDTH-1:0] s_axis_tdata,
|
input [DATA_WIDTH-1:0] s_axis_tdata,
|
||||||
input s_axis_tvalid,
|
input s_axis_tvalid,
|
||||||
|
|
||||||
@@ -20,7 +21,10 @@ module adder
|
|||||||
logic [ACCUM_WIDTH-1:0] accum, res;
|
logic [ACCUM_WIDTH-1:0] accum, res;
|
||||||
logic [DATA_WIDTH-1:0] axis_data;
|
logic [DATA_WIDTH-1:0] axis_data;
|
||||||
logic res_valid, axis_valid;
|
logic res_valid, axis_valid;
|
||||||
(* MARK_DEBUG = "TRUE" *) logic [15:0] cnt;
|
(* 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;
|
||||||
|
|
||||||
always @(posedge clk_in) begin
|
always @(posedge clk_in) begin
|
||||||
if (rst) begin
|
if (rst) begin
|
||||||
@@ -28,12 +32,24 @@ module adder
|
|||||||
cnt <= '0;
|
cnt <= '0;
|
||||||
res <= '0;
|
res <= '0;
|
||||||
res_valid <= 0;
|
res_valid <= 0;
|
||||||
|
axis_data <= '0;
|
||||||
|
axis_valid <= 0;
|
||||||
|
window_size_reg <= 32'd1;
|
||||||
end else begin
|
end else begin
|
||||||
res_valid <= 0;
|
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_data <= s_axis_tdata;
|
||||||
axis_valid <= s_axis_tvalid;
|
axis_valid <= s_axis_tvalid;
|
||||||
if (axis_valid) begin
|
if (axis_valid) begin
|
||||||
if (cnt == WINDOW_SIZE-1) begin
|
if (cnt == window_size_reg - 1) begin
|
||||||
res <= accum + axis_data;
|
res <= accum + axis_data;
|
||||||
res_valid <= 1;
|
res_valid <= 1;
|
||||||
accum <= '0;
|
accum <= '0;
|
||||||
@@ -45,6 +61,7 @@ module adder
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
assign sum_valid = res_valid;
|
assign sum_valid = res_valid;
|
||||||
assign sum_data = res;
|
assign sum_data = res;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
module out_axis_fifo #(
|
module out_axis_fifo #(
|
||||||
parameter ACCUM_WIDTH = 32,
|
parameter ACCUM_WIDTH = 32,
|
||||||
parameter WINDOW_SIZE = 65,
|
|
||||||
parameter PACKET_SIZE = 1024
|
parameter PACKET_SIZE = 1024
|
||||||
) (
|
) (
|
||||||
input logic eth_clk_in,
|
input logic eth_clk_in,
|
||||||
input logic acc_clk_in,
|
input logic acc_clk_in,
|
||||||
input logic rst,
|
input logic rst,
|
||||||
input logic [31:0] smp_num,
|
input logic [31:0] smp_num,
|
||||||
|
input logic [31:0] window_size,
|
||||||
|
|
||||||
// AXI stream master for output, eth_clk_in domain
|
// AXI stream master for output, eth_clk_in domain
|
||||||
output logic [7:0] m_axis_tdata,
|
output logic [7:0] m_axis_tdata,
|
||||||
@@ -85,6 +85,8 @@ module out_axis_fifo #(
|
|||||||
reg [31:0] wr_cnt; // current BIT mem ptr
|
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_batch_tgt; // next 'target' that should be written from batch
|
||||||
reg [31:0] wr_total; // total BITS to be sent!
|
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;
|
wire empty;
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ module out_axis_fifo #(
|
|||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// each written "acc_din" ACCUM_WIDTH word
|
// each written "acc_din" ACCUM_WIDTH word
|
||||||
// is counted as WINDOWS_SIZE samples actually
|
// is counted as window_size samples actually
|
||||||
// because hw division for counters is painful
|
// because hw division for counters is painful
|
||||||
// so we just increased the counter sizes
|
// so we just increased the counter sizes
|
||||||
|
|
||||||
@@ -102,6 +104,7 @@ module out_axis_fifo #(
|
|||||||
wr_cnt <= 32'b0;
|
wr_cnt <= 32'b0;
|
||||||
wr_batch_tgt <= 32'b0;
|
wr_batch_tgt <= 32'b0;
|
||||||
wr_total <= 32'b0;
|
wr_total <= 32'b0;
|
||||||
|
window_size_reg <= 32'd1;
|
||||||
batch_req <= 0;
|
batch_req <= 0;
|
||||||
finish <= 0;
|
finish <= 0;
|
||||||
|
|
||||||
@@ -115,6 +118,7 @@ module out_axis_fifo #(
|
|||||||
wr_state <= WR_CHECK;
|
wr_state <= WR_CHECK;
|
||||||
wr_total <= smp_num * ACCUM_WIDTH;
|
wr_total <= smp_num * ACCUM_WIDTH;
|
||||||
wr_batch_tgt <= 32'b0;
|
wr_batch_tgt <= 32'b0;
|
||||||
|
window_size_reg <= window_size_safe;
|
||||||
batch_req <= 0;
|
batch_req <= 0;
|
||||||
finish <= 0;
|
finish <= 0;
|
||||||
end
|
end
|
||||||
@@ -126,9 +130,9 @@ module out_axis_fifo #(
|
|||||||
if ((wr_data_count < (FIFO_WDEPTH - (PACKET_SIZE / (ACCUM_WIDTH / 8)))) && ~wr_rst_busy) begin
|
if ((wr_data_count < (FIFO_WDEPTH - (PACKET_SIZE / (ACCUM_WIDTH / 8)))) && ~wr_rst_busy) begin
|
||||||
batch_req <= 1;
|
batch_req <= 1;
|
||||||
// should give us exactly PACKET_SIZE * 8 bits
|
// should give us exactly PACKET_SIZE * 8 bits
|
||||||
// multiplied by WINDOW_SIZE, because we count
|
// multiplied by window_size, because we count
|
||||||
// each given ACCUM_WIDTH word as WINDOWS_SIZE samples !!!
|
// each given ACCUM_WIDTH word as window_size samples !!!
|
||||||
wr_batch_tgt <= wr_batch_tgt + (8 * WINDOW_SIZE * PACKET_SIZE);
|
wr_batch_tgt <= wr_batch_tgt + (8 * window_size_reg * PACKET_SIZE);
|
||||||
wr_state <= WR_RUN;
|
wr_state <= WR_RUN;
|
||||||
end else begin
|
end else begin
|
||||||
batch_req <= 0;
|
batch_req <= 0;
|
||||||
@@ -150,8 +154,8 @@ module out_axis_fifo #(
|
|||||||
|
|
||||||
if (din_valid) begin
|
if (din_valid) begin
|
||||||
// data supplied
|
// data supplied
|
||||||
// count as we got WINDOW_SIZE samples
|
// count as we got window_size samples
|
||||||
wr_cnt <= wr_cnt + ACCUM_WIDTH * WINDOW_SIZE;
|
wr_cnt <= wr_cnt + ACCUM_WIDTH * window_size_reg;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -13,20 +13,24 @@ FPGA_ARCH = artix7
|
|||||||
|
|
||||||
RTL_DIR = ../src
|
RTL_DIR = ../src
|
||||||
|
|
||||||
|
# Simulation settings
|
||||||
|
SIM_TOP = tb_accumulator_top
|
||||||
|
SIM_RUNTIME ?= 10000 us
|
||||||
|
|
||||||
include ../../../scripts/vivado.mk
|
# Design sources only
|
||||||
|
SYN_FILES += $(filter-out %_tb.sv,$(sort $(shell find $(RTL_DIR) -type f -name '*.v' -o -type f -name '*.sv')))
|
||||||
|
|
||||||
SYN_FILES += $(sort $(shell find ../src -type f \( -name '*.v' -o -name '*.sv' \)))
|
# 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
|
||||||
|
|
||||||
XCI_FILES = $(sort $(shell find ../src -type f -name '*.xci'))
|
XCI_FILES = $(sort $(shell find $(RTL_DIR) -type f -name '*.xci'))
|
||||||
|
|
||||||
XDC_FILES += ../../../constraints/ax7a035b.xdc
|
XDC_FILES += ../../../constraints/ax7a035b.xdc
|
||||||
XDC_FILES += test_timing.xdc
|
XDC_FILES += test_timing.xdc
|
||||||
|
|
||||||
SYN_FILES += out_axis_fifo_tb.sv
|
include ../../../scripts/vivado.mk
|
||||||
SYN_FILES += accum_full_tb.sv
|
include ../../../scripts/questa.mk
|
||||||
SIM_TOP = tb_accumulator_top
|
|
||||||
|
|
||||||
|
|
||||||
program: $(PROJECT).bit
|
program: $(PROJECT).bit
|
||||||
echo "open_hw_manager" > program.tcl
|
echo "open_hw_manager" > program.tcl
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module tb_accumulator_top;
|
|||||||
localparam DATA_WIDTH = 12;
|
localparam DATA_WIDTH = 12;
|
||||||
localparam ACCUM_WIDTH = 32;
|
localparam ACCUM_WIDTH = 32;
|
||||||
localparam N_MAX = 4096;
|
localparam N_MAX = 4096;
|
||||||
localparam WINDOW_SIZE = 65;
|
localparam MAX_WINDOW_SIZE = 65;
|
||||||
localparam PACKET_SIZE = 1024;
|
localparam PACKET_SIZE = 1024;
|
||||||
localparam READ_BATCH_SIZE = (PACKET_SIZE*8)/ACCUM_WIDTH;
|
localparam READ_BATCH_SIZE = (PACKET_SIZE*8)/ACCUM_WIDTH;
|
||||||
localparam MAX_WORDS = N_MAX;
|
localparam MAX_WORDS = N_MAX;
|
||||||
@@ -20,6 +20,7 @@ module tb_accumulator_top;
|
|||||||
logic start;
|
logic start;
|
||||||
logic [31:0] smp_num;
|
logic [31:0] smp_num;
|
||||||
logic [15:0] seq_num;
|
logic [15:0] seq_num;
|
||||||
|
logic [31:0] window_size;
|
||||||
|
|
||||||
logic req_ready;
|
logic req_ready;
|
||||||
wire send_req;
|
wire send_req;
|
||||||
@@ -50,7 +51,6 @@ module tb_accumulator_top;
|
|||||||
.DATA_WIDTH(DATA_WIDTH),
|
.DATA_WIDTH(DATA_WIDTH),
|
||||||
.ACCUM_WIDTH(ACCUM_WIDTH),
|
.ACCUM_WIDTH(ACCUM_WIDTH),
|
||||||
.N_MAX(N_MAX),
|
.N_MAX(N_MAX),
|
||||||
.WINDOW_SIZE(WINDOW_SIZE),
|
|
||||||
.PACKET_SIZE(PACKET_SIZE)
|
.PACKET_SIZE(PACKET_SIZE)
|
||||||
) dut (
|
) dut (
|
||||||
.clk_in(clk_in),
|
.clk_in(clk_in),
|
||||||
@@ -60,6 +60,7 @@ module tb_accumulator_top;
|
|||||||
.start(start),
|
.start(start),
|
||||||
.smp_num(smp_num),
|
.smp_num(smp_num),
|
||||||
.seq_num(seq_num),
|
.seq_num(seq_num),
|
||||||
|
.window_size(window_size),
|
||||||
.eth_clk_in(eth_clk_in),
|
.eth_clk_in(eth_clk_in),
|
||||||
.req_ready(req_ready),
|
.req_ready(req_ready),
|
||||||
.send_req(send_req),
|
.send_req(send_req),
|
||||||
@@ -104,6 +105,7 @@ module tb_accumulator_top;
|
|||||||
s_axis_tvalid = 1'b0;
|
s_axis_tvalid = 1'b0;
|
||||||
smp_num = '0;
|
smp_num = '0;
|
||||||
seq_num = '0;
|
seq_num = '0;
|
||||||
|
window_size = 32'd1;
|
||||||
req_ready = 1'b0;
|
req_ready = 1'b0;
|
||||||
m_axis_tready = 1'b1;
|
m_axis_tready = 1'b1;
|
||||||
clear_scoreboard();
|
clear_scoreboard();
|
||||||
@@ -141,13 +143,14 @@ module tb_accumulator_top;
|
|||||||
|
|
||||||
task automatic run_test(
|
task automatic run_test(
|
||||||
input integer test_id,
|
input integer test_id,
|
||||||
|
input integer window_size_i,
|
||||||
input integer seq_num_i,
|
input integer seq_num_i,
|
||||||
input integer smp_num_i,
|
input integer smp_num_i,
|
||||||
input bit randomize_data,
|
input bit randomize_data,
|
||||||
input integer base_value,
|
input integer base_value,
|
||||||
input string test_name
|
input string test_name
|
||||||
);
|
);
|
||||||
logic [DATA_WIDTH-1:0] sample_mem [0:MAX_SEQ_NUM-1][0:(N_MAX*WINDOW_SIZE)-1];
|
logic [DATA_WIDTH-1:0] sample_mem [0:MAX_SEQ_NUM-1][0:(N_MAX*MAX_WINDOW_SIZE)-1];
|
||||||
integer seq_idx;
|
integer seq_idx;
|
||||||
integer sample_idx;
|
integer sample_idx;
|
||||||
integer word_idx;
|
integer word_idx;
|
||||||
@@ -165,22 +168,25 @@ module tb_accumulator_top;
|
|||||||
tests_total = tests_total + 1;
|
tests_total = tests_total + 1;
|
||||||
errors_before = total_errors;
|
errors_before = total_errors;
|
||||||
|
|
||||||
if (smp_num_i <= 0 || smp_num_i > N_MAX * WINDOW_SIZE || (smp_num_i % WINDOW_SIZE) != 0)
|
if (window_size_i <= 0 || window_size_i > MAX_WINDOW_SIZE)
|
||||||
$fatal(1, "[%0s] invalid smp_num=%0d", test_name, smp_num_i);
|
$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 (seq_num_i <= 0 || seq_num_i > MAX_SEQ_NUM)
|
if (seq_num_i <= 0 || seq_num_i > MAX_SEQ_NUM)
|
||||||
$fatal(1, "[%0s] invalid seq_num=%0d", test_name, seq_num_i);
|
$fatal(1, "[%0s] invalid seq_num=%0d", test_name, seq_num_i);
|
||||||
|
|
||||||
$display("\n========================================");
|
$display("\n========================================");
|
||||||
$display("TEST %0d: %0s", test_id, test_name);
|
$display("TEST %0d: %0s", test_id, test_name);
|
||||||
$display("seq_num=%0d smp_num=%0d randomize=%0d", seq_num_i, smp_num_i, randomize_data);
|
$display("window_size=%0d seq_num=%0d smp_num=%0d randomize=%0d", window_size_i, seq_num_i, smp_num_i, randomize_data);
|
||||||
$display("========================================");
|
$display("========================================");
|
||||||
|
|
||||||
reset_dut();
|
reset_dut();
|
||||||
smp_num = smp_num_i;
|
smp_num = smp_num_i;
|
||||||
seq_num = seq_num_i;
|
seq_num = seq_num_i;
|
||||||
|
window_size = window_size_i;
|
||||||
req_ready = 1'b1; // приемник готов заранее
|
req_ready = 1'b1; // приемник готов заранее
|
||||||
|
|
||||||
exp_word_count = smp_num_i / WINDOW_SIZE;
|
exp_word_count = smp_num_i / window_size_i;
|
||||||
exp_packet_count = (exp_word_count + READ_BATCH_SIZE - 1) / READ_BATCH_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
|
for (seq_idx = 0; seq_idx < seq_num_i; seq_idx = seq_idx + 1) begin
|
||||||
@@ -196,8 +202,8 @@ module tb_accumulator_top;
|
|||||||
for (word_idx = 0; word_idx < exp_word_count; word_idx = word_idx + 1) begin
|
for (word_idx = 0; word_idx < exp_word_count; word_idx = word_idx + 1) begin
|
||||||
local_sum = 0;
|
local_sum = 0;
|
||||||
for (seq_idx = 0; seq_idx < seq_num_i; seq_idx = seq_idx + 1) begin
|
for (seq_idx = 0; seq_idx < seq_num_i; seq_idx = seq_idx + 1) begin
|
||||||
for (k = 0; k < WINDOW_SIZE; k = k + 1)
|
for (k = 0; k < window_size_i; k = k + 1)
|
||||||
local_sum = local_sum + sample_mem[seq_idx][word_idx * WINDOW_SIZE + k];
|
local_sum = local_sum + sample_mem[seq_idx][word_idx * window_size_i + k];
|
||||||
end
|
end
|
||||||
expected_words[word_idx] = local_sum[ACCUM_WIDTH-1:0];
|
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]);
|
$display(" expected[%0d] = %0d (0x%08x)", word_idx, expected_words[word_idx], expected_words[word_idx]);
|
||||||
@@ -318,16 +324,17 @@ module tb_accumulator_top;
|
|||||||
|
|
||||||
reset_dut();
|
reset_dut();
|
||||||
|
|
||||||
run_test(1, 1, 1 * WINDOW_SIZE, 1'b0, 1, "deterministic_small");
|
run_test(1, 1, 1, 1 * 1, 1'b0, 1, "w1_deterministic_small");
|
||||||
// $finish;
|
run_test(2, 1, 2, 16 * 1, 1'b1, 0, "w1_random_seq2_smp16");
|
||||||
run_test(2, 2, 1 * WINDOW_SIZE, 1'b1, 0, "random_seq3_smp8");
|
run_test(3, 2, 2, 16 * 2, 1'b1, 0, "w2_random_seq2_smp32");
|
||||||
run_test(3, 1, 16 * WINDOW_SIZE, 1'b1, 0, "random_seq5_smp16_multi_packet");
|
run_test(4, 3, 1, 16 * 3, 1'b1, 0, "w3_random_seq1_smp48");
|
||||||
run_test(4, 2, 12 * WINDOW_SIZE, 1'b1, 0, "random_seq7_smp12");
|
run_test(5, 4, 2, 12 * 4, 1'b1, 0, "w4_random_seq2_smp48");
|
||||||
run_test(5, 4, 256 * WINDOW_SIZE, 1'b1, 0, "random_max_smpnum");
|
run_test(6, 65, 4, 256 * 65, 1'b1, 0, "w65_random_seq4_smp16640");
|
||||||
run_test(6, 2, 1500 * WINDOW_SIZE, 1'b1, 0, "random_max_smpnum2");
|
run_test(7, 2, 20, 3 * 2, 1'b1, 0, "w2_random_20seqx3");
|
||||||
run_test(7, 20, 1 * WINDOW_SIZE, 1'b1, 0, "random_20seq");
|
run_test(8, 65, 20, 3 * 65, 1'b1, 0, "w65_random_20seqx3");
|
||||||
run_test(8, 20, 3 * WINDOW_SIZE, 1'b1, 0, "random_20seqx3");
|
run_test(9, 1, 200, 1 * 1, 1'b1, 0, "w1_random_200seq");
|
||||||
run_test(9, 200, 1 * WINDOW_SIZE, 1'b1, 0, "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");
|
||||||
|
|
||||||
$display("\n========================================");
|
$display("\n========================================");
|
||||||
$display("ALL TESTS COMPLETED");
|
$display("ALL TESTS COMPLETED");
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# 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)
|
||||||
+736
@@ -0,0 +1,736 @@
|
|||||||
|
# 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()
|
||||||
@@ -0,0 +1,505 @@
|
|||||||
|
<?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>
|
||||||
Reference in New Issue
Block a user