From ee2bf1cda81db6d11c0e06b9699430ff3d03c545 Mon Sep 17 00:00:00 2001 From: FallenSigh Date: Mon, 29 Jun 2026 22:15:39 +0800 Subject: [PATCH] test(top): two-instance hello_world TB (genenc + dec split) Add tb_mlkem_two_inst_xsim: runs the hello_world protocol across TWO mlkem_top instances, modeling the real two-party split: * u_genenc: KeyGen THEN Encaps on one instance. KeyGen writes ek into its own ek_bram and Encaps reuses it directly (no re-streaming) -> shared_key, ct. * u_dec: Decaps on a separate instance, receiving dk + ct streamed over from u_genenc via the input ports. Verifies A.shared_key == B.recovered_key and 'hello world' round-trips. Output matches the single-instance TB and the Rust reference (key=ced0c031a4bee34a...). run_hello.sh gains a 'two' arg to select this TB; default stays single-instance. --- run_hello.sh | 17 ++- sync_rtl/top/TB/tb_mlkem_two_inst_xsim.v | 169 +++++++++++++++++++++++ 2 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 sync_rtl/top/TB/tb_mlkem_two_inst_xsim.v diff --git a/run_hello.sh b/run_hello.sh index 2f4dfde..2c33899 100755 --- a/run_hello.sh +++ b/run_hello.sh @@ -1,16 +1,25 @@ #!/usr/bin/env bash -# run_hello.sh - compile + run the ML-KEM hello_world hardware testbench. +# run_hello.sh - compile + run an ML-KEM hello_world hardware testbench. +# ./run_hello.sh single-instance protocol (default) +# ./run_hello.sh two two-instance (genenc + dec) protocol set -e cd "$(dirname "$0")" source /opt/Xilinx/Vivado/2019.2/settings64.sh >/dev/null 2>&1 export LD_PRELOAD="/usr/lib64/libtinfo.so.5" rm -rf xsim.dir .Xil +if [ "$1" = "two" ]; then + TB=tb_mlkem_two_inst_xsim; SNAP=mlkem_two +else + TB=tb_mlkem_hello_world_xsim; SNAP=mlkem_hello +fi + # compile the RTL (every non-TB xvlog line from the shared tcl) grep -E '^xvlog ' sync_rtl/top/TB/xsim_run.tcl | grep -v 'TB/tb_' | while read -r cmd; do eval "$cmd" >/dev/null done -xvlog -sv --relax sync_rtl/top/TB/tb_mlkem_hello_world_xsim.v >/dev/null -xelab tb_mlkem_hello_world_xsim -s mlkem_hello --timescale 1ns/1ps >/dev/null 2>&1 -xsim mlkem_hello -R 2>/dev/null | grep -vE '^(#|INFO|Time resolution|run -all|exit|xsim|Vivado|====.*Simulator|SW Build|IP Build|Copyright|Tool Version|Start of|source |## )' | sed '/^$/N;/^\n$/D' +xvlog -sv --relax sync_rtl/top/TB/$TB.v >/dev/null +xelab $TB -s $SNAP --timescale 1ns/1ps >/dev/null 2>&1 +xsim $SNAP -R 2>/dev/null | grep -vE '^(#|INFO|Time resolution|run -all|exit|xsim|Vivado|====.*Simulator|SW Build|IP Build|Copyright|Tool Version|Start of|source |## )' | sed '/^$/N;/^\n$/D' + diff --git a/sync_rtl/top/TB/tb_mlkem_two_inst_xsim.v b/sync_rtl/top/TB/tb_mlkem_two_inst_xsim.v new file mode 100644 index 0000000..3f19a44 --- /dev/null +++ b/sync_rtl/top/TB/tb_mlkem_two_inst_xsim.v @@ -0,0 +1,169 @@ +// tb_mlkem_two_inst_xsim.v - ML-KEM hello_world on TWO mlkem_top instances. +// +// Models the real two-party split: +// * Instance A (u_genenc): Alice/Bob side that does KeyGen THEN Encaps. +// - KeyGen(d,z) writes ek into A's own ek_bram and produces dk. +// - Encaps(m) reuses that same ek_bram (no re-streaming) -> shared_key, ct. +// * Instance B (u_dec): Alice side that does Decaps. +// - receives dk (read out of A via dbg_dk) and ct (read out of A via +// dbg_ct), streamed into B's input ports -> recovered_key. +// +// Then XOR-encrypt/decrypt "hello world" and verify both keys + message match. +// Each step prints its inputs and outputs. +// +// xelab tb_mlkem_two_inst_xsim ; xsim +`timescale 1ns/1ps +module tb_mlkem_two_inst_xsim; + localparam KP = 2; // ML-KEM-512 + localparam EKB = 384*KP + 32; // 800 + localparam DKB = 768*KP + 96; // 1632 + localparam CTB = 32*(10*KP + 4); // 768 + localparam MLEN = 11; // "hello world" + + reg clk=0; + always #5 clk = ~clk; + + // ---------------- Instance A: KeyGen + Encaps ---------------- + reg a_rst_n=0, a_start=0; + reg [1:0] a_op=0; + reg [255:0] a_d=0, a_z=0, a_msg=0; + wire a_busy, a_done; + wire [255:0] a_ss; + reg [10:0] a_ct_idx=0; wire [7:0] a_ct_o; + reg a_byte_sel=0; reg [10:0] a_byte_idx=0; wire [7:0] a_byte_o; + reg [11:0] a_dk_idx=0; wire [7:0] a_dk_o; + wire [255:0] a_rho,a_sigma,a_r,a_hek,a_mp,a_kbar,a_dz,a_dh; + + mlkem_top u_genenc ( + .clk(clk), .rst_n(a_rst_n), .k_i(KP[2:0]), .op_i(a_op), + .d_i(a_d), .z_i(a_z), .msg_i(a_msg), .start_i(a_start), + .busy_o(a_busy), .done_o(a_done), + .ek_in_we(1'b0), .ek_in_addr(11'd0), .ek_in_byte(8'd0), + .dk_in_we(1'b0), .dk_in_addr(12'd0), .dk_in_byte(8'd0), + .c_in_we(1'b0), .c_in_addr(11'd0), .c_in_byte(8'd0), + .ss_o(a_ss), .dbg_ct_idx_i(a_ct_idx), .dbg_ct_o(a_ct_o), + .dbg_slot_i(6'd0), .dbg_idx_i(8'd0), .dbg_coeff_o(), + .dbg_byte_sel_i(a_byte_sel), .dbg_byte_idx_i(a_byte_idx), .dbg_byte_o(a_byte_o), + .dbg_dk_idx_i(a_dk_idx), .dbg_dk_o(a_dk_o), + .dbg_rho_o(a_rho), .dbg_sigma_o(a_sigma), .dbg_r_o(a_r), .dbg_hek_o(a_hek), + .dbg_mprime_o(a_mp), .dbg_kbar_o(a_kbar), .dbg_decz_o(a_dz), .dbg_dech_o(a_dh) + ); + + // ---------------- Instance B: Decaps ---------------- + reg b_rst_n=0, b_start=0; + wire b_busy, b_done; + wire [255:0] b_ss; + reg b_dk_we=0; reg [11:0] b_dk_addr=0; reg [7:0] b_dk_byte=0; + reg b_c_we=0; reg [10:0] b_c_addr=0; reg [7:0] b_c_byte=0; + wire [255:0] b_rho,b_sigma,b_r,b_hek,b_mp,b_kbar,b_dz,b_dh; + wire [7:0] b_byte_o, b_dk_o, b_ct_o; + + mlkem_top u_dec ( + .clk(clk), .rst_n(b_rst_n), .k_i(KP[2:0]), .op_i(2'd2), + .d_i(256'd0), .z_i(256'd0), .msg_i(256'd0), .start_i(b_start), + .busy_o(b_busy), .done_o(b_done), + .ek_in_we(1'b0), .ek_in_addr(11'd0), .ek_in_byte(8'd0), + .dk_in_we(b_dk_we), .dk_in_addr(b_dk_addr), .dk_in_byte(b_dk_byte), + .c_in_we(b_c_we), .c_in_addr(b_c_addr), .c_in_byte(b_c_byte), + .ss_o(b_ss), .dbg_ct_idx_i(11'd0), .dbg_ct_o(b_ct_o), + .dbg_slot_i(6'd0), .dbg_idx_i(8'd0), .dbg_coeff_o(), + .dbg_byte_sel_i(1'b0), .dbg_byte_idx_i(11'd0), .dbg_byte_o(b_byte_o), + .dbg_dk_idx_i(12'd0), .dbg_dk_o(b_dk_o), + .dbg_rho_o(b_rho), .dbg_sigma_o(b_sigma), .dbg_r_o(b_r), .dbg_hek_o(b_hek), + .dbg_mprime_o(b_mp), .dbg_kbar_o(b_kbar), .dbg_decz_o(b_dz), .dbg_dech_o(b_dh) + ); + + // shuttle storage (the wire between the two instances) + reg [7:0] dk_b [0:DKB-1]; + reg [7:0] ct_b [0:CTB-1]; + reg [7:0] msg_b [0:MLEN-1]; + reg [7:0] enc_b [0:MLEN-1]; + reg [7:0] dec_b [0:MLEN-1]; + reg [255:0] shared_key, recovered_key; + integer c, i, j, errors; + + task wait_a; begin + c=0; while(!a_done && c<4000000) begin @(posedge clk); c=c+1; end + if(!a_done) begin $display("FAIL: instance A timeout (op=%0d)", a_op); $finish; end + end endtask + task wait_b; begin + c=0; while(!b_done && c<4000000) begin @(posedge clk); c=c+1; end + if(!b_done) begin $display("FAIL: instance B timeout"); $finish; end + end endtask + + task print_hex32; input [255:0] v; begin + for (j=0;j<32;j=j+1) $write("%02x", v[8*j +: 8]); $write("\n"); + end endtask + + initial begin + errors = 0; + msg_b[0]="h"; msg_b[1]="e"; msg_b[2]="l"; msg_b[3]="l"; msg_b[4]="o"; + msg_b[5]=" "; msg_b[6]="w"; msg_b[7]="o"; msg_b[8]="r"; msg_b[9]="l"; msg_b[10]="d"; + + $display("=== ML-KEM hello_world (TWO instances: genenc + dec, ML-KEM-512) ==="); + $write("Original: \""); for(i=0;i \""); for(i=0;i