use crate::verilog::V;
pub fn emit_requant_q15() -> String {
let mut v = V::new();
v.banner("requant_q15 — half-width Q0.15 epilogue (≤1 ulp vs Q0.31)");
v.comment("Inputs:");
v.comment(" acc i32 accumulator");
v.comment(" m0 i16 Q0.15 significand, in [2^14, 2^15)");
v.comment(" shift u4 right-shift count, in [0, 15]");
v.comment(" out_zp i32 output zero point");
v.comment("Output:");
v.comment(" q i8");
v.blank();
v.module(
"requant_q15",
&[],
&[
"input logic signed [31:0] acc".into(),
"input logic signed [15:0] m0".into(),
"input logic [3:0] shift".into(),
"input logic signed [31:0] out_zp".into(),
"output logic signed [7:0] q".into(),
],
|v| {
v.line("logic signed [47:0] ab;");
v.line("logic signed [47:0] nudge;");
v.line("logic signed [47:0] sum;");
v.line("logic signed [31:0] srdhm_out;");
v.blank();
v.always_comb(|v| {
v.line("ab = $signed(acc) * $signed(m0); // i32 × i16 → i48");
v.line("nudge = ab >= 0 ? 48'sd16384 : -48'sd16383; // +2^14 / -(2^14 - 1)");
v.line("sum = ab + nudge;");
v.comment("Truncate-toward-zero division by 2^15");
v.line("if (sum >= 0) srdhm_out = sum[46:15];");
v.line("else srdhm_out = -((-sum) >>> 15);");
});
v.blank();
v.line("logic signed [31:0] mask_v, rem_v, thresh_v, rdpot_out;");
v.always_comb(|v| {
v.line("if (shift == 4'd0) begin");
v.block(|v| {
v.line("mask_v = 32'sd0;");
v.line("rem_v = 32'sd0;");
v.line("thresh_v = 32'sd0;");
v.line("rdpot_out = srdhm_out;");
});
v.line("end else begin");
v.block(|v| {
v.line("mask_v = (32'sd1 <<< shift) - 32'sd1;");
v.line("rem_v = srdhm_out & mask_v;");
v.line("thresh_v = (mask_v >>> 1) + (srdhm_out < 0 ? 32'sd1 : 32'sd0);");
v.line("rdpot_out = (srdhm_out >>> shift) + ((rem_v > thresh_v) ? 32'sd1 : 32'sd0);");
});
v.line("end");
});
v.blank();
v.line("logic signed [31:0] zp_added;");
v.always_comb(|v| {
v.line("zp_added = rdpot_out + out_zp;");
v.line("if (zp_added > 32'sd127) q = 8'sh7F;");
v.line("else if (zp_added < -32'sd128) q = 8'sh80;");
v.line("else q = zp_added[7:0];");
});
},
);
v.into_string()
}
pub fn emit_requant_q31() -> String {
let mut v = V::new();
v.banner("requant_q31 — Q0.31 requantize: srdhm + rounding-shift + sat_i8");
v.comment("Inputs:");
v.comment(" acc i32 GEMM/conv accumulator");
v.comment(" m0 i32 Q0.31 significand, in [2^30, 2^31)");
v.comment(" shift u5 right-shift count, in [0, 31]");
v.comment(" out_zp i32 output zero point");
v.comment("Output:");
v.comment(" q i8 saturated, requantized code");
v.comment("");
v.comment("Mirrors gemmlowp / TFLite-Micro / CMSIS-NN. The lone");
v.comment("saturation case for SRDHM is (i32::MIN, i32::MIN) →");
v.comment("i32::MAX; we don't expect to hit it, but the guard is");
v.comment("free and matches the Rust reference.");
v.blank();
v.module(
"requant_q31",
&[],
&[
"input logic signed [31:0] acc".into(),
"input logic signed [31:0] m0".into(),
"input logic [4:0] shift".into(),
"input logic signed [31:0] out_zp".into(),
"output logic signed [7:0] q".into(),
],
|v| {
v.comment("─── SRDHM (saturating rounding doubling high mul) ───");
v.line("logic signed [63:0] ab;");
v.line("logic signed [63:0] nudge;");
v.line("logic signed [63:0] sum;");
v.line("logic signed [31:0] srdhm_out;");
v.line("logic srdhm_overflow;");
v.blank();
v.always_comb(|v| {
v.line("ab = acc * m0; // 64-bit signed");
v.line("nudge = ab >= 0 ? 64'sd1073741824 // +2^30");
v.line(" : -64'sd1073741823; // 1 - 2^30");
v.line("sum = ab + nudge;");
v.comment("Truncate-toward-zero division by 2^31 (NOT >>>, which");
v.comment("rounds toward -inf). For sum >= 0, identical to >> 31;");
v.comment("for sum < 0, we negate, shift, then negate.");
v.line("if (sum >= 0) begin");
v.block(|v| v.line("srdhm_out = sum[62:31];"));
v.line("end else begin");
v.block(|v| v.line("srdhm_out = -((-sum) >>> 31);"));
v.line("end");
v.comment("Saturating overflow: only (MIN, MIN) wraps.");
v.line("srdhm_overflow = (acc == 32'sh80000000) && (m0 == 32'sh80000000);");
v.line("if (srdhm_overflow) srdhm_out = 32'sh7FFFFFFF;");
});
v.blank();
v.comment("─── RDPOT (rounding divide by power of two) ───");
v.comment("mask = (1 << shift) - 1");
v.comment("rem = srdhm_out & mask");
v.comment("threshold= (mask >> 1) + (srdhm_out < 0)");
v.comment("rdpot_out= (srdhm_out >>> shift) + (rem > threshold)");
v.line("logic signed [31:0] mask_v;");
v.line("logic signed [31:0] rem_v;");
v.line("logic signed [31:0] thresh_v;");
v.line("logic signed [31:0] rdpot_out;");
v.blank();
v.always_comb(|v| {
v.line("if (shift == 5'd0) begin");
v.block(|v| {
v.line("mask_v = 32'sd0;");
v.line("rem_v = 32'sd0;");
v.line("thresh_v = 32'sd0;");
v.line("rdpot_out = srdhm_out;");
});
v.line("end else begin");
v.block(|v| {
v.line("mask_v = (32'sd1 <<< shift) - 32'sd1;");
v.line("rem_v = srdhm_out & mask_v;");
v.line("thresh_v = (mask_v >>> 1) + (srdhm_out < 0 ? 32'sd1 : 32'sd0);");
v.line("rdpot_out = (srdhm_out >>> shift)");
v.line(" + ((rem_v > thresh_v) ? 32'sd1 : 32'sd0);");
});
v.line("end");
});
v.blank();
v.comment("─── + out_zp, then saturate to i8 ───");
v.line("logic signed [31:0] zp_added;");
v.always_comb(|v| {
v.line("zp_added = rdpot_out + out_zp;");
v.line("if (zp_added > 32'sd127) q = 8'sh7F;");
v.line("else if (zp_added < -32'sd128) q = 8'sh80;");
v.line("else q = zp_added[7:0];");
});
},
);
v.into_string()
}