480 lines
16 KiB
Systemverilog
480 lines
16 KiB
Systemverilog
|
|
`timescale 1ns/1ps
|
||
|
|
|
||
|
|
module TB;
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// Parameters
|
||
|
|
// ====================================================
|
||
|
|
parameter FIFO_DEPTH = 64;
|
||
|
|
parameter SCRAMBLER_SEED = 32'hFFFFFFFF;
|
||
|
|
parameter CLK_PERIOD = 10; // 10ns
|
||
|
|
parameter PATN_COUNT = 20'd10; // Training match count
|
||
|
|
parameter TAP_STEP = 3'd3; // Delay adjustment step
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// Constants (moved to top for visibility)
|
||
|
|
// ====================================================
|
||
|
|
localparam [31:0] TRAINING_PATN = 32'h68666E6C; // "hfnl"
|
||
|
|
localparam [31:0] TRAINING_EXIT = 32'h65786974; // "exit"
|
||
|
|
localparam [31:0] FRAME_HEADER = 32'hBCBCBCBC;
|
||
|
|
|
||
|
|
initial begin
|
||
|
|
$fsdbAutoSwitchDumpfile(500, "./verdplus.fsdb", 1000000);
|
||
|
|
$fsdbDumpvars();
|
||
|
|
$fsdbDumpMDA();
|
||
|
|
end
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// Signals
|
||
|
|
// ====================================================
|
||
|
|
logic clk;
|
||
|
|
logic rst_n;
|
||
|
|
logic [3:0] serial_in;
|
||
|
|
logic [19:0] patn_count;
|
||
|
|
logic [2:0] tap_step;
|
||
|
|
logic link_down;
|
||
|
|
logic [2:0] delay_tap;
|
||
|
|
logic [12:0] wr_addr;
|
||
|
|
logic [511:0] wr_data;
|
||
|
|
logic wr_en;
|
||
|
|
logic [63:0] byte_mask;
|
||
|
|
logic crc_error;
|
||
|
|
logic [2 :0] tap_adj_mask = 3'b111; // Delay adjustment mask
|
||
|
|
logic [0 :0] tap_force = 1'b0 ; // Delay force to tap_step
|
||
|
|
logic tap_adj_req ;
|
||
|
|
logic frame_done ;
|
||
|
|
logic [31 :0] train_status ;
|
||
|
|
logic [31 :0] frame_status ;
|
||
|
|
logic always_on = 1'b0 ;
|
||
|
|
logic prefilling ;
|
||
|
|
logic prefill_start;
|
||
|
|
|
||
|
|
// Added for scrambler test
|
||
|
|
logic descram_en;
|
||
|
|
logic [31:0] lfsr_lane[0:3]; // 4 independent LFSRs for scrambling
|
||
|
|
logic train_ready;
|
||
|
|
logic force_train; // Force Training
|
||
|
|
// ====================================================
|
||
|
|
// DUT Instance
|
||
|
|
// ====================================================
|
||
|
|
ulink_rx #(
|
||
|
|
.FIFO_DEPTH ( FIFO_DEPTH )
|
||
|
|
,.SCRAMBLER_SEED ( SCRAMBLER_SEED )
|
||
|
|
) dut (
|
||
|
|
.clk ( clk )
|
||
|
|
,.rst_n ( rst_n )
|
||
|
|
,.serial_in ( serial_in )
|
||
|
|
,.patn_count ( patn_count )
|
||
|
|
,.tap_step ( tap_step )
|
||
|
|
,.descram_en ( descram_en ) // now variable
|
||
|
|
,.link_down ( link_down )
|
||
|
|
,.delay_tap ( delay_tap )
|
||
|
|
,.wr_addr ( wr_addr )
|
||
|
|
,.wr_data ( wr_data )
|
||
|
|
,.wr_en ( wr_en )
|
||
|
|
,.byte_mask ( byte_mask )
|
||
|
|
,.crc_error ( crc_error )
|
||
|
|
,.tap_adj_mask ( tap_adj_mask )
|
||
|
|
,.tap_force ( tap_force )
|
||
|
|
,.tap_adj_req ( tap_adj_req )
|
||
|
|
,.frame_done ( frame_done )
|
||
|
|
,.train_status ( train_status )
|
||
|
|
,.frame_status ( frame_status )
|
||
|
|
,.always_on ( always_on )
|
||
|
|
,.prefilling ( prefilling )
|
||
|
|
,.prefill_start ( prefill_start )
|
||
|
|
,.train_ready ( train_ready )
|
||
|
|
,.force_train ( force_train )
|
||
|
|
);
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// Clock Generation
|
||
|
|
// ====================================================
|
||
|
|
initial begin
|
||
|
|
clk = 0;
|
||
|
|
forever #(CLK_PERIOD/2) clk = ~clk;
|
||
|
|
end
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// Tasks for Stimulus Generation
|
||
|
|
// ====================================================
|
||
|
|
|
||
|
|
// Reset task
|
||
|
|
task reset;
|
||
|
|
begin
|
||
|
|
rst_n = 0;
|
||
|
|
repeat(5) @(posedge clk);
|
||
|
|
rst_n = 1;
|
||
|
|
repeat(2) @(posedge clk);
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
// Send one 128-bit block over 32 cycles
|
||
|
|
task send_block;
|
||
|
|
input [127:0] block;
|
||
|
|
begin
|
||
|
|
integer i;
|
||
|
|
for (i = 31; i >= 0; i = i - 1) begin
|
||
|
|
serial_in[0] = block[i];
|
||
|
|
serial_in[1] = block[32 + i];
|
||
|
|
serial_in[2] = block[64 + i];
|
||
|
|
serial_in[3] = block[96 + i];
|
||
|
|
@(posedge clk);
|
||
|
|
#0.1;
|
||
|
|
end
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
// Send multiple identical blocks
|
||
|
|
task send_blocks;
|
||
|
|
input [127:0] block;
|
||
|
|
input integer count;
|
||
|
|
begin
|
||
|
|
repeat(count) send_block(block);
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
// Send a sequence of 32-bit words (packed into 128-bit blocks)
|
||
|
|
task send_words;
|
||
|
|
input integer num_words;
|
||
|
|
input [31:0] words[0:255];
|
||
|
|
begin
|
||
|
|
integer word_idx;
|
||
|
|
integer j;
|
||
|
|
reg [127:0] block;
|
||
|
|
word_idx = 0;
|
||
|
|
while (word_idx < num_words) begin
|
||
|
|
block = 128'h0;
|
||
|
|
for (j = 0; j < 4; j = j + 1) begin
|
||
|
|
if (word_idx < num_words) begin
|
||
|
|
block[32*j +: 32] = words[word_idx];
|
||
|
|
word_idx = word_idx + 1;
|
||
|
|
end
|
||
|
|
end
|
||
|
|
send_block(block);
|
||
|
|
end
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// Scrambler tasks (used when descram_en=1)
|
||
|
|
// ====================================================
|
||
|
|
|
||
|
|
// Original scrambler (all lanes scrambled)
|
||
|
|
task scramble_block;
|
||
|
|
input [127:0] original;
|
||
|
|
output [127:0] scrambled;
|
||
|
|
integer i;
|
||
|
|
reg [31:0] lane_word;
|
||
|
|
begin
|
||
|
|
scrambled = 128'h0;
|
||
|
|
for (i = 0; i < 4; i = i + 1) begin
|
||
|
|
lane_word = original[32*i +: 32];
|
||
|
|
scrambled[32*i +: 32] = lane_word ^ lfsr_lane[i];
|
||
|
|
// Update LFSR: feedback = lfsr[31]^lfsr[30]^lfsr[29]^lfsr[28]
|
||
|
|
lfsr_lane[i] = {lfsr_lane[i][30:0],
|
||
|
|
lfsr_lane[i][31] ^ lfsr_lane[i][30] ^
|
||
|
|
lfsr_lane[i][29] ^ lfsr_lane[i][28]};
|
||
|
|
end
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
// New scrambler with per-lane mask (1=scramble that lane)
|
||
|
|
task scramble_block_masked;
|
||
|
|
input [127:0] original;
|
||
|
|
input [3:0] mask; // 1=scramble this lane
|
||
|
|
output [127:0] scrambled;
|
||
|
|
integer i;
|
||
|
|
reg [31:0] lane_word;
|
||
|
|
begin
|
||
|
|
scrambled = 128'h0;
|
||
|
|
for (i = 0; i < 4; i = i + 1) begin
|
||
|
|
lane_word = original[32*i +: 32];
|
||
|
|
if (mask[i]) begin
|
||
|
|
scrambled[32*i +: 32] = lane_word ^ lfsr_lane[i];
|
||
|
|
// Update LFSR only for scrambled lanes
|
||
|
|
lfsr_lane[i] = {lfsr_lane[i][30:0],
|
||
|
|
lfsr_lane[i][31] ^ lfsr_lane[i][30] ^
|
||
|
|
lfsr_lane[i][29] ^ lfsr_lane[i][28]};
|
||
|
|
end else begin
|
||
|
|
scrambled[32*i +: 32] = lane_word;
|
||
|
|
// No LFSR update for unscrambled lanes
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// CRC32 Computation Functions
|
||
|
|
// ====================================================
|
||
|
|
function automatic [31:0] crc32_next;
|
||
|
|
input [31:0] crc;
|
||
|
|
input [31:0] data;
|
||
|
|
reg [31:0] new_crc;
|
||
|
|
reg b;
|
||
|
|
integer i;
|
||
|
|
begin
|
||
|
|
new_crc = crc;
|
||
|
|
for (i = 0; i < 32; i = i + 1) begin
|
||
|
|
b = new_crc[31] ^ data[31-i];
|
||
|
|
new_crc = {new_crc[30:0], 1'b0};
|
||
|
|
if (b) new_crc = new_crc ^ 32'h04C11DB7;
|
||
|
|
end
|
||
|
|
crc32_next = new_crc;
|
||
|
|
end
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
function automatic [31:0] crc32_compute;
|
||
|
|
input [31:0] words[0:255];
|
||
|
|
input integer num;
|
||
|
|
reg [31:0] crc;
|
||
|
|
integer i;
|
||
|
|
begin
|
||
|
|
crc = 32'hFFFFFFFF;
|
||
|
|
for (i = 0; i < num; i = i + 1) begin
|
||
|
|
crc = crc32_next(crc, words[i]);
|
||
|
|
end
|
||
|
|
crc32_compute = crc;
|
||
|
|
end
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// Test Variables (module level)
|
||
|
|
// ====================================================
|
||
|
|
integer data_words;
|
||
|
|
reg [31:0] frame_words[0:255];
|
||
|
|
integer frame_len;
|
||
|
|
integer i;
|
||
|
|
// ====================================================
|
||
|
|
// Test Sequence
|
||
|
|
// ====================================================
|
||
|
|
initial begin
|
||
|
|
// Initialize
|
||
|
|
serial_in = 4'h0;
|
||
|
|
patn_count = PATN_COUNT;
|
||
|
|
tap_step = TAP_STEP;
|
||
|
|
descram_en = 0; // default disabled
|
||
|
|
force_train = 0;
|
||
|
|
|
||
|
|
$display("========================================");
|
||
|
|
$display("Testbench started at %0t", $time);
|
||
|
|
$display("========================================");
|
||
|
|
|
||
|
|
reset();
|
||
|
|
wait(prefill_start == 1);
|
||
|
|
repeat(10) @(posedge clk);
|
||
|
|
prefilling = 1;
|
||
|
|
repeat(100) @(posedge clk);
|
||
|
|
prefilling = 0;
|
||
|
|
// -------------------------------------------------
|
||
|
|
// Phase 1: Link Training (successful)
|
||
|
|
// -------------------------------------------------
|
||
|
|
$display("Phase 1: Training with correct patterns...");
|
||
|
|
|
||
|
|
send_blocks({4{TRAINING_PATN}}, PATN_COUNT - 0);
|
||
|
|
send_blocks({4{TRAINING_EXIT}}, 1);
|
||
|
|
|
||
|
|
wait(dut.u_train.train_ready == 1);
|
||
|
|
$display("Link ready at %0t", $time);
|
||
|
|
|
||
|
|
// -------------------------------------------------
|
||
|
|
// Phase 2: Send a correct frame
|
||
|
|
// -------------------------------------------------
|
||
|
|
$display("Phase 2: Sending a correct frame...");
|
||
|
|
|
||
|
|
data_words = 20;
|
||
|
|
frame_len = 2 + data_words + 1;
|
||
|
|
|
||
|
|
frame_words[0] = FRAME_HEADER;
|
||
|
|
frame_words[1] = (32'h1235 << 16) | data_words;
|
||
|
|
|
||
|
|
for (i = 0; i < data_words; i = i + 1) begin
|
||
|
|
frame_words[2+i] = 32'hA0000000 + i;
|
||
|
|
end
|
||
|
|
|
||
|
|
// Compute CRC over addr_len and data (1 + data_words words)
|
||
|
|
begin
|
||
|
|
reg [31:0] crc_tmp[0:255];
|
||
|
|
integer k;
|
||
|
|
for (k = 0; k < 1 + data_words; k = k + 1) begin
|
||
|
|
crc_tmp[k] = frame_words[1 + k];
|
||
|
|
end
|
||
|
|
frame_words[2 + data_words] = crc32_compute(crc_tmp, 1 + data_words);
|
||
|
|
end
|
||
|
|
|
||
|
|
send_words(frame_len, frame_words);
|
||
|
|
|
||
|
|
fork
|
||
|
|
begin
|
||
|
|
@(posedge wr_en);
|
||
|
|
$display("Write detected: addr=%0d data=%h mask=%h", wr_addr, wr_data, byte_mask);
|
||
|
|
if (wr_addr !== 13'h123) $error("Unexpected write address: %0d", wr_addr);
|
||
|
|
if (byte_mask[31:0] !== 32'hFFFF_0000) $error("Byte mask mismatch: %h", byte_mask);
|
||
|
|
if (byte_mask[63:32] !== 32'hFFFF_FFFF) $error("Byte mask high part not zero: %h", byte_mask[63:32]);
|
||
|
|
$display("Correct frame write verified.");
|
||
|
|
end
|
||
|
|
join_none
|
||
|
|
|
||
|
|
repeat(100) @(posedge clk);
|
||
|
|
|
||
|
|
// -------------------------------------------------
|
||
|
|
// Phase 3: Send a frame with bad CRC
|
||
|
|
// -------------------------------------------------
|
||
|
|
$display("Phase 3: Sending a frame with bad CRC...");
|
||
|
|
|
||
|
|
frame_words[2+data_words] = ~frame_words[2+data_words];
|
||
|
|
send_words(frame_len, frame_words);
|
||
|
|
|
||
|
|
@(posedge crc_error);
|
||
|
|
$display("CRC error detected at %0t", $time);
|
||
|
|
repeat(10) @(posedge clk);
|
||
|
|
if (link_down) $display("Link down as expected.");
|
||
|
|
else $error("Link not down after CRC error.");
|
||
|
|
|
||
|
|
// -------------------------------------------------
|
||
|
|
// Phase 4: Re-train and send another correct frame
|
||
|
|
// -------------------------------------------------
|
||
|
|
$display("Phase 4: Re-training...");
|
||
|
|
|
||
|
|
send_blocks({4{TRAINING_PATN}}, PATN_COUNT - 0);
|
||
|
|
send_blocks({4{TRAINING_EXIT}}, 1);
|
||
|
|
|
||
|
|
wait(dut.u_train.train_ready == 1);
|
||
|
|
$display("Link ready again.");
|
||
|
|
|
||
|
|
// Compute CRC over addr_len and data (1 + data_words words)
|
||
|
|
begin
|
||
|
|
reg [31:0] crc_tmp[0:255];
|
||
|
|
integer k;
|
||
|
|
for (k = 0; k < 1 + data_words; k = k + 1) begin
|
||
|
|
crc_tmp[k] = frame_words[1 + k];
|
||
|
|
end
|
||
|
|
frame_words[2 + data_words] = crc32_compute(crc_tmp, 1 + data_words);
|
||
|
|
end
|
||
|
|
send_words(frame_len, frame_words);
|
||
|
|
|
||
|
|
repeat(100) @(posedge clk);
|
||
|
|
|
||
|
|
// -------------------------------------------------
|
||
|
|
// Phase 5: Test delay_tap adjustment on match failure
|
||
|
|
// -------------------------------------------------
|
||
|
|
$display("Phase 5: Testing delay_tap adjustment...");
|
||
|
|
|
||
|
|
force_train = 1;
|
||
|
|
repeat(1) @(posedge clk);
|
||
|
|
force_train = 0;
|
||
|
|
send_blocks(128'h0, 5);
|
||
|
|
repeat(200) @(posedge clk);
|
||
|
|
$display("Final delay_tap = %0d", delay_tap);
|
||
|
|
|
||
|
|
// -------------------------------------------------
|
||
|
|
// Phase 6: Test with scrambler enabled (header not scrambled)
|
||
|
|
// -------------------------------------------------
|
||
|
|
$display("Phase 6: Testing with descrambler enabled (header not scrambled)...");
|
||
|
|
|
||
|
|
// Reset and train with descram_en=0 (training patterns are not scrambled)
|
||
|
|
descram_en = 0;
|
||
|
|
force_train = 1;
|
||
|
|
repeat(1) @(posedge clk);
|
||
|
|
force_train = 0;
|
||
|
|
send_blocks({4{TRAINING_PATN}}, PATN_COUNT - 0);
|
||
|
|
send_blocks({4{TRAINING_EXIT}}, 1);
|
||
|
|
|
||
|
|
wait(dut.u_train.train_ready == 1);
|
||
|
|
$display("Link ready for scrambled data.");
|
||
|
|
|
||
|
|
// Enable descrambler in DUT and initialize LFSRs for transmission
|
||
|
|
descram_en = 1;
|
||
|
|
for (i = 0; i < 4; i = i + 1) lfsr_lane[i] = SCRAMBLER_SEED;
|
||
|
|
|
||
|
|
// Construct a simple frame (small data length)
|
||
|
|
data_words = 10;
|
||
|
|
frame_len = 2 + data_words + 1;
|
||
|
|
|
||
|
|
frame_words[0] = FRAME_HEADER;
|
||
|
|
frame_words[1] = (32'h1234 << 16) | data_words; // base_addr=0x1234, offset=0, len=10
|
||
|
|
|
||
|
|
for (i = 0; i < data_words; i = i + 1) begin
|
||
|
|
frame_words[2+i] = 32'hB0000000 + i; // different pattern from Phase 2
|
||
|
|
end
|
||
|
|
|
||
|
|
// Compute CRC over addr_len and data (1 + data_words words)
|
||
|
|
begin
|
||
|
|
reg [31:0] crc_tmp[0:255];
|
||
|
|
integer k;
|
||
|
|
for (k = 0; k < 1 + data_words; k = k + 1) begin
|
||
|
|
crc_tmp[k] = frame_words[1 + k];
|
||
|
|
end
|
||
|
|
frame_words[2 + data_words] = crc32_compute(crc_tmp, 1 + data_words);
|
||
|
|
end
|
||
|
|
|
||
|
|
// Now scramble each 128-bit block and send
|
||
|
|
begin
|
||
|
|
integer word_idx;
|
||
|
|
integer j;
|
||
|
|
integer block_num;
|
||
|
|
reg [127:0] orig_block, scrambled_block;
|
||
|
|
reg [3:0] block_mask;
|
||
|
|
word_idx = 0;
|
||
|
|
block_num = 0;
|
||
|
|
while (word_idx < frame_len) begin
|
||
|
|
orig_block = 128'h0;
|
||
|
|
// Fill block with up to 4 words, pad with 0 if needed
|
||
|
|
for (j = 0; j < 4; j = j + 1) begin
|
||
|
|
if (word_idx < frame_len) begin
|
||
|
|
orig_block[32*j +: 32] = frame_words[word_idx];
|
||
|
|
word_idx = word_idx + 1;
|
||
|
|
end else begin
|
||
|
|
orig_block[32*j +: 32] = 32'h0; // pad with 0
|
||
|
|
end
|
||
|
|
end
|
||
|
|
// Determine mask: first block leaves lane0 unscrambled (header), others all scrambled
|
||
|
|
block_mask = (block_num == 0) ? 4'b1110 : 4'b1111;
|
||
|
|
scramble_block_masked(orig_block, block_mask, scrambled_block);
|
||
|
|
send_block(scrambled_block);
|
||
|
|
block_num = block_num + 1;
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
// Monitor writes
|
||
|
|
fork
|
||
|
|
begin
|
||
|
|
@(posedge wr_en);
|
||
|
|
$display("Scrambled test write: addr=%0d data=%h mask=%h", wr_addr, wr_data, byte_mask);
|
||
|
|
end
|
||
|
|
join_none
|
||
|
|
|
||
|
|
repeat(200) @(posedge clk);
|
||
|
|
|
||
|
|
// Check that no CRC error happened
|
||
|
|
if (crc_error) $error("CRC error in scrambled test");
|
||
|
|
else $display("Scrambled test passed (no CRC error).");
|
||
|
|
|
||
|
|
// Disable scrambler for rest of test
|
||
|
|
descram_en = 0;
|
||
|
|
|
||
|
|
// -------------------------------------------------
|
||
|
|
// End of test
|
||
|
|
// -------------------------------------------------
|
||
|
|
repeat(50) @(posedge clk);
|
||
|
|
$display("========================================");
|
||
|
|
$display("Testbench finished at %0t", $time);
|
||
|
|
$display("========================================");
|
||
|
|
$finish;
|
||
|
|
end
|
||
|
|
|
||
|
|
// ====================================================
|
||
|
|
// Monitor and Assertions
|
||
|
|
// ====================================================
|
||
|
|
always @(posedge clk) begin
|
||
|
|
if (wr_en) $display("WRITE: addr=%0d data=%h mask=%h", wr_addr, wr_data, byte_mask);
|
||
|
|
if (crc_error) $display("CRC_ERROR pulse at %0t", $time);
|
||
|
|
end
|
||
|
|
|
||
|
|
always @(posedge clk) begin
|
||
|
|
if (dut.u_train.train_ready && link_down)
|
||
|
|
$error("Inconsistent state: train_ready and link_down both high");
|
||
|
|
end
|
||
|
|
|
||
|
|
endmodule
|