28. Signed vs Unsigned Compare

module signed_thresh(
  input  wire [7:0] sample,
  input  wire [7:0] thresh,
  output wire       gt_unsigned,
  output wire       gt_signed
);
  assign gt_unsigned = (sample > thresh);
  assign gt_signed   = ($signed(sample) > $signed(thresh));
endmodule

💡 Remember

  • In Verilog, vectors are unsigned by default; relational operators use the operands’ signedness.
  • Casting with $signed(expr) interprets the bit pattern as two’s-complement and performs a signed comparison.
  • Equality ==/!= is unaffected by signedness (but X/Z still matter); ordering operators (<, <=, >, >=) are where signed vs unsigned diverge.
  • Typical surprises: 8'h80 is 128 unsigned but −128 signed; 8'hFF is 255 unsigned but −1 signed.
  • When sizes differ, Verilog self-determines a common size; keep both sides same width to avoid accidental extension rules masking bugs.

Testbench Code

`timescale 1ns/1ps
module tb_signed_thresh;
  reg  [7:0] sample;
  reg  [7:0] thresh;

  wire gt_unsigned;
  wire gt_signed;

  wire expected_gt_unsigned;
  wire expected_gt_signed;

  wire mismatch;

  signed_thresh dut(
    .sample(sample),
    .thresh(thresh),
    .gt_unsigned(gt_unsigned),
    .gt_signed(gt_signed)
  );

  assign expected_gt_unsigned = (sample > thresh);
  assign expected_gt_signed   = ($signed(sample) > $signed(thresh));

  wire mm_u = (gt_unsigned !== expected_gt_unsigned);
  wire mm_s = (gt_signed   !== expected_gt_signed);
  assign mismatch = mm_u | mm_s;

  initial begin
    $dumpfile("tb_signed_thresh.vcd");
    $dumpvars(0,
      sample, thresh,
      gt_unsigned, gt_signed,
      expected_gt_unsigned, expected_gt_signed,
      mismatch
    );
  end

  integer TOTAL_TEST_CASES, TOTAL_PASSED_TEST_CASES, TOTAL_FAILED_TEST_CASES;
  reg [8*48-1:0] TC_NAME;

  task run_case;
    input [7:0] s;
    input [7:0] t;
    input [8*48-1:0] name;
    begin
      sample = s; thresh = t; #1;
      TC_NAME = name;
      $display("CASE=%s : sample=0x%02h thresh=0x%02h | gt_u=%0b exp_u=%0b  gt_s=%0b exp_s=%0b  %s",
               TC_NAME, sample, thresh,
               gt_unsigned, expected_gt_unsigned,
               gt_signed,   expected_gt_signed,
               mismatch ? "MISMATCH" : "OK");
      TOTAL_TEST_CASES = TOTAL_TEST_CASES + 1;
      if (!mismatch) TOTAL_PASSED_TEST_CASES = TOTAL_PASSED_TEST_CASES + 1;
      else begin
        TOTAL_FAILED_TEST_CASES = TOTAL_FAILED_TEST_CASES + 1;
        $display("  FAILED INPUTS: sample=0x%02h thresh=0x%02h EXPECTED: gt_u=%0b gt_s=%0b",
                 sample, thresh, expected_gt_unsigned, expected_gt_signed);
      end
      #4;
    end
  endtask

  initial begin
    TOTAL_TEST_CASES = 0; TOTAL_PASSED_TEST_CASES = 0; TOTAL_FAILED_TEST_CASES = 0;

    run_case(8'h80, 8'h00, "0x80_vs_0x00");
    run_case(8'hFF, 8'h00, "0xFF_vs_0x00");
    run_case(8'h80, 8'h7F, "0x80_vs_0x7F");
    run_case(8'h7F, 8'h80, "0x7F_vs_0x80");
    run_case(8'h00, 8'hFF, "0x00_vs_0xFF");
    run_case(8'h01, 8'hFF, "0x01_vs_0xFF");
    run_case(8'hFF, 8'hFE, "0xFF_vs_0xFE");
    run_case(8'hFE, 8'hF0, "0xFE_vs_0xF0");
    run_case(8'hF0, 8'hFE, "0xF0_vs_0xFE");
    run_case(8'h80, 8'h80, "0x80_vs_0x80");
    run_case(8'h7E, 8'h7F, "0x7E_vs_0x7F");
    run_case(8'h7F, 8'h7E, "0x7F_vs_0x7E");

    $display("TOTAL_TEST_CASES=%0d",        TOTAL_TEST_CASES);
    $display("TOTAL_PASSED_TEST_CASES=%0d", TOTAL_PASSED_TEST_CASES);
    $display("TOTAL_FAILED_TEST_CASES=%0d", TOTAL_FAILED_TEST_CASES);
    $display("ALL_TEST_CASES_PASSED=%s",    (TOTAL_FAILED_TEST_CASES==0) ? "true" : "false");

    $finish;
  end
endmodule