aboutsummaryrefslogblamecommitdiff
path: root/bench/tb_core_full_512.v
blob: e9b9f3002aa1db1307d764be349abc7d5b45a225 (plain) (tree)
1
2
3
4

                                                                        

                                                            










                                                                         


                                                                         














                                                                           
































                                                                        
                                     

















































                                                                                                                               



                                                                                                   


















                                                                                                               




             



                                                                
 










                                                                       
 












                                                                                                   
    


                                                          





                   
                      
















                                  








                                                
    








                                      
                                    




















                                                 
                       
      







                                                  
    
    



                 


      
                                         
      
                    

              
 
        


                                                                                      
        

                                                                
                    







                                                                                                                

                                      







                                                                                                                

                                      

           
    







                                             
    








                                                             
    

























                                                                  







                   
                       
      

                          

                                                                                                               






                                                                                                                                            




           
                       
      
                          
             
                                                                                                                        








                                                                                                                                    




           
                      
      
                         
             




                                                                                                         




           
                         
      
                            
























































                                                                                                        






















                                                                        
                              














                                                                    
                              






                                                          
                               
      

                                

           
    
      
                            
      
                             
             

                                   




           
                      
      











                                




                            
                       
          
                        

                                         
                          

           
 
      
                           
          
                            

                                         
                              
           

 


                                 




                                   


                         
















                                                                                                                            
 
         
//======================================================================
//
// Copyright: 2019, The Commons Conservancy Cryptech Project
// SPDX-License-Identifier: BSD-3-Clause
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// - Redistributions of source code must retain the above copyright
//   notice, this list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the
//   documentation and/or other materials provided with the distribution.
//
// - Neither the name of the copyright holder nor the names of its
//   contributors may be used to endorse or promote products derived from
//   this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//======================================================================

`timescale 1ns / 1ps

module tb_core_full_512;


    //
    // Headers
    //
    `include "../rtl/modexpng_parameters.vh"


    //
    // Test Vectors
    //
    localparam TB_MODULUS_LENGTH_N  = 512;
    localparam TB_MODULUS_LENGTH_PQ = TB_MODULUS_LENGTH_N  / 2;
    localparam TB_NUM_WORDS_PQ      = TB_MODULUS_LENGTH_PQ / BUS_DATA_W;
    localparam TB_NUM_WORDS_N       = TB_MODULUS_LENGTH_N  / BUS_DATA_W;
    localparam CORE_NUM_WORDS_PQ    = TB_MODULUS_LENGTH_PQ / WORD_W;
    localparam CORE_NUM_WORDS_N     = TB_MODULUS_LENGTH_N  / WORD_W;

    reg [31:0] M[0:TB_NUM_WORDS_N-1];
    reg [31:0] N[0:TB_NUM_WORDS_N-1];
    reg [31:0] N_FACTOR[0:TB_NUM_WORDS_N-1];
    reg [31:0] N_COEFF[0:TB_NUM_WORDS_N];
    reg [31:0] X[0:TB_NUM_WORDS_N-1];
    reg [31:0] Y[0:TB_NUM_WORDS_N-1];
    reg [31:0] P[0:TB_NUM_WORDS_PQ-1];
    reg [31:0] Q[0:TB_NUM_WORDS_PQ-1];
    reg [31:0] P_FACTOR[0:TB_NUM_WORDS_PQ-1];
    reg [31:0] Q_FACTOR[0:TB_NUM_WORDS_PQ-1];
    reg [31:0] P_COEFF[0:TB_NUM_WORDS_PQ];
    reg [31:0] Q_COEFF[0:TB_NUM_WORDS_PQ];
    reg [31:0] D[0:TB_NUM_WORDS_N-1];
    reg [31:0] DP[0:TB_NUM_WORDS_PQ-1];
    reg [31:0] DQ[0:TB_NUM_WORDS_PQ-1];
    reg [31:0] QINV[0:TB_NUM_WORDS_PQ-1];
    reg [31:0] XM[0:TB_NUM_WORDS_N-1];
    reg [31:0] YM[0:TB_NUM_WORDS_N-1];
    reg [31:0] S[0:TB_NUM_WORDS_N-1];
    reg [31:0] XM_READBACK[0:TB_NUM_WORDS_N-1];
    reg [31:0] YM_READBACK[0:TB_NUM_WORDS_N-1];
    reg [31:0] S_READBACK[0:TB_NUM_WORDS_N-1];
    
    initial begin
        M[  0] = 32'h8d3b583b; M[  1] = 32'hc370f07e; M[  2] = 32'hb9078738; M[  3] = 32'haf37f86c;
        M[  4] = 32'h02f0e161; M[  5] = 32'h0506a68a; M[  6] = 32'h1ae65107; M[  7] = 32'hcd3a97f1;
        M[  8] = 32'hb27244b8; M[  9] = 32'h9bc3c400; M[ 10] = 32'he4d5636e; M[ 11] = 32'h35187c07;
        M[ 12] = 32'h78a661c9; M[ 13] = 32'h1e7ec273; M[ 14] = 32'hcdc31041; M[ 15] = 32'h002291d8;
        N[  0] = 32'hcb703101; N[  1] = 32'h82bc8290; N[  2] = 32'hdb2372c2; N[  3] = 32'hdeeb692e;
        N[  4] = 32'ha3ee352a; N[  5] = 32'h81a711ba; N[  6] = 32'h14ee23bd; N[  7] = 32'h8ad351c0;
        N[  8] = 32'h75ecd3d5; N[  9] = 32'h51c9b22f; N[ 10] = 32'hc1d3496e; N[ 11] = 32'h48176f3e;
        N[ 12] = 32'hd2aca749; N[ 13] = 32'hf236cea9; N[ 14] = 32'h7f4525ed; N[ 15] = 32'hb4fc5067;        
        N_FACTOR[  0] = 32'he253bfbf; N_FACTOR[  1] = 32'h8e0b26aa; N_FACTOR[  2] = 32'h0480b661; N_FACTOR[  3] = 32'h9a13f7a1;
        N_FACTOR[  4] = 32'h464b7342; N_FACTOR[  5] = 32'hfb6f8e41; N_FACTOR[  6] = 32'h081208e4; N_FACTOR[  7] = 32'h63d8328a;
        N_FACTOR[  8] = 32'h604d2b71; N_FACTOR[  9] = 32'hc987dabe; N_FACTOR[ 10] = 32'h8a474e35; N_FACTOR[ 11] = 32'hc053ba1c;
        N_FACTOR[ 12] = 32'h15b82dd9; N_FACTOR[ 13] = 32'h42c2bbfa; N_FACTOR[ 14] = 32'h1681e95d; N_FACTOR[ 15] = 32'h07dee5fa;
        N_COEFF[  0] = 32'h730f30ff; N_COEFF[  1] = 32'h50ed900a; N_COEFF[  2] = 32'h0b9038c5; N_COEFF[  3] = 32'h974ddd03;
        N_COEFF[  4] = 32'he2c118c8; N_COEFF[  5] = 32'hbe1bc7e1; N_COEFF[  6] = 32'h224d548c; N_COEFF[  7] = 32'h48ea2ee4;
        N_COEFF[  8] = 32'heb379247; N_COEFF[  9] = 32'had97b934; N_COEFF[ 10] = 32'hfc6dfd93; N_COEFF[ 11] = 32'h3a0246ef;
        N_COEFF[ 12] = 32'h1baa167c; N_COEFF[ 13] = 32'h7d7ee254; N_COEFF[ 14] = 32'h657f0a53; N_COEFF[ 15] = 32'hea9e7245;
        N_COEFF[ 16] = 32'h0000f88c;        
        X[  0] = 32'h2d532c22; X[  1] = 32'h2d3c3b06; X[  2] = 32'he2862a8f; X[  3] = 32'he8616ce4;
        X[  4] = 32'h5d77ee51; X[  5] = 32'he609de07; X[  6] = 32'hef718044; X[  7] = 32'h82f35f8b;
        X[  8] = 32'hcdb9dcfe; X[  9] = 32'hff6ea364; X[ 10] = 32'h0994ae28; X[ 11] = 32'h409b369b;
        X[ 12] = 32'hcfabda4e; X[ 13] = 32'h5cd52bbc; X[ 14] = 32'hd90e1715; X[ 15] = 32'h00f4dcf2;
        Y[  0] = 32'h34fff653; Y[  1] = 32'h50f52544; Y[  2] = 32'h0ebf96a7; Y[  3] = 32'h98352265;
        Y[  4] = 32'hbe372927; Y[  5] = 32'h5b2f6394; Y[  6] = 32'h9acfccb3; Y[  7] = 32'h7b5bd4b2;
        Y[  8] = 32'h79b09448; Y[  9] = 32'h08f11fa6; Y[ 10] = 32'h8411d066; Y[ 11] = 32'h58ba5021;
        Y[ 12] = 32'h03c1cb72; Y[ 13] = 32'hacf0689d; Y[ 14] = 32'h983c65bd; Y[ 15] = 32'h29a39dcc;
        P[  0] = 32'hebfc2433; P[  1] = 32'ha2cfbc81; P[  2] = 32'hea08812b; P[  3] = 32'h0adf004f;
        P[  4] = 32'hb987a8c6; P[  5] = 32'h2860f873; P[  6] = 32'haf2cfe12; P[  7] = 32'hddd53c3a;
        Q[  0] = 32'hdc1981fb; Q[  1] = 32'h01184053; Q[  2] = 32'h7ab8d640; Q[  3] = 32'h62ba8a22;
        Q[  4] = 32'h6cb226a1; Q[  5] = 32'he1f08e16; Q[  6] = 32'h13e990b5; Q[  7] = 32'hd0dc7ce3;        
        P_FACTOR[  0] = 32'h043fb284; P_FACTOR[  1] = 32'hcaab7ce3; P_FACTOR[  2] = 32'h543c62ef; P_FACTOR[  3] = 32'h8aa74942;
        P_FACTOR[  4] = 32'hefa2ea7b; P_FACTOR[  5] = 32'hdb8513b5; P_FACTOR[  6] = 32'h0ea607a4; P_FACTOR[  7] = 32'h6a59e5a7;
        Q_FACTOR[  0] = 32'h089dcd43; Q_FACTOR[  1] = 32'h5b23611b; Q_FACTOR[  2] = 32'h02f0f47c; Q_FACTOR[  3] = 32'h952ababd;
        Q_FACTOR[  4] = 32'hc4ee13fe; Q_FACTOR[  5] = 32'h3feb46fa; Q_FACTOR[  6] = 32'h96b679df; Q_FACTOR[  7] = 32'h831126dd;
        P_COEFF[  0] = 32'h647c8905; P_COEFF[  1] = 32'hcb7c6b7d; P_COEFF[  2] = 32'h8053b8be; P_COEFF[  3] = 32'hb28f33a7;
        P_COEFF[  4] = 32'hb3207e05; P_COEFF[  5] = 32'h4e3d416e; P_COEFF[  6] = 32'h1911d8d9; P_COEFF[  7] = 32'hd569156e;
        P_COEFF[  8] = 32'h00003dd7;
        Q_COEFF[  0] = 32'h5eee9ecd; Q_COEFF[  1] = 32'h085153b0; Q_COEFF[  2] = 32'h85326da6; Q_COEFF[  3] = 32'h7521931a;
        Q_COEFF[  4] = 32'h99e0eef1; Q_COEFF[  5] = 32'ha219917b; Q_COEFF[  6] = 32'he8e9087a; Q_COEFF[  7] = 32'h5239d12b;
        Q_COEFF[  8] = 32'h0000ed92;
        D[  0] = 32'hf127ca41; D[  1] = 32'hc4975ff0; D[  2] = 32'h69ebbe13; D[  3] = 32'h66fe0018;
        D[  4] = 32'hf2089237; D[  5] = 32'hfa3f05ab; D[  6] = 32'h2ab183c4; D[  7] = 32'h1e4b3c04;
        D[  8] = 32'ha67974e8; D[  9] = 32'ha6714d63; D[ 10] = 32'hfe5cd801; D[ 11] = 32'h13f2071a;
        D[ 12] = 32'h0b978309; D[ 13] = 32'hb0ddb4a0; D[ 14] = 32'ha437a2cc; D[ 15] = 32'h2391b2fb;
        DP[  0] = 32'h3891ed91; DP[  1] = 32'h775046c2; DP[  2] = 32'h60180c26; DP[  3] = 32'h5130700a;
        DP[  4] = 32'hb13c8216; DP[  5] = 32'h833fcf78; DP[  6] = 32'h7ab89b12; DP[  7] = 32'hb976758c;
        DQ[  0] = 32'h28cc59ad; DQ[  1] = 32'h3ce6ed45; DQ[  2] = 32'ha1f53aeb; DQ[  3] = 32'h06ca05e1;
        DQ[  4] = 32'hc5195df6; DQ[  5] = 32'h42cf91f8; DQ[  6] = 32'h93d6f054; DQ[  7] = 32'h3d3bc769;
        QINV[  0] = 32'h50201af6; QINV[  1] = 32'h85d97b7f; QINV[  2] = 32'h4247e697; QINV[  3] = 32'h9fd231fe;
        QINV[  4] = 32'h21e98610; QINV[  5] = 32'ha0bc58dc; QINV[  6] = 32'ha86f266c; QINV[  7] = 32'h838688c8;
        XM[  0] = 32'hf9980f33; XM[  1] = 32'hb444f483; XM[  2] = 32'h0a6f8294; XM[  3] = 32'h1c74da49;
        XM[  4] = 32'h0aa4151f; XM[  5] = 32'ha1dfb66f; XM[  6] = 32'h1415da79; XM[  7] = 32'had7d3272;
        XM[  8] = 32'h43d7b612; XM[  9] = 32'h56626cce; XM[ 10] = 32'ha65edef6; XM[ 11] = 32'h28c49eb8;
        XM[ 12] = 32'h5364b7f8; XM[ 13] = 32'hd170915e; XM[ 14] = 32'h5a4c960d; XM[ 15] = 32'h27cb1911;
        YM[  0] = 32'h7640c2ca; YM[  1] = 32'hf49d583a; YM[  2] = 32'he4ae0f22; YM[  3] = 32'ha3dad5ed;
        YM[  4] = 32'hbe88ab4d; YM[  5] = 32'h9fb50b38; YM[  6] = 32'h223feceb; YM[  7] = 32'hfc4893ff;
        YM[  8] = 32'hb40556c2; YM[  9] = 32'hb25b27fa; YM[ 10] = 32'h7e277535; YM[ 11] = 32'h42e1e9ab;
        YM[ 12] = 32'hebd55ef2; YM[ 13] = 32'h8b6d8c0b; YM[ 14] = 32'h4d91ad9a; YM[ 15] = 32'h0e8bf565;
        S[  0] = 32'h2f89f059; S[  1] = 32'hdbc41170; S[  2] = 32'h1d7ea6c0; S[  3] = 32'h1df9add6;
        S[  4] = 32'ha619e2e1; S[  5] = 32'h253fcd88; S[  6] = 32'h6c03a351; S[  7] = 32'h795b1df0;
        S[  8] = 32'h2854a51a; S[  9] = 32'h0245619b; S[ 10] = 32'hfb67ef8f; S[ 11] = 32'hcc5bdd4f;
        S[ 12] = 32'ha70f58bd; S[ 13] = 32'h31f15702; S[ 14] = 32'hd6f36259; S[ 15] = 32'h280e67e0;
    end


    //
    // Clocks
    //
    `define CLK_FREQUENCY_MHZ     (100.0)
    `define CLK_PERIOD_NS         (1000.0 / `CLK_FREQUENCY_MHZ)
    `define CLK_PERIOD_HALF_NS    (0.5    * `CLK_PERIOD_NS)
    `define CLK_PERIOD_QUARTER_NS (0.5    * `CLK_PERIOD_HALF_NS)

    `define CLK_BUS_FREQUENCY_MHZ     (25.0)
    `define CLK_BUS_PERIOD_NS         (1000.0 / `CLK_BUS_FREQUENCY_MHZ)
    `define CLK_BUS_PERIOD_HALF_NS    (0.5    * `CLK_BUS_PERIOD_NS)
    
	reg  clk      = 1'b1;
	reg  clk_dly  = 1'b0;
	wire clk_idle = clk & clk_dly; 
	
	reg  clk_bus      = 1'b1;
	reg  clk_bus_dly  = 1'b0;
	wire clk_bus_idle = clk_bus & clk_bus_dly;  

    always #`CLK_PERIOD_HALF_NS     clk     <= ~clk;
    always #`CLK_BUS_PERIOD_HALF_NS clk_bus <= ~clk_bus;
    
    always @(clk    ) clk_dly     <= #(`CLK_PERIOD_HALF_NS     - `CLK_PERIOD_QUARTER_NS) clk;
    always @(clk_bus) clk_bus_dly <= #(`CLK_BUS_PERIOD_HALF_NS - `CLK_PERIOD_QUARTER_NS) clk_bus;  
    
    
    //
    // Clock Sync
    //
    task sync_clk;
        while (clk_idle !== 1) _wait_quarter_clk_tick;
    endtask
    
    task sync_clk_bus;
        while (clk_bus_idle !== 1) _wait_quarter_clk_tick;
    endtask
    
    
    //
    // Reset
    //
    reg rst = 1'b1;
    wire rst_n = ~rst;
    
    
    //
    // Control / Status
    //
    reg [ 7:0] word_index_last_n;
    reg [ 7:0] word_index_last_pq;
    reg [11:0] bit_index_last_n;
    reg [11:0] bit_index_last_pq;
    reg        core_next = 1'b0;
    wire       core_valid;
    reg        core_crt_mode;
    
    
    //
    // System Bus
    //
    reg         bus_cs = 1'b0;
    reg         bus_we = 1'b0;
    reg  [11:0] bus_addr;
    reg  [31:0] bus_data_wr;
    wire [31:0] bus_data_rd;

    wire [ 1:0] bus_addr_sel  = bus_addr[11:10];
    wire [ 2:0] bus_addr_bank = bus_addr[9:7];
    wire [ 6:0] bus_addr_data = bus_addr[6:0];
    

    //
    // UUT
    //
    modexpng_core_top uut
    (
        .clk                (clk),
        .clk_bus            (clk_bus),
        
        .rst_n              (rst_n),
        
        .next               (core_next),
        .valid              (core_valid),
        
        .crt_mode           (core_crt_mode),
        
        .word_index_last_n  (word_index_last_n),
        .word_index_last_pq (word_index_last_pq),
        
        .bit_index_last_n   (bit_index_last_n),
        .bit_index_last_pq  (bit_index_last_pq),
        
        .bus_cs             (bus_cs),
        .bus_we             (bus_we),
        .bus_addr           (bus_addr),
        .bus_data_wr        (bus_data_wr),
        .bus_data_rd        (bus_data_rd)
    );


    //
    // Bus Init Routine
    //
    task core_set_input;
        begin         
            core_set_input_1;
            core_set_input_2;
            wait_clk_bus_ticks(10);
            $display("Core input banks written.");
        end
    endtask
    
    
    //
    // Script
    //
    initial main;
    
    
    //
    // Main Routine (Control/Status, Bus)
    //
    integer i, j, k;
    task main;
        begin

        
            sync_clk;               // switch to fast core clock
            core_reset;             // reset core
            core_set_params;        // set parameters (modulus width, exponent length)
        
            sync_clk_bus;           // switch to slow bus clock
            core_set_input;         // write to core input banks
            /**//**/
            sync_clk;               // switch to fast core clock
            core_set_crt_mode(1);   // enable CRT signing
            core_pulse_next;        // assert 'next' bit for one cycle
            core_wait_valid;        // wait till 'valid' bit gets asserted
    
            sync_clk_bus;           // switch to slow bus clock
            core_get_output;        // read from core output banks
            core_verify_output;     // check, whether core output matches precomputed known good refrence values
            core_print_load;        //
            /**//**/
            sync_clk;               // switch to fast core clock
            core_set_crt_mode(0);   // disable CRT signing
            core_pulse_next;        // assert 'next' bit for one cycle
            core_wait_valid;        // wait till 'valid' bit gets asserted
    
            sync_clk_bus;           // switch to slow bus clock
            core_get_output;        // read from core output banks
            core_verify_output;     // check, whether core output matches precomputed known good refrence values
            core_print_load;        //
            /**//**/
        end
    endtask
    
    task core_reset;
        begin
            wait_clk_ticks(100);
            rst = 1'b0;
            wait_clk_ticks(10);
            $display("Core reset finished.");
        end
    endtask
    
    task core_set_params;
        begin
            word_index_last_n  = CORE_NUM_WORDS_N - 1;
            word_index_last_pq = CORE_NUM_WORDS_PQ - 1;
            bit_index_last_n   = TB_MODULUS_LENGTH_N - 1;
            bit_index_last_pq  = TB_MODULUS_LENGTH_N / 2 - 1;
            $display("Core parameters set.");
        end
    endtask
    
    task core_set_crt_mode;
        input _crt;
        begin
            core_crt_mode = _crt;
            if (_crt) $display("Enabled CRT mode.");
            else      $display("Disabled CRT mode.");
        end
    endtask
    
    task core_pulse_next;
        begin
            core_next = 1'b1;
            wait_clk_tick;
            core_next = 1'b0;
            $display("Pulsed core 'next' control signal.");
        end
    endtask

    task core_wait_valid;
        begin
            while (!core_valid) wait_clk_tick;
            wait_clk_ticks(10);
            $display("Detected high core 'valid' status signal.");
        end
    endtask

    
    //
    // Variables
    //    
    integer _w, _n;
    

    //
    // core_set_input_1
    //
    task core_set_input_1;
        reg [9:0] _tn;
        begin
            _tn = BANK_IN_1_N_COEFF * 2 ** BUS_OP_ADDR_W + TB_NUM_WORDS_N; // trick to write extra trailer word
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_write(2'd1, BANK_IN_1_M,        _w[6:0],  M[_w]);
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_write(2'd1, BANK_IN_1_N,        _w[6:0],  N[_w]);
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_write(2'd1, BANK_IN_1_N_FACTOR, _w[6:0],  N_FACTOR[_w]);
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_write(2'd1, BANK_IN_1_N_COEFF,  _w[6:0],  N_COEFF[_w]);
                                                   bus_write(2'd1, _tn[9:7],           _tn[6:0], N_COEFF[TB_NUM_WORDS_N]);
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_write(2'd1, BANK_IN_1_X,        _w[6:0],  X[_w]);
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_write(2'd1, BANK_IN_1_Y,        _w[6:0],  Y[_w]);                                    
        end
    endtask
    
    
    //
    // core_set_input_2
    //
    task core_set_input_2;
        begin
            for (_w=0; _w< TB_NUM_WORDS_N;  _w=_w+1) bus_write(2'd2, BANK_IN_2_D,        {      _w[6:0]}, D       [_w]);
            for (_w=0; _w< TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_P,        {1'b0, _w[5:0]}, P       [_w]);
            for (_w=0; _w< TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_P,        {1'b1, _w[5:0]}, DP      [_w]);
            for (_w=0; _w< TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_P_FACTOR, {      _w[6:0]}, P_FACTOR[_w]);
            for (_w=0; _w<=TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_P_COEFF,  {      _w[6:0]}, P_COEFF [_w]);
            for (_w=0; _w< TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_Q,        {1'b0, _w[5:0]}, Q       [_w]);
            for (_w=0; _w< TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_Q,        {1'b1, _w[5:0]}, DQ      [_w]);
            for (_w=0; _w< TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_Q_FACTOR, {      _w[6:0]}, Q_FACTOR[_w]);
            for (_w=0; _w<=TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_Q_COEFF,  {      _w[6:0]}, Q_COEFF [_w]);            
            for (_w=0; _w< TB_NUM_WORDS_PQ; _w=_w+1) bus_write(2'd2, BANK_IN_2_QINV,     {      _w[6:0]}, QINV    [_w]);
        end
    endtask
    
    
    //
    // core_get_output
    //
    task core_get_output;
        begin
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_read(2'd3, BANK_OUT_XM, _w[6:0], XM_READBACK[_w]);
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_read(2'd3, BANK_OUT_YM, _w[6:0], YM_READBACK[_w]);
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) bus_read(2'd3, BANK_OUT_S,  _w[6:0], S_READBACK[_w]);
            wait_clk_bus_ticks(10);
            $display("Core output banks read.");
        end
    endtask


    //
    // core_verify_output
    //
    task core_verify_output;
        //
        reg xm_ok;
        reg ym_ok;
        reg s_ok;
            //
        begin
            //
            xm_ok = 1;
            ym_ok = 1;
            s_ok = 1;
            //
            for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) begin
               if (XM_READBACK[_w] !== XM[_w]) xm_ok = 0; 
               if (YM_READBACK[_w] !== YM[_w]) ym_ok = 0;
               if (S_READBACK[_w] !== S[_w]) s_ok = 0;
            end
            //
            if (!xm_ok)
                //
                for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) begin
                    $write("XM / XM_READBACK [%3d] = 0x%08x / 0x%08x", _w, XM[_w], XM_READBACK[_w]);
                    if (XM[_w] !== XM_READBACK[_w]) $write(" <???: 0x%08x> ", XM[_w] ^ XM_READBACK[_w]);
                    $write("\n");
                end
            //
            if (!ym_ok)
                //
                for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) begin
                    $write("YM / YM_READBACK [%3d] = 0x%08x / 0x%08x", _w, YM[_w], YM_READBACK[_w]);
                    if (YM[_w] !== YM_READBACK[_w]) $write(" <???: 0x%08x> ", YM[_w] ^ YM_READBACK[_w]);
                    $write("\n");
                end
            //
            if (!s_ok)
                //
                for (_w=0; _w<TB_NUM_WORDS_N; _w=_w+1) begin
                    $write("S / S_READBACK [%3d] = 0x%08x / 0x%08x", _w, S[_w], S_READBACK[_w]);
                    if (S[_w] !== S_READBACK[_w]) $write(" <???: 0x%08x> ", S[_w] ^ S_READBACK[_w]);
                    $write("\n");
                end
            //
            $write("XM is ");
            if (xm_ok) $write("OK.\n");
            else       $write("WRONG!\n");
            //
            $write("YM is ");
            if (ym_ok) $write("OK.\n");
            else       $write("WRONG!\n");
            //
            $write("S is ");
            if (s_ok) $write("OK.\n");
            else       $write("WRONG!\n");                        
            //
        end
        //
    endtask
    

    //
    // _bus_drive()
    //    
    task _bus_drive;
        input cs;
        input we;
        input [11:0] addr;
        input [31:0] data;
        {bus_cs, bus_we, bus_addr, bus_data_wr} <= {cs, we, addr, data};
    endtask
    
    
    //
    // bus_write()
    //
    task bus_write;
        input  [ 1:0] sel;
        input  [ 2:0] bank;
        input  [ 6:0] addr;
        input  [31:0] data;
        begin
            _bus_drive(1'b1, 1'b1, {sel, bank, addr}, data);
            wait_clk_bus_tick;
            _bus_drive(1'b0, 1'b0, 12'hXXX, 32'hXXXXXXXX);
        end
    endtask
    
    
    //
    // bus_read()
    //
    task bus_read;
        input  [ 1:0] sel;
        input  [ 2:0] bank;
        input  [ 6:0] addr;
        output [31:0] data;
        begin
            _bus_drive(1'b1, 1'b0, {sel, bank, addr}, 32'hXXXXXXXX);
            wait_clk_bus_tick;
            data = bus_data_rd;
            _bus_drive(1'b0, 1'b0, 12'hXXX, 32'hXXXXXXXX);
        end
    endtask
    
    
    //
    // _wait_quarter_clk_tick()
    //
    task _wait_quarter_clk_tick;
        #`CLK_PERIOD_QUARTER_NS;
    endtask
    
    
    //
    // _wait_half_clk_tick()
    //
    task _wait_half_clk_tick;
        begin
            _wait_quarter_clk_tick;
            _wait_quarter_clk_tick;
        end
    endtask
    
    
    //
    // wait_clk_tick()
    //
    task wait_clk_tick;
        begin
            _wait_half_clk_tick;
            _wait_half_clk_tick;
        end
    endtask
    

    //
    // wait_clk_bus_tick()
    //
    task wait_clk_bus_tick;
        #`CLK_BUS_PERIOD_NS;
    endtask


    //
    // wait_clk_ticks()
    //    
    task wait_clk_ticks;
        input integer num_ticks;
        for (_n=0; _n<num_ticks; _n=_n+1)
            wait_clk_tick;
    endtask
    

    //
    // wait_clk_bus_ticks()
    //    
    task wait_clk_bus_ticks;
        input integer num_ticks;
        for (_n=0; _n<num_ticks; _n=_n+1)
            wait_clk_bus_tick;
    endtask


    //
    // Multiplier Load Calculator
    //
    real load_cyc_total_prev = 0.0;
    real load_cyc_mult_prev = 0.0;

    real load_cyc_total = 0.0;
        
    always @(posedge clk)
        //
        if (!core_valid)
            load_cyc_total <= load_cyc_total + 1.0;
    
    task core_print_load;
        real delta_cyc_total, delta_cyc_mult, load_pct;
        begin
        `ifndef MODEXPNG_ENABLE_DEBUG
            $display("core_print_load: Multiplier load was not calculated, since MODEXPNG_ENABLE_DEBUG was no defined.");   
        `else
            delta_cyc_total = load_cyc_total - load_cyc_total_prev;
            delta_cyc_mult  = uut.mmm_x.load_cyc_mult - load_cyc_mult_prev;
            load_pct = 100.0 * delta_cyc_mult / delta_cyc_total;  
            $display("Multiplier load: %.1f%%", load_pct);
            load_cyc_total_prev = load_cyc_total; 
            load_cyc_mult_prev = uut.mmm_x.load_cyc_mult;
        `endif
        end
    endtask

endmodule