// Bounded synthesizable integer inference path, not a generic model runtime. // sx=sw=1, sy=2, zx=zw=zy=0; round-to-nearest-even, int8 saturation, ReLU. module cnn_conv3x3 #( parameter logic signed [7:0] K00 = 8'sd1, parameter logic signed [7:0] K01 = 8'sd0, parameter logic signed [7:0] K02 = -8'sd1, parameter logic signed [7:0] K10 = 8'sd1, parameter logic signed [7:0] K11 = 8'sd0, parameter logic signed [7:0] K12 = -8'sd1, parameter logic signed [7:0] K20 = 8'sd1, parameter logic signed [7:0] K21 = 8'sd0, parameter logic signed [7:0] K22 = -8'sd1, parameter logic signed [23:0] BIAS = 24'sd8 ) ( input logic clk, reset, input logic in_valid, output logic in_ready, input logic signed [7:0] in_pixel, output logic out_valid, input logic out_ready, output logic [1:0] out_row, out_col, output logic signed [23:0] out_acc, output logic signed [7:0] out_code, output logic [7:0] out_activation, output logic done, output logic [7:0] pooled ); logic window_valid, window_ready, input_done; logic [1:0] window_row, window_col; logic signed [7:0] w00, w01, w02, w10, w11, w12, w20, w21, w22; logic signed [15:0] p00, p01, p02, p10, p11, p12, p20, p21, p22; logic signed [23:0] sum, rounded; logic signed [7:0] clipped; logic [7:0] pool_max; cnn_window3x3 windows ( .clk(clk), .reset(reset), .in_valid(in_valid), .in_ready(in_ready), .in_pixel(in_pixel), .out_valid(window_valid), .out_ready(window_ready), .out_row(window_row), .out_col(window_col), .w00(w00), .w01(w01), .w02(w02), .w10(w10), .w11(w11), .w12(w12), .w20(w20), .w21(w21), .w22(w22), .input_done(input_done) ); assign window_ready = !out_valid || out_ready; // Explicit full-width signed products; sign-extend before the wide sum. assign p00 = $signed(w00) * $signed(K00); assign p01 = $signed(w01) * $signed(K01); assign p02 = $signed(w02) * $signed(K02); assign p10 = $signed(w10) * $signed(K10); assign p11 = $signed(w11) * $signed(K11); assign p12 = $signed(w12) * $signed(K12); assign p20 = $signed(w20) * $signed(K20); assign p21 = $signed(w21) * $signed(K21); assign p22 = $signed(w22) * $signed(K22); always @* begin sum = BIAS; sum = sum + $signed({{8{p00[15]}}, p00}); sum = sum + $signed({{8{p01[15]}}, p01}); sum = sum + $signed({{8{p02[15]}}, p02}); sum = sum + $signed({{8{p10[15]}}, p10}); sum = sum + $signed({{8{p11[15]}}, p11}); sum = sum + $signed({{8{p12[15]}}, p12}); sum = sum + $signed({{8{p20[15]}}, p20}); sum = sum + $signed({{8{p21[15]}}, p21}); sum = sum + $signed({{8{p22[15]}}, p22}); // Arithmetic shift gives floor(a/2). Odd floor at an exact half rounds up. rounded = sum >>> 1; if (sum[0] && rounded[0]) rounded = rounded + 24'sd1; if (rounded > 24'sd127) clipped = 8'sd127; else if (rounded < -24'sd128) clipped = -8'sd128; else clipped = rounded[7:0]; end always_ff @(posedge clk) begin if (reset) begin out_valid <= 0; out_row <= 0; out_col <= 0; out_acc <= 0; out_code <= 0; out_activation <= 0; pool_max <= 0; pooled <= 0; done <= 0; end else begin if (out_valid && out_ready) begin if (out_row == 0 && out_col == 0) pool_max <= out_activation; else if (out_activation > pool_max) pool_max <= out_activation; if (out_row == 1 && out_col == 1) begin pooled <= out_activation > pool_max ? out_activation : pool_max; done <= 1; end end if (window_ready) begin out_valid <= window_valid; if (window_valid) begin out_row <= window_row; out_col <= window_col; out_acc <= sum; out_code <= clipped; out_activation <= clipped[7] ? 8'd0 : $unsigned(clipped); end end end end endmodule