Implement discrete dynamic NEAR ladder strategy

This commit is contained in:
2026-04-16 19:40:36 +08:00
parent e4fdbdfeec
commit 7c56def929
5 changed files with 334 additions and 56 deletions

View File

@@ -84,7 +84,7 @@ module tb_jls_near_ctrl;
strip_done_valid = 1'b0;
@(posedge clk);
#1;
if (current_near !== 6'd1 || actual_bits_cumulative !== 48'd800 ||
if (current_near !== 6'd4 || actual_bits_cumulative !== 48'd800 ||
target_bits_cumulative !== 48'd512) begin
$fatal(1, "ratio=2 first strip update mismatch");
end
@@ -96,7 +96,7 @@ module tb_jls_near_ctrl;
strip_done_valid = 1'b0;
@(posedge clk);
#1;
if (current_near !== 6'd2 || actual_bits_cumulative !== 48'd1120 ||
if (current_near !== 6'd8 || actual_bits_cumulative !== 48'd1120 ||
target_bits_cumulative !== 48'd1024) begin
$fatal(1, "ratio=2 second strip update mismatch");
end
@@ -108,7 +108,7 @@ module tb_jls_near_ctrl;
strip_done_valid = 1'b0;
@(posedge clk);
#1;
if (current_near !== 6'd1 || actual_bits_cumulative !== 48'd1200 ||
if (current_near !== 6'd4 || actual_bits_cumulative !== 48'd1200 ||
target_bits_cumulative !== 48'd1536) begin
$fatal(1, "ratio=2 third strip update mismatch");
end
@@ -131,15 +131,15 @@ module tb_jls_near_ctrl;
$fatal(1, "lossless ratio should force NEAR to 0 without target miss");
end
// Repeated over-target strips saturate at MAX_NEAR and then set the sticky
// miss flag on the next over-target completion.
// Repeated over-target strips jump up the discrete NEAR ladder and then
// set the sticky miss flag once the ladder is already saturated.
@(posedge clk);
image_ratio = 4'd1;
image_start_valid = 1'b1;
@(posedge clk);
image_start_valid = 1'b0;
for (loop_index = 0; loop_index < 32; loop_index = loop_index + 1) begin
for (loop_index = 0; loop_index < 8; loop_index = loop_index + 1) begin
@(posedge clk);
strip_output_bytes = 32'd1000;
strip_done_valid = 1'b1;

View File

@@ -230,14 +230,26 @@ SOF 协议:
- `ratio=0` 或非法 ratio 时,整帧 `NEAR` 固定为 0不进行动态调整。
- `ratio=1/2/3` 时,每个条带 frame 结束后比较累计实际输出 bit 数与累计目标
bit 数,并调整下一条带 frame 的 `NEAR`。
- 第一版调节策略:
- 若累计实际输出 bit 数大于累计目标 bit 数,`NEAR = NEAR + 1`。
- 若累计实际输出 bit 数小于累计目标 bit 数且 `NEAR > 0`
`NEAR = NEAR - 1`。
- 若二者相等,`NEAR` 保持不变。
- `NEAR` 钳位到 `0..31`
- 如果第一版效果不理想,再参考 CN102088602A 所述方法优化调节步长和累计偏差
控制策略。
- 当前实现采用离散 `NEAR` 档位集合
`{0, 1, 2, 4, 8, 16, 31}`,而不是逐条带线性 `+1/-1`。
- 第一条带结束后,控制器根据首条带 `actual_bits / target_bits` 的粗分档直接跳到
一个离散 `NEAR` 档位;当前 RTL 为便于时序收敛,采用移位加法近似阈值:
- `<= 1.125x target` -> `NEAR=0`
- `<= 1.25x target` -> `NEAR=1`
- `<= 1.5x target` -> `NEAR=2`
- `<= 2x target` -> `NEAR=4`
- `<= 3x target` -> `NEAR=8`
- `<= 5x target` -> `NEAR=16`
- `> 5x target` -> `NEAR=31`
- 第二条带及后续条带使用累计 bit 偏差做离散档位微调,而不是重新全量估计:
- 若累计实际 bit 数 `> target + target/4`,档位 `+2`
- 若累计实际 bit 数 `> target + target/16`,档位 `+1`
- 若累计实际 bit 数 `< target - target/4`,档位 `-2`
- 若累计实际 bit 数 `< target - target/16`,档位 `-1`
- 否则保持当前档位不变
- 档位更新后仍按集合 `{0,1,2,4,8,16,31}` 钳位,不输出非整数 `NEAR`。
- 若 `NEAR` 已处于 `31` 且累计实际 bit 数仍高于累计目标 bit 数,置
`target_miss_at_max_near` 粘滞标志,供验证报告使用。
### 4.3 误差验收

View File

@@ -7,8 +7,9 @@
// Example : For ratio=2, target bits are source bits divided by 4.
//
// Dynamic NEAR controller. This project-specific controller keeps NEAR at 0
// for lossless/invalid ratios and applies a simple cumulative actual-vs-target
// step after each standalone strip frame is fully output.
// for lossless/invalid ratios, uses a first-strip coarse jump to a discrete
// NEAR level, then applies cumulative actual-vs-target micro-adjustments on the
// remaining strip frames.
`default_nettype none
@@ -63,12 +64,22 @@ module jls_near_ctrl #(
localparam logic [3:0] RATIO_1_TO_4 = 4'd2;
localparam logic [3:0] RATIO_1_TO_8 = 4'd3;
// Saturated project maximum NEAR value.
localparam logic [5:0] MAX_NEAR_VALUE = MAX_NEAR[5:0];
// Discrete NEAR ladder used by the dynamic controller:
// level 0 -> 0, 1 -> 1, 2 -> 2, 3 -> 4, 4 -> 8, 5 -> 16, 6 -> 31.
localparam logic [2:0] NEAR_LEVEL_0 = 3'd0;
localparam logic [2:0] NEAR_LEVEL_1 = 3'd1;
localparam logic [2:0] NEAR_LEVEL_2 = 3'd2;
localparam logic [2:0] NEAR_LEVEL_4 = 3'd3;
localparam logic [2:0] NEAR_LEVEL_8 = 3'd4;
localparam logic [2:0] NEAR_LEVEL_16 = 3'd5;
localparam logic [2:0] NEAR_LEVEL_31 = 3'd6;
// Latched ratio for the current original image.
logic [3:0] active_ratio;
// Internal discrete NEAR level register. current_near is decoded from it.
logic [2:0] current_near_level;
// Strip-level source and target bit calculations.
logic [47:0] strip_pixel_count_ext;
logic [47:0] strip_source_bits;
@@ -80,24 +91,86 @@ module jls_near_ctrl #(
logic [47:0] target_bits_sum;
// Registered strip-completion update. This splits the 48-bit adders from
// the actual-vs-target compare and NEAR step logic for 250 MHz timing.
// the ratio-bucket compare and NEAR-level update logic for 250 MHz timing.
logic pending_update_valid;
logic [47:0] pending_actual_bits_sum;
logic [47:0] pending_target_bits_sum;
logic pending_ratio_is_lossless_or_invalid;
logic pending_first_strip_update;
// Ratio classification and NEAR update decisions.
// Ratio classification.
logic ratio_is_lossless_or_invalid;
logic actual_over_target;
logic actual_under_target;
logic near_can_increase;
logic near_can_decrease;
logic near_is_max;
// Shift-add thresholds for the first-strip coarse jump. These are chosen to
// approximate the project review buckets while avoiding wide constant
// multipliers in the 250 MHz strip-control path.
logic [47:0] target_plus_one_eighth;
logic [47:0] target_plus_one_quarter;
logic [47:0] target_plus_one_half;
logic [47:0] target_times_two;
logic [47:0] target_times_three;
logic [47:0] target_times_five;
logic [2:0] first_strip_level_next;
// Cumulative micro-adjust thresholds for later strips. +/-1/16 is the hold
// band; beyond +/-1/4 the controller skips one NEAR rung.
logic [47:0] target_plus_sixteenth;
logic [47:0] target_minus_sixteenth;
logic [47:0] target_plus_quarter;
logic [47:0] target_minus_quarter;
logic step_up_one_level;
logic step_up_two_levels;
logic step_down_one_level;
logic step_down_two_levels;
// Discrete NEAR ladder movement.
logic [2:0] near_level_plus_one;
logic [2:0] near_level_plus_two;
logic [2:0] near_level_minus_one;
logic [2:0] near_level_minus_two;
logic [2:0] adjusted_near_level_next;
// Max-level sticky miss reporting.
logic near_level_is_max;
logic target_still_missed_at_max;
always_comb begin
update_busy = pending_update_valid;
end
always_comb begin
current_near = 6'd0;
case (current_near_level)
NEAR_LEVEL_1: begin
current_near = 6'd1;
end
NEAR_LEVEL_2: begin
current_near = 6'd2;
end
NEAR_LEVEL_4: begin
current_near = 6'd4;
end
NEAR_LEVEL_8: begin
current_near = 6'd8;
end
NEAR_LEVEL_16: begin
current_near = 6'd16;
end
NEAR_LEVEL_31: begin
current_near = 6'd31;
end
default: begin
current_near = 6'd0;
end
endcase
end
always_comb begin
strip_pixel_count_ext = {16'd0, strip_pixel_count};
end
@@ -168,53 +241,236 @@ module jls_near_ctrl #(
always_comb begin
ratio_is_lossless_or_invalid = 1'b0;
case (active_ratio)
RATIO_1_TO_2: ratio_is_lossless_or_invalid = 1'b0;
RATIO_1_TO_4: ratio_is_lossless_or_invalid = 1'b0;
RATIO_1_TO_8: ratio_is_lossless_or_invalid = 1'b0;
default: ratio_is_lossless_or_invalid = 1'b1;
RATIO_1_TO_2: begin
ratio_is_lossless_or_invalid = 1'b0;
end
RATIO_1_TO_4: begin
ratio_is_lossless_or_invalid = 1'b0;
end
RATIO_1_TO_8: begin
ratio_is_lossless_or_invalid = 1'b0;
end
default: begin
ratio_is_lossless_or_invalid = 1'b1;
end
endcase
end
always_comb begin
actual_over_target = 1'b0;
if (pending_actual_bits_sum > pending_target_bits_sum) begin
actual_over_target = 1'b1;
target_plus_one_eighth = pending_target_bits_sum + {3'b000, pending_target_bits_sum[47:3]};
target_plus_one_quarter = pending_target_bits_sum + {2'b00, pending_target_bits_sum[47:2]};
target_plus_one_half = pending_target_bits_sum + {1'b0, pending_target_bits_sum[47:1]};
target_times_two = {pending_target_bits_sum[46:0], 1'b0};
target_times_three = pending_target_bits_sum + {pending_target_bits_sum[46:0], 1'b0};
target_times_five = pending_target_bits_sum + {pending_target_bits_sum[45:0], 2'b00};
end
always_comb begin
first_strip_level_next = NEAR_LEVEL_31;
if (pending_actual_bits_sum <= target_plus_one_eighth) begin
first_strip_level_next = NEAR_LEVEL_0;
end else if (pending_actual_bits_sum <= target_plus_one_quarter) begin
first_strip_level_next = NEAR_LEVEL_1;
end else if (pending_actual_bits_sum <= target_plus_one_half) begin
first_strip_level_next = NEAR_LEVEL_2;
end else if (pending_actual_bits_sum <= target_times_two) begin
first_strip_level_next = NEAR_LEVEL_4;
end else if (pending_actual_bits_sum <= target_times_three) begin
first_strip_level_next = NEAR_LEVEL_8;
end else if (pending_actual_bits_sum <= target_times_five) begin
first_strip_level_next = NEAR_LEVEL_16;
end
end
always_comb begin
actual_under_target = 1'b0;
if (pending_actual_bits_sum < pending_target_bits_sum) begin
actual_under_target = 1'b1;
target_plus_sixteenth = pending_target_bits_sum + {4'b0000, pending_target_bits_sum[47:4]};
target_minus_sixteenth = pending_target_bits_sum - {4'b0000, pending_target_bits_sum[47:4]};
target_plus_quarter = pending_target_bits_sum + {2'b00, pending_target_bits_sum[47:2]};
target_minus_quarter = pending_target_bits_sum - {2'b00, pending_target_bits_sum[47:2]};
end
always_comb begin
step_up_one_level = 1'b0;
if (pending_actual_bits_sum > target_plus_sixteenth) begin
step_up_one_level = 1'b1;
end
end
always_comb begin
near_is_max = 1'b0;
if (current_near >= MAX_NEAR_VALUE) begin
near_is_max = 1'b1;
step_up_two_levels = 1'b0;
if (pending_actual_bits_sum > target_plus_quarter) begin
step_up_two_levels = 1'b1;
end
end
always_comb begin
near_can_increase = 1'b0;
if (!pending_ratio_is_lossless_or_invalid && actual_over_target && !near_is_max) begin
near_can_increase = 1'b1;
step_down_one_level = 1'b0;
if (pending_actual_bits_sum < target_minus_sixteenth) begin
step_down_one_level = 1'b1;
end
end
always_comb begin
near_can_decrease = 1'b0;
if (!pending_ratio_is_lossless_or_invalid && actual_under_target &&
current_near != 6'd0) begin
near_can_decrease = 1'b1;
step_down_two_levels = 1'b0;
if (pending_actual_bits_sum < target_minus_quarter) begin
step_down_two_levels = 1'b1;
end
end
always_comb begin
near_level_plus_one = current_near_level;
case (current_near_level)
NEAR_LEVEL_0: begin
near_level_plus_one = NEAR_LEVEL_1;
end
NEAR_LEVEL_1: begin
near_level_plus_one = NEAR_LEVEL_2;
end
NEAR_LEVEL_2: begin
near_level_plus_one = NEAR_LEVEL_4;
end
NEAR_LEVEL_4: begin
near_level_plus_one = NEAR_LEVEL_8;
end
NEAR_LEVEL_8: begin
near_level_plus_one = NEAR_LEVEL_16;
end
default: begin
near_level_plus_one = NEAR_LEVEL_31;
end
endcase
end
always_comb begin
near_level_plus_two = current_near_level;
case (current_near_level)
NEAR_LEVEL_0: begin
near_level_plus_two = NEAR_LEVEL_2;
end
NEAR_LEVEL_1: begin
near_level_plus_two = NEAR_LEVEL_4;
end
NEAR_LEVEL_2: begin
near_level_plus_two = NEAR_LEVEL_8;
end
NEAR_LEVEL_4: begin
near_level_plus_two = NEAR_LEVEL_16;
end
default: begin
near_level_plus_two = NEAR_LEVEL_31;
end
endcase
end
always_comb begin
near_level_minus_one = current_near_level;
case (current_near_level)
NEAR_LEVEL_1: begin
near_level_minus_one = NEAR_LEVEL_0;
end
NEAR_LEVEL_2: begin
near_level_minus_one = NEAR_LEVEL_1;
end
NEAR_LEVEL_4: begin
near_level_minus_one = NEAR_LEVEL_2;
end
NEAR_LEVEL_8: begin
near_level_minus_one = NEAR_LEVEL_4;
end
NEAR_LEVEL_16: begin
near_level_minus_one = NEAR_LEVEL_8;
end
NEAR_LEVEL_31: begin
near_level_minus_one = NEAR_LEVEL_16;
end
default: begin
near_level_minus_one = NEAR_LEVEL_0;
end
endcase
end
always_comb begin
near_level_minus_two = current_near_level;
case (current_near_level)
NEAR_LEVEL_0: begin
near_level_minus_two = NEAR_LEVEL_0;
end
NEAR_LEVEL_1: begin
near_level_minus_two = NEAR_LEVEL_0;
end
NEAR_LEVEL_2: begin
near_level_minus_two = NEAR_LEVEL_0;
end
NEAR_LEVEL_4: begin
near_level_minus_two = NEAR_LEVEL_1;
end
NEAR_LEVEL_8: begin
near_level_minus_two = NEAR_LEVEL_2;
end
NEAR_LEVEL_16: begin
near_level_minus_two = NEAR_LEVEL_4;
end
default: begin
near_level_minus_two = NEAR_LEVEL_8;
end
endcase
end
always_comb begin
adjusted_near_level_next = current_near_level;
if (step_up_two_levels) begin
adjusted_near_level_next = near_level_plus_two;
end else if (step_up_one_level) begin
adjusted_near_level_next = near_level_plus_one;
end else if (step_down_two_levels) begin
adjusted_near_level_next = near_level_minus_two;
end else if (step_down_one_level) begin
adjusted_near_level_next = near_level_minus_one;
end
end
always_comb begin
near_level_is_max = 1'b0;
if (current_near_level == NEAR_LEVEL_31) begin
near_level_is_max = 1'b1;
end
end
always_comb begin
target_still_missed_at_max = 1'b0;
if (near_level_is_max && (pending_actual_bits_sum > pending_target_bits_sum)) begin
target_still_missed_at_max = 1'b1;
end
end
always_ff @(posedge clk) begin
if (rst) begin
active_ratio <= RATIO_LOSSLESS;
current_near <= 6'd0;
current_near_level <= NEAR_LEVEL_0;
actual_bits_cumulative <= 48'd0;
target_bits_cumulative <= 48'd0;
target_miss_at_max_near <= 1'b0;
@@ -222,10 +478,11 @@ module jls_near_ctrl #(
pending_actual_bits_sum <= 48'd0;
pending_target_bits_sum <= 48'd0;
pending_ratio_is_lossless_or_invalid <= 1'b1;
pending_first_strip_update <= 1'b0;
end else begin
if (image_start_valid) begin
active_ratio <= image_ratio;
current_near <= 6'd0;
current_near_level <= NEAR_LEVEL_0;
actual_bits_cumulative <= 48'd0;
target_bits_cumulative <= 48'd0;
target_miss_at_max_near <= 1'b0;
@@ -233,19 +490,20 @@ module jls_near_ctrl #(
pending_actual_bits_sum <= 48'd0;
pending_target_bits_sum <= 48'd0;
pending_ratio_is_lossless_or_invalid <= 1'b1;
pending_first_strip_update <= 1'b0;
end else if (pending_update_valid) begin
actual_bits_cumulative <= pending_actual_bits_sum;
target_bits_cumulative <= pending_target_bits_sum;
if (pending_ratio_is_lossless_or_invalid) begin
current_near <= 6'd0;
end else if (near_can_increase) begin
current_near <= current_near + 6'd1;
end else if (near_can_decrease) begin
current_near <= current_near - 6'd1;
current_near_level <= NEAR_LEVEL_0;
end else if (pending_first_strip_update) begin
current_near_level <= first_strip_level_next;
end else begin
current_near_level <= adjusted_near_level_next;
end
if (!pending_ratio_is_lossless_or_invalid && actual_over_target && near_is_max) begin
if (target_still_missed_at_max) begin
target_miss_at_max_near <= 1'b1;
end
@@ -255,6 +513,8 @@ module jls_near_ctrl #(
pending_actual_bits_sum <= actual_bits_sum;
pending_target_bits_sum <= target_bits_sum;
pending_ratio_is_lossless_or_invalid <= ratio_is_lossless_or_invalid;
pending_first_strip_update <= (actual_bits_cumulative == 48'd0) &&
(target_bits_cumulative == 48'd0);
end
end
end