├── .gitignore ├── LICENSE ├── README.md ├── combinational ├── README.md ├── add.sv ├── add_tb.sv ├── alu.sv ├── alu_pkg.sv ├── alu_tb.sv ├── mult.sv ├── mult_tb.sv ├── mux2x1.sv ├── mux2x1_tb.sv ├── priority_encoder.sv ├── priority_encoder_4in.sv ├── priority_encoder_4in_tb.sv └── priority_encoder_tb.sv ├── fsm ├── README.md ├── fsm.pdf ├── fsm.vsd ├── mealy.sv ├── mealy_tb.sv ├── moore.sv └── moore_tb.sv ├── fsmd ├── README.md ├── bit_diff.docx ├── bit_diff.pdf ├── bit_diff.pptx ├── bit_diff.sv └── bit_diff_tb.sv ├── gotchas ├── README.md ├── mult_add.sv ├── mult_add_tb.sv ├── multiple_add.sv ├── mux4x1.sv ├── mux4x1_tb.sv ├── ram.sv ├── structure.sv └── structure_tb.sv ├── ram ├── README.md ├── ram_sdp.sv ├── ram_sdp_tb.sv ├── ram_sdp_with_reset_vivado.sv ├── ram_tdp.sv ├── ram_tdp_quartus.sv └── ram_tdp_vivado.sv ├── sequential ├── README.md ├── architectures.pdf ├── architectures.vsd ├── register.sv ├── register_tb.sv └── seq_example.sv ├── structural ├── README.md ├── delay.docx ├── delay.pdf ├── delay.sv ├── delay_tb.sv ├── mux4x1.pdf ├── mux4x1.pptx ├── mux4x1.sv ├── mux4x1_tb.sv ├── ripple_carry_adder.docx ├── ripple_carry_adder.pdf ├── ripple_carry_adder.sv └── ripple_carry_adder_tb.sv └── testbenches ├── README.md ├── assertions ├── delay.sv ├── delay_tb.sv ├── ff.sv ├── ff_tb.sv ├── fifo.sv ├── fifo_tb.sv ├── mux2x1.sv ├── mux2x1_tb.sv ├── register.sv ├── register_tb.sv ├── simple_pipeline_with_en.sv └── simple_pipeline_with_en_tb.sv ├── basic ├── mux2x1.sv ├── mux2x1_tb.sv ├── race.sv ├── register.sv ├── register_tb.sv └── reset_race.sv ├── coverage ├── add.sv ├── add_tb.sv ├── fifo.sv └── fifo_tb.sv ├── crv ├── add.sv ├── add_tb.sv ├── bit_diff.sv ├── bit_diff_oop │ ├── bit_diff.sv │ ├── bit_diff_bfm.sv │ ├── bit_diff_item.svh │ ├── bit_diff_tb.sv │ ├── driver.svh │ ├── environment.svh │ ├── generator.svh │ ├── monitor.svh │ ├── scoreboard.svh │ └── test.svh └── bit_diff_tb.sv └── uvm ├── agents ├── Makefile ├── README.md ├── axi4_stream_agent.svh ├── axi4_stream_driver.svh ├── axi4_stream_if.sv ├── axi4_stream_monitor.svh ├── axi4_stream_seq_item.svh ├── axi4_stream_sequencer.svh ├── mult.sv ├── mult_base_test.svh ├── mult_coverage.svh ├── mult_env.svh ├── mult_scoreboard.svh ├── mult_sequence.svh ├── mult_simple_test.svh ├── mult_tb.sv ├── mult_tb_pkg.sv └── sources.txt ├── agents_parameterized ├── Makefile ├── README.md ├── axi4_stream_agent.svh ├── axi4_stream_driver.svh ├── axi4_stream_if.sv ├── axi4_stream_monitor.svh ├── axi4_stream_pkg.sv ├── axi4_stream_seq_item.svh ├── axi4_stream_sequencer.svh ├── mult.sv ├── mult_base_test.svh ├── mult_coverage.svh ├── mult_env.svh ├── mult_scoreboard.svh ├── mult_sequence.svh ├── mult_simple_test.svh ├── mult_tb.sv ├── mult_tb_pkg.sv └── sources.txt ├── basics ├── Makefile ├── README.md ├── bit_diff.sv ├── bit_diff_agent.svh ├── bit_diff_base_test.svh ├── bit_diff_driver.svh ├── bit_diff_env.svh ├── bit_diff_if.sv ├── bit_diff_if_pkg.sv ├── bit_diff_item.svh ├── bit_diff_monitor.svh ├── bit_diff_scoreboard.svh ├── bit_diff_sequence.svh ├── bit_diff_sequencer.svh ├── bit_diff_simple_test.svh ├── bit_diff_tb.sv └── sources.txt └── multiple_tests ├── Makefile ├── README.md ├── accum.sv ├── accum_base_test.svh ├── accum_coverage.svh ├── accum_env.svh ├── accum_packet_test.svh ├── accum_scoreboard.svh ├── accum_sequence.svh ├── accum_single_beat_test.svh ├── accum_tb.sv ├── accum_tb_pkg.sv ├── axi4_stream_agent.svh ├── axi4_stream_driver.svh ├── axi4_stream_if.sv ├── axi4_stream_monitor.svh ├── axi4_stream_pkg.sv ├── axi4_stream_seq_item.svh ├── axi4_stream_sequencer.svh └── sources.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | # Quartus Project Files 4 | qdb/ 5 | output_files/ 6 | tmp-clearbox/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This repository provides a tutorial on how to write synthesizable SystemVerilog code. It touches on verification topics, but the primary focus is on code for synthesis. Most of the provided examples include multiple implementations that illustrate common mistakes, different ways of implementing the same circuit, or different tradeoffs. 4 | 5 | # Prerequisites 6 | 7 | This tutorial assumes you already have a background in digital logic, synthesis tools, and simulators. All examples have been tested in Quartus and Modelsim, for which there are free versions available for students. 8 | 9 | # Methodology: design the circuit, then write the code. 10 | 11 | My biggest suggestion for writing synthesizable code in any language is to design the circuit, then write the code. Basically, you should be able to hierarchically divide a large circuit into smaller and smaller components, until each component is either combinational logic, sequential logic, a combination of both (e.g., a datapath), finite state machines, or memories. Then, you can simply follow the following synthesis guidelines for each of these types of circuits. Note that some of the guidelines contradict each other if used for the wrong type of logic, which is why it is always important to know the type of logic that you are designing. In other words, don't start writing the code until you know what type of circuit you are describing. If you are creating a structural archiecture, draw the schematic first. If you are designing a state machine, draw the FSM first. If you are designing a circuit with registers, first figure out exactly where you want registers, etc. 12 | 13 | # Suggested Study Order 14 | 15 | 1. [Combinational Logic](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/combinational) 16 | 1. [Structural Architectures](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/structural) 17 | 1. [Sequential Logic](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/sequential) 18 | 1. [Finite-State Machines](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/fsm) 19 | 1. [Finite-State Machines + Datapaths](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/fsmd) 20 | 1. [RAM Inference Templates](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/ram) 21 | 1. [Problematic Coding Practices (Gotchas)](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/gotchas) 22 | 1. [Testbenches](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/testbenches) 23 | -------------------------------------------------------------------------------- /combinational/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This directory provides a tutorial on how to create synthesizable behavior descriptions of combinational logic. All examples include testbenches for simulation, which uses the name of the module being simulated with a _tb.sv suffix. Testbenches are commented, but will be explained in detail in a different section of the tutorial. 4 | 5 | # Methodology: design the circuit, then write the code. 6 | 7 | As with all circuits, first design the combinational circuit, then write the code. With combinational logic, this methodology is often confusing because synthesis tools are generally very good at optimizing combinational logic. So, unlike other types of logic, you can write often combinational logic in many ways that will all synthesize to efficient circuits. However, you should at the very least consider the I/O interface before starting to write the code. You could also try to simplify the logic manually, but for pure combinational logic, synthesis tools will likely do a better job. 8 | 9 | # Suggested Study Order 10 | 11 | 1. [2:1 mux](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/combinational/mux2x1.sv) 12 | - Introduces basic constructs and guidelines for combinational logic. 13 | - Includes a top-level module mux2x1 that allows you to change the module that is synthesized. 14 | - Includes a testbench that tests all included modules at the same time. 15 | 1. [4-input Priority Encoder](https://github.com/ARC-Lab-UF/sv-tutorial/blob/main/combinational/priority_encoder_4in.sv) 16 | - Introduces packed arrays. 17 | - Discusses appropriate situations for if and case statements. 18 | 1. [Parameterized Priority Encoder](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/combinational/priority_encoder.sv) 19 | - Introduces parameters to support any number of inputs. 20 | - Introduces for loops inside always blocks. 21 | - Introduces local parameters. 22 | - Introduces how to convert an integer to any number of bits to avoid width mismatch problems. 23 | 1. [Adders](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/combinational/add.sv) 24 | - Introduces arithmetic operations, blocking vs. non-blocking assignments, concatenation, automatic variable resizing. 25 | - Illustrates a variety of adders (no carry, carry out, carry in & out, carry in, out, and overflow) 26 | 1. [Multipliers](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/combinational/mult.sv) 27 | - Introduces signed and unsigned, generate statements, variable scope, slicing, and hiearchical access of generate blocks. 28 | -Testbench tests signed and unsigned instances simultaneously. 29 | 1. [ALU](https://github.com/ARC-Lab-UF/sv-tutorial/tree/main/combinational/alu.sv) 30 | - Introduces common problems with latches, strategies for avoiding latches, local parameters, and tasks. 31 | - Introduces packages, importing, and scope resolution to avoid namespace conflicts. 32 | 33 | -------------------------------------------------------------------------------- /combinational/alu_pkg.sv: -------------------------------------------------------------------------------- 1 | package alu_pkg; 2 | typedef enum logic [1:0] { 3 | ADD_SEL = 2'b00, 4 | SUB_SEL = 2'b01, 5 | AND_SEL = 2'b10, 6 | OR_SEL = 2'b11 7 | } alu_sel_t; 8 | endpackage -------------------------------------------------------------------------------- /combinational/alu_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 100 ps 5 | 6 | // Module: alu_tb 7 | // Description: Testbench for alu module. 8 | 9 | module alu_tb #( 10 | parameter int NUM_TESTS = 10000, 11 | parameter int WIDTH = 8 12 | ); 13 | logic [WIDTH-1:0] in0, in1, out; 14 | logic [1:0] sel; 15 | logic neg, pos, zero; 16 | logic [WIDTH-1:0] correct_out; 17 | 18 | alu #(.WIDTH(WIDTH)) DUT (.*); 19 | 20 | // Function to check the status flags 21 | function void check_flags(); 22 | // Check negative flag. 23 | if (correct_out[WIDTH-1] != neg) 24 | $display("ERROR (time %0t): neg = %b instead of %b.", $realtime, neg, correct_out[WIDTH-1]); 25 | 26 | // Check zero flag. 27 | if ((correct_out == 0) != zero) 28 | $display("ERROR (time %0t): zero = %b instead of %b.", $realtime, zero, correct_out == 0); 29 | 30 | // Check pos flag. 31 | if ((signed'(correct_out) > 0) != pos) 32 | $error( 33 | "[%0t]: pos = %b instead of %b.", $realtime, pos, correct_out != 0 && !correct_out[WIDTH-1] 34 | ); 35 | endfunction 36 | 37 | initial begin 38 | $timeformat(-9, 0, " ns"); 39 | 40 | // Test NUM_TESTS random inputs and select values. 41 | for (int i = 0; i < NUM_TESTS; i++) begin 42 | in0 <= $urandom; 43 | in1 <= $urandom; 44 | sel <= $urandom; 45 | #10; 46 | if (sel == 2'b00) correct_out = in0 + in1; 47 | else if (sel == 2'b01) correct_out = in0 - in1; 48 | else if (sel == 2'b10) correct_out = in0 & in1; 49 | else if (sel == 2'b11) correct_out = in0 | in1; 50 | 51 | if (out != correct_out) 52 | $display("ERROR (time %0t): out = %h instead of %h.", $realtime, out, correct_out); 53 | 54 | if (sel == 2'b00 || sel == 2'b01) check_flags(); 55 | end 56 | 57 | $display("Tests completed."); 58 | end 59 | endmodule 60 | -------------------------------------------------------------------------------- /combinational/mult_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // Module: mult_tb 5 | // Description: Testbench for the different mult modules in mult.sv. Change 6 | // the module that is instantiated in the mult module to test different modules. 7 | 8 | module mult_tb #( 9 | parameter int NUM_TESTS = 1000, 10 | parameter int INPUT_WIDTH = 16 11 | ); 12 | logic [INPUT_WIDTH-1:0] in0, in1; 13 | logic [INPUT_WIDTH*2-1:0] product_signed, product_unsigned; 14 | 15 | // Apparently SV doesn't allow the .* operator for parameters, only ports. 16 | mult #( 17 | .IS_SIGNED (1'b1), 18 | .INPUT_WIDTH(INPUT_WIDTH) 19 | ) DUT_SIGNED ( 20 | .product(product_signed), 21 | .* 22 | ); 23 | 24 | mult #( 25 | .IS_SIGNED (1'b0), 26 | .INPUT_WIDTH(INPUT_WIDTH) 27 | ) DUT_UNSIGNED ( 28 | .product(product_unsigned), 29 | .* 30 | ); 31 | 32 | initial begin 33 | 34 | logic [INPUT_WIDTH*2-1:0] correct_product_signed, correct_product_unsigned; 35 | 36 | for (int i = 0; i < NUM_TESTS; i++) begin 37 | in0 <= $urandom; 38 | in1 <= $urandom; 39 | #10; 40 | correct_product_signed = signed'(in0) * signed'(in1); 41 | correct_product_unsigned = in0 * in1; 42 | 43 | if (product_signed != correct_product_signed) 44 | $error( 45 | "[%0t]: signed product = %d instead of %d.", 46 | $realtime, 47 | product_signed, 48 | correct_product_signed 49 | ); 50 | 51 | if (product_unsigned != correct_product_unsigned) 52 | $error( 53 | "[%0t]: unsigned product = %d instead of %d.", 54 | $realtime, 55 | product_unsigned, 56 | correct_product_unsigned 57 | ); 58 | end 59 | 60 | $display("Tests completed."); 61 | end 62 | endmodule // mult_tb 63 | -------------------------------------------------------------------------------- /combinational/priority_encoder_4in_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1ns / 100 ps 5 | 6 | // Module: priority_encoder_4in_tb 7 | // Description: A simple testbench for the priority_encoder_4in module. 8 | 9 | module priority_encoder_4in_tb; 10 | 11 | logic [3:0] inputs; 12 | logic [1:0] result; 13 | logic valid; 14 | 15 | priority_encoder_4in DUT (.*); 16 | 17 | initial begin 18 | logic [1:0] correct_result; 19 | logic correct_valid; 20 | $timeformat(-9, 0, " ns"); 21 | 22 | for (int i = 0; i < 16; i++) begin 23 | inputs <= i; 24 | #10; 25 | 26 | correct_result = '0; 27 | for (int j = 3; j >= 0; j--) begin 28 | if (inputs[j] == 1'b1) begin 29 | correct_result = j; 30 | break; 31 | end 32 | end 33 | 34 | correct_valid = inputs != 4'b0; 35 | 36 | if (result != correct_result) 37 | $display("ERROR (time %0t): result = %b instead of %b.", $realtime, result, correct_result); 38 | 39 | if (valid != correct_valid) 40 | $display("ERROR (time %0t): valid = %b instead of %b.", $realtime, valid, correct_valid); 41 | end 42 | 43 | $display("Tests completed."); 44 | end 45 | endmodule 46 | 47 | -------------------------------------------------------------------------------- /combinational/priority_encoder_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 100 ps 5 | 6 | // Module: priority_encoder_tb 7 | // Description: A simple testbench for the priority_encoder module. 8 | // NOTE: The testbench is exhaustive, so large NUM_INPUTS values will not 9 | // be feasible to test here. 10 | 11 | module priority_encoder_tb #( 12 | parameter int NUM_INPUTS = 8 13 | ); 14 | 15 | logic [NUM_INPUTS-1:0] inputs; 16 | logic [$clog2(NUM_INPUTS)-1:0] result; 17 | logic valid; 18 | 19 | priority_encoder #(.NUM_INPUTS(NUM_INPUTS)) DUT (.*); 20 | 21 | initial begin 22 | logic [$clog2(NUM_INPUTS)-1:0] correct_result; 23 | logic correct_valid; 24 | $timeformat(-9, 0, " ns"); 25 | 26 | for (int i = 0; i < 2 ** NUM_INPUTS; i++) begin 27 | inputs <= i; 28 | #10; 29 | 30 | correct_result = '0; 31 | for (int j = NUM_INPUTS - 1; j >= 0; j--) begin 32 | if (inputs[j] == 1'b1) begin 33 | correct_result = j; 34 | break; 35 | end 36 | end 37 | 38 | correct_valid = inputs != 0; 39 | 40 | if (result !== correct_result) 41 | $error("([%0t] result = %b instead of %b.", $realtime, result, correct_result); 42 | 43 | if (valid !== correct_valid) 44 | $error("[%0t] valid = %b instead of %b.", $realtime, valid, correct_valid); 45 | end 46 | 47 | $display("Tests completed."); 48 | end 49 | 50 | endmodule 51 | -------------------------------------------------------------------------------- /fsm/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This directory provides a tutorial on how to create finite state machines (FSMs) in SystemVerilog. The example demonstrates two different models, which I 4 | refer to as the 1-process/block and 2-process/block models. The 1-process model uses the coding guidelines from sequential logic to capture the state register, 5 | next-state logic, and output logic in a single process. However, this model has the limitation of registering the outputs, which I don't recommend 6 | unless you have a good reason to do so. The 2-process model uses one process for sequential logic (the state register) and another process for 7 | combinational logic (next-state and output logic). I strongly recommend the 2-process model to avoid the extra cycle of delay on the output of the 8 | 1-process model. 9 | 10 | You might see other people using 3- and 4-process models, but I've never seen an advantage. [This paper](http://www.sunburst-design.com/papers/CummingsSNUG2019SV_FSM1.pdf) is an excellent paper on different FSM coding styles. It reports advantages of their 11 | 3- and 4- process models, but I can represent everything in those models using 2 processes, so to my knowledge there is no inherent 12 | technical advantage, possibly just a convenience advantage. In any case, I will explore the issue further and update the examples 13 | for any new findings. 14 | 15 | # Methodology: design the circuit, then write the code. 16 | 17 | For FSMs, designing the circuit is a little more obvious, and corresponds to creating the states, transitions between states, and outputs for each 18 | state and/or transition. Basically, you want create a diagram for the FSM. Given that diagram, it is trivial to convert the FSM into code, as shown 19 | in the examples below. 20 | 21 | # Suggested Study Order 22 | 23 | 1. [Moore](moore.sv) 24 | - Illustrates various architectures for a 1-process and 2-process model of a Moore state machine. 25 | - See the Moore diagram in [fsm.pdf](fsm.pdf) for an illustration of the FSM represented in code. 26 | 1. [Mealy](mealy.sv) 27 | - Illustrates various 2-process architectures for a Mealy state machine. 28 | - Illustrates a hybrid Moore/Mealy implementation. 29 | - See the Mealy and Hybrid Mealy diagram in [fsm.pdf](fsm.pdf) for an illustration of the FSMs represented in code. 30 | - NOTE: Provided testbench does not fully check for correctness. 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /fsm/fsm.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/fsm/fsm.pdf -------------------------------------------------------------------------------- /fsm/fsm.vsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/fsm/fsm.vsd -------------------------------------------------------------------------------- /fsm/mealy_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 100 ps 5 | 6 | // Module: mealy_tb 7 | // Description: A testbench for the mealy module. This testbench only checks 8 | // that done is asserted. It mainly provides an input stimulus and should not 9 | // be considered a thorough test. 10 | 11 | module mealy_tb; 12 | 13 | logic clk = 0, rst, go, ack, done, en; 14 | 15 | mealy DUT (.*); 16 | 17 | initial begin : generate_clock 18 | while (1) #5 clk = ~clk; 19 | end 20 | 21 | initial begin 22 | $timeformat(-9, 0, " ns"); 23 | 24 | // Reset the FSM. 25 | rst <= 1'b1; 26 | ack <= 1'b0; 27 | go <= 1'b0; 28 | repeat(5) @(posedge clk); 29 | 30 | rst = 1'b0; 31 | @(posedge clk); 32 | 33 | // Start the FSM. 34 | go <= 1'b1; 35 | repeat(5) @(posedge clk); 36 | 37 | ack <= 1'b1; 38 | @(posedge clk); 39 | ack <= 1'b0; 40 | if (!done) $error("[%0t] done not asserted.", $realtime); 41 | 42 | // Clear go 43 | go <= 1'b0; 44 | @(posedge clk); 45 | 46 | // Repeat the execution to test the RESTART state 47 | go <= 1'b1; 48 | repeat(5) @(posedge clk); 49 | 50 | ack <= 1'b1; 51 | @(posedge clk); 52 | ack <= 1'b0; 53 | if (!done) $error("[%0t] done not asserted after restart.", $realtime); 54 | go <= 1'b0; 55 | 56 | disable generate_clock; 57 | $display("Tests completed."); 58 | end 59 | 60 | endmodule 61 | -------------------------------------------------------------------------------- /fsm/moore_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 100 ps 5 | 6 | // Module: moore_tb 7 | // Description: Testbench for the moore module, which implements a Moore FSM. 8 | // Note that if moore is changed to use the 1-process version, this testbench 9 | // will start to report errors because of the 1-cycle delay for the outputs. 10 | // It is left as an exercise to adapt the testbench to work for both models. 11 | 12 | module moore_tb #( 13 | parameter int NUM_CYCLES = 1000, 14 | 15 | // Set to 1 for 1-process tests, and 0 for 2-process tests. 16 | parameter bit DELAY_CORRECT_OUTPUT = 1'b0 17 | ); 18 | 19 | logic clk = 0, rst, en; 20 | logic [3:0] out; 21 | 22 | moore DUT (.*); 23 | 24 | initial begin : generate_clock 25 | forever #10 clk = ~clk; 26 | end 27 | 28 | logic [$bits(out)-1:0] correct_out; 29 | 30 | initial begin 31 | $timeformat(-9, 0, " ns"); 32 | 33 | rst <= 1'b1; 34 | en <= 1'b0; 35 | correct_out <= "0001"; 36 | repeat (5) @(posedge clk); 37 | 38 | rst <= 1'b0; 39 | 40 | for (int i = 0; i < NUM_CYCLES; i++) begin 41 | en <= $urandom; 42 | @(posedge clk); 43 | // The correct output simply rotates every time the enable is asserted. 44 | if (en) correct_out <= {correct_out[2:0], correct_out[3]}; 45 | 46 | //if (out != correct_out) $error("[%0t]: out = %h instead of %h.", $realtime, out, correct_out); 47 | end 48 | 49 | disable generate_clock; 50 | $display("Tests completed."); 51 | end 52 | 53 | if (DELAY_CORRECT_OUTPUT) begin 54 | assert property (@(posedge clk) disable iff (rst) out == $past(correct_out, 1)) 55 | else $error("[%0t] out = %h instead of %h.", $realtime, $sampled(out), $past(correct_out, 1)); 56 | end else begin 57 | assert property (@(posedge clk) out == correct_out) 58 | else $error("[%0t] out = %h instead of %h.", $realtime, $sampled(out), $past(correct_out, 1)); 59 | end 60 | endmodule 61 | -------------------------------------------------------------------------------- /fsmd/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This directory provides a tutorial on how to create controllers and datapaths to implement a specific algorithm. The examples demonstrate two different specification styles: 4 | FSMDs and FSM+Ds. An FSMD describes both the controller and datapath functionality at the same time in a single module. An FSM+D first creates an explicit datapath and a 5 | corresponding controller, and then combines them together. 6 | 7 | # Methodology: design the circuit, then write the code. 8 | 9 | For FSMDs and FSM+Ds, designing the circuit first requires designing an algorithm, which in many cases of hardware design is provided by a separate designer. For an FSMD, 10 | the next step in designing the circuit is breaking up the operations in the algorithm into separate states. The resulting design is essentially a finite state machine, but 11 | where instead of just having outputs and next-state transitions, you also have datapath operations assigned to states. After creating a diagram for this FSMD, there is a 12 | straightfoward translation into code. For FSM+Ds, the next step after creating the algorithm is to design a datapath to provide the necessary resources. You then create a 13 | module to capture this datapath, often structurally using other modules. After designing the datapath, you then create a corresponding controller, which is just a normal 14 | FSM. In some cases, that FSM will match the control states of the FSMD, but not always. After designing the controller, the FSM+D simply connects the controller with the 15 | datapath to provide a complete solution. 16 | 17 | # Suggested Study Order 18 | 19 | 1. [Bit Difference Calculator](bit_diff.sv) 20 | - Introduces 1- and 2-process FSMDs. 21 | - Introduces FSM+Ds with 3 different datapaths. 22 | - See the included bit_diff.pdf for an illustration of the FSMD and different datapaths. 23 | - Includes a top-level module bit_diff that allows you to change the module that is synthesized. 24 | -------------------------------------------------------------------------------- /fsmd/bit_diff.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/fsmd/bit_diff.docx -------------------------------------------------------------------------------- /fsmd/bit_diff.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/fsmd/bit_diff.pdf -------------------------------------------------------------------------------- /fsmd/bit_diff.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/fsmd/bit_diff.pptx -------------------------------------------------------------------------------- /fsmd/bit_diff_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // This file provides a simple testbench for the bit difference calculator. 5 | 6 | `timescale 1 ns / 10 ps 7 | 8 | // Module: bit_diff_tb 9 | // Description: Testbench for the bit difference calculator. 10 | 11 | module bit_diff_tb #( 12 | parameter int NUM_TESTS = 10000, 13 | parameter int WIDTH = 16 14 | ); 15 | 16 | logic clk, rst, go, done; 17 | logic [WIDTH-1:0] data; 18 | logic signed [$clog2(2*WIDTH+1)-1:0] result; 19 | int passed, failed, reference; 20 | 21 | // Change the instantiated implementation in the bit_diff module of 22 | // bit_diff.sv to test different implementations. 23 | bit_diff #(WIDTH) DUT (.*); 24 | 25 | // Reference model for correct result. 26 | function int model(int data, int width); 27 | automatic int diff = 0; 28 | 29 | for (int i = 0; i < WIDTH; i++) begin 30 | diff = data[0] ? diff + 1 : diff - 1; 31 | data = data >> 1; 32 | end 33 | 34 | return diff; 35 | endfunction 36 | 37 | initial begin : generate_clock 38 | clk = 1'b0; 39 | forever #5 clk <= ~clk; 40 | end 41 | 42 | initial begin 43 | $timeformat(-9, 0, " ns"); 44 | 45 | passed = 0; 46 | failed = 0; 47 | 48 | // Reset the design. 49 | rst <= 1'b1; 50 | go <= 1'b0; 51 | data <= '0; 52 | repeat(5) @(posedge clk); 53 | @(negedge clk); 54 | rst <= 1'b0; 55 | 56 | for (int i = 0; i < NUM_TESTS; i++) begin 57 | data <= $random; 58 | go <= 1'b1; 59 | @(posedge clk); 60 | go <= 1'b0; 61 | 62 | // Works for registered outputs, but not safe for glitches that may 63 | // occur from combinational logic outputs. 64 | // Test bit_diff_fsmd_2p for an example of where this fails. 65 | //@(posedge done); 66 | 67 | // Instead, wait until done is cleared on an edge, and then asserted 68 | // on an edge. 69 | @(posedge clk iff (done == 1'b0)); 70 | //$display("Done is 0 (time %0t).", $time); 71 | @(posedge clk iff (done == 1'b1)); 72 | //$display("Done is 1 (time %0t).", $time); 73 | 74 | // Similar strategy, but less concise 75 | /*while(1) begin 76 | @(posedge clk); 77 | if (done) break; 78 | end */ 79 | 80 | // Compare the output with the reference model. 81 | reference = model(data, WIDTH); 82 | if (result == reference) begin 83 | passed++; 84 | end else begin 85 | $display("Test failed (time %0t): result = %0d instead of %0d.", $time, result, reference); 86 | failed++; 87 | end 88 | end 89 | 90 | $display("Tests completed: %0d passed, %0d failed", passed, failed); 91 | disable generate_clock; 92 | end 93 | 94 | // Check to make sure done cleared within a cycle. 95 | assert property (@(posedge clk) disable iff (rst) go && done |=> !done); 96 | 97 | endmodule 98 | -------------------------------------------------------------------------------- /gotchas/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This directory looks at some of the common SystemVerilog "gotchas." These gotchas are potentially problematic constructs or practices that often lead to bugs that are difficult 4 | to identify. One challenge with SystemVerilog is that it does much more for you automatically than VHDL. Unfortunately, these automatic changes often disguise design mistakes that 5 | may not even result in warnings. As much as possible, our goal is to catch problems at compile time because debugging in simulation is much harder. In some situations, these 6 | gotchas can result in differences between simulation and synthesized behavior, which requires debugging on an FPGA. Such debugging is incredibly difficult and should be avoided 7 | whenever possible. To avoid these situations, it is very important to avoid these gotchas. 8 | 9 | # Suggested Study Order 10 | 11 | 1. [mult_add](mult_add.sv) 12 | - Gotchas: automatic net inference and automatic width conversion 13 | 1. [4:1 mux](mux4x1.sv) 14 | - Gotcha: invalid array indexes in unpacked arrays. 15 | - Make sure to look at both the [module](mux4x1.sv) and [testbench](mux4x1_tb.sv) since the testbench has its own gotchas. 16 | 1. [ram](ram.sv) 17 | - Gotcha: index truncation in unpacked arrays. 18 | - Note: there is no testbench for this example. The intent is to synthesize it and see that there are no errors are warnings, despite an obvious problem in the code. 19 | 1. [structure](structure.sv) 20 | - Gotcha: width mismatches in structural architectures. 21 | 1. [multiple_add](multiple_add.sv) 22 | - Gotcha: inputs can accidentally be driven internally from a module via port mapping, which causes synthesis to optimize away parts of the design. 23 | -------------------------------------------------------------------------------- /gotchas/mult_add_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // This files provides a basic testbench to demonstrate the gotcha from 5 | // mult_add.sv. 6 | 7 | `timescale 1 ns / 10 ps 8 | 9 | module mult_add_tb; 10 | 11 | localparam WIDTH = 8; 12 | localparam NUM_TESTS = 1000; 13 | 14 | logic [WIDTH-1:0] inputs[4]; 15 | logic [2*WIDTH-1:0] result; 16 | logic clk; 17 | 18 | mult_add_bad #(.WIDTH(WIDTH)) DUT (.*); 19 | 20 | initial begin : generate_clock 21 | clk = 1'b0; 22 | while (1) #5 clk = ~clk; 23 | end 24 | 25 | initial begin 26 | for (int i=0; i < NUM_TESTS; i++) begin 27 | inputs[0] = $random; 28 | inputs[1] = $random; 29 | inputs[2] = $random; 30 | inputs[3] = $random; 31 | @(posedge clk); 32 | end 33 | 34 | $display("Tests completed."); 35 | disable generate_clock; 36 | end 37 | 38 | assert property (@(posedge clk) result == inputs[0]*inputs[1] + inputs[2]*inputs[3]); 39 | 40 | endmodule 41 | -------------------------------------------------------------------------------- /gotchas/multiple_add.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // In this example, we demonstrate how an input to a module can accidentally be 5 | // driven from inside the module through an incorrect port mapping. 6 | 7 | // Module: multiple_add2 8 | // Description: This module simply takes an input, registers it, and adds it 9 | // with NUM_ADDERS separate constants to generate NUM_ADDERS outputs. 10 | // There is nothing wrong with this module, but when we try to synthesize it 11 | // with multiple_add (see below) as the top-level module, we'll see all the 12 | // outputs are disconnected. 13 | 14 | module multiple_add2 15 | #( 16 | parameter int DATA_WIDTH=32, 17 | parameter int NUM_ADDERS=64 18 | ) 19 | ( 20 | input logic clk, 21 | input logic rst, 22 | input logic [DATA_WIDTH-1:0] in, 23 | output logic [DATA_WIDTH-1:0] out[NUM_ADDERS] 24 | ); 25 | 26 | logic [DATA_WIDTH-1:0] in_r; 27 | 28 | always_ff @(posedge clk or posedge rst) begin 29 | if (rst) begin 30 | in_r <= '0; 31 | for (int i=0; i < NUM_ADDERS; i++) out[i] <= '0; 32 | end 33 | else begin 34 | in_r <= in; 35 | for (int i=0; i < NUM_ADDERS; i++) out[i] <= in_r + DATA_WIDTH'(i); 36 | end 37 | end 38 | endmodule 39 | 40 | 41 | // Module: multiple_add 42 | // Description: A top-level module illustrating a subtle problem that can 43 | // cause the entire design to be optimized away without errors or warnings. 44 | 45 | module multiple_add 46 | #( 47 | parameter int DATA_WIDTH=16, 48 | parameter int NUM_ADDERS=8 49 | ) 50 | ( 51 | input logic clk, 52 | input logic rst, 53 | input logic [DATA_WIDTH-1:0] in, 54 | // Here we make the mistake of specifying out as an input. Ideally, this 55 | // would cause an error, but we will see that it doesn't. 56 | input logic [DATA_WIDTH-1:0] out[NUM_ADDERS] 57 | ); 58 | 59 | // Here we simply instantiate another module with the same parameters and 60 | // port to do the actual work. 61 | // What happens here is that the input out from the current module gets 62 | // connected to the output out from the instanitated module, which doesn't 63 | // make any sense. I haveen't tested other synthesis tools, but Quartus 64 | // lets us do this, and then since there aren't any actual outputs, the 65 | // entire design gets optimized away. 66 | // 67 | // GOTCHA: you can accidentally drive an input internally through a port 68 | // mapping. 69 | 70 | multiple_add2 #(.DATA_WIDTH(DATA_WIDTH), .NUM_ADDERS(NUM_ADDERS)) top (.*); 71 | 72 | // We get the same problem if we specify it explicitly. 73 | //multiple_add2 #(.DATA_WIDTH(DATA_WIDTH), .NUM_ADDERS(NUM_ADDERS)) top (.out(out), .*); 74 | 75 | // Interestingly, if we try to assign something to an input, we get errors, 76 | // so, the gotcha is when you accidentally port map an output onto an input. 77 | endmodule 78 | -------------------------------------------------------------------------------- /gotchas/mux4x1.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // Description: This file illustrates a gotcha caused by SV allowing illegal 5 | // indexing into unpacked arrays. There is actually a double gotcha in this 6 | // example, because despite there being an obvious error in the code, the 7 | // testbench does not report any errors. See the corresponding testbench for 8 | // an explanation. 9 | // 10 | 11 | // Module: mux4x1 12 | // Description: A purposely incorrect 4:1 mux that demonstrates an SV gotcha. 13 | // Although this example is somewhat synthetic, it is repesentative of what 14 | // can easily happen in large designs. 15 | 16 | module mux4x1 17 | #( 18 | parameter WIDTH=8 19 | ) 20 | ( 21 | input logic [WIDTH-1:0] inputs[4], 22 | input logic [1:0] sel, 23 | output logic [WIDTH-1:0] out 24 | ); 25 | 26 | always_comb begin 27 | case (sel) 28 | 2'b00 : out = inputs[0]; 29 | 2'b01 : out = inputs[1]; 30 | 2'b10 : out = inputs[2]; 31 | 32 | // Here we make a typo to illustrate the problem of using an invalid 33 | // index into an unpacked array. 34 | 2'b11 : out = inputs[4]; 35 | 36 | // Surprinsgly, this will compile in Modelsim without any warnings. 37 | // Fortunately, Quartus will catch the problem and report an error, but 38 | // unfortunately the SV standard does not require it to be an error, 39 | // or even a warning: 40 | // 41 | // "If an index expression is out of bounds or if any bit in the index 42 | // expression is x or z, then the index shall be invalid. Reading from 43 | // an unpacked array of any kind with an invalid index shall return the 44 | // value specified in Table 7-1. Writing to an array with an invalid 45 | // index shall perform no operation, with the exceptions of writing to 46 | // element [$+1] of a queue (described in 7.10.1) and creating a new 47 | // element of an associative array (described in 7.8.6). 48 | // Implementations may issue a warning if an invalid index occurs for 49 | // a read or write operation on an array." 50 | // 51 | // So, the standard recommends a warning, but that's it. The only 52 | // required behavior is for the compiler to return a default value 53 | // when reading from an invalid index. Similarly, writing to an array 54 | // with an invalid index simply gets ignored in most cases. 55 | // 56 | // For more details, here is an excellent analysis on this gotcha: 57 | // 58 | // https://www.amiq.com/consulting/2016/01/26/gotcha-access-an-out-of-bounds-index-for-a-systemverilog-fixed-size-array/ 59 | 60 | endcase 61 | end 62 | endmodule 63 | -------------------------------------------------------------------------------- /gotchas/mux4x1_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // This file provides a testbench for the gotcha in the mux4x1 module, while 5 | // also demonstrating a gotcha that can occur in testbenches. 6 | // 7 | // Note that when running this testbench in the provided form, there will be 8 | // no errors reported, which is the gotcha. If you look at the waveform, there 9 | // are clearly errors. Read below for the explanation and fix. 10 | 11 | `timescale 1 ns / 100 ps 12 | 13 | // Module: mux4x1_tb 14 | // Description: Testbench for the mux4x1 module. 15 | 16 | module mux4x1_tb; 17 | 18 | localparam WIDTH = 8; 19 | localparam NUM_TESTS = 100; 20 | logic [WIDTH-1:0] inputs[4]; 21 | logic [1:0] sel; 22 | logic [WIDTH-1:0] out; 23 | 24 | mux4x1 #(.WIDTH(WIDTH)) DUT (.*); 25 | 26 | initial begin 27 | $timeformat(-9, 0, " ns"); 28 | 29 | for (int i=0; i < NUM_TESTS; i++) begin 30 | for (int j=0; j < 4; j++) begin 31 | inputs[j] = $random; 32 | end 33 | sel = $random; 34 | #10; 35 | 36 | // The following doesn't catch any of the errors. To see there are 37 | // errors, look at the waveform and see that whenever sel == 2'b00 38 | // the output is all Xs. Clearly inputs[sel] is never equal to all Xs 39 | // so why is this condition reporting that they are in fact equal? 40 | // 41 | // GOTCHA: there are multiple comparison operators in SV. 42 | // == and != will return Xs if there are Xs in either input to the 43 | // comparison. In a boolean condition, an X will be treated as 0, or 44 | // false. Therefore, despite the fact that the values being compared 45 | // are not equal, this condition will actually be false. 46 | if (out != inputs[sel]) 47 | $display("ERROR (time %0t): out = %b instead of %b.", $realtime, out, inputs[sel]); 48 | 49 | // To fix the issue, we need to use the === or !== comparison 50 | // operators. These comparison will do a literal comparision, so Xs 51 | // will be treated as a unique value. For example, comparing Xs will 52 | // be true, but comparing Xs and anything else will be false. 53 | // We can therefore fix the testbench by just doing the != as an !==. 54 | // Uncomment the following to see the printing of the errors. 55 | // 56 | //if (out !== inputs[sel]) 57 | // $display("ERROR (time %0t): out = %b instead of %b.", $realtime, out, inputs[sel]); 58 | end 59 | end 60 | endmodule // mux4x1_tb 61 | 62 | 63 | -------------------------------------------------------------------------------- /gotchas/ram.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // TODO: Add testbench to see how Modelsim handles these examples. Also, check 5 | // synthesis results in Quartus to see how the RAM is getting synthesized. 6 | 7 | // Module: ram 8 | // Description: This module implements a problematic RAM to demonstrate a 9 | // gotcha with invalid array indexing. 10 | 11 | module ram 12 | ( 13 | input logic clk, 14 | input logic [7:0] rd_addr, 15 | output logic [15:0] rd_data, 16 | 17 | input logic wr_en, 18 | input logic [7:0] wr_addr, 19 | input logic [15:0] wr_data 20 | ); 21 | 22 | // The problem is that the RAM only has 64 words of storage. However, the 23 | // addresses are 8 bits, which corresponds to 256 words. Although it is not 24 | // uncommon for a RAM to not fill up the entire address space of a system, 25 | // it would be really strange to purposely create a RAM with fewer words 26 | // than the address lines would support. 27 | // 28 | // Since such an occurrence would almost always be a design error, ideally 29 | // the compiler would tell us if we tried to access this array outside its 30 | // bounds. 31 | logic [15:0] ram[64]; 32 | 33 | always_ff @(posedge clk) begin 34 | // GOTCHA: Here we are accessing the ram array using an index that 35 | // potentially exceeds the ram bounds. However, when we compile, we get 36 | // no errors. While some tools might report a warning, Quartus did not in 37 | // our tests. 38 | // 39 | // What ends up happening here is that the address is simply truncated to 40 | // fit within the bounds of the ram array. Although there might be very 41 | // rare situations where we would want that, the vast majority of the time 42 | // this would likely be accidental. Since there isn't even a warning, we 43 | // would have to debug this in simulation in the best case. 44 | rd_data <= ram[rd_addr]; 45 | 46 | // We have a similar issue here. In fact, this could potentially be worse 47 | // be writes to addresses >= 64 would overwrite data in the RAM, which 48 | // could be difficult to debug. 49 | if (wr_en) 50 | ram[wr_addr] <= wr_data; 51 | end 52 | 53 | endmodule 54 | 55 | 56 | // Module: ram2 57 | // Description: A similar ram that demonstrates a similar problem where 58 | // the RAM has more words than can be accessed by a corresponding address. 59 | // Like before, Quartus reports no warnings. 60 | 61 | module ram2 62 | ( 63 | input logic clk, 64 | input logic [7:0] rd_addr, 65 | output logic [15:0] rd_data, 66 | 67 | input logic wr_en, 68 | input logic [7:0] wr_addr, 69 | input logic [15:0] wr_data 70 | ); 71 | 72 | // Here, we make the RAM 1024 words. With an 8-bit address, most of this 73 | // RAM is inaccessible. However, Quartus reports no warnings. 74 | logic [15:0] ram[1024]; 75 | 76 | always_ff @(posedge clk) begin 77 | rd_data <= ram[rd_addr]; 78 | if (wr_en) 79 | ram[wr_addr] <= wr_data; 80 | end 81 | 82 | endmodule 83 | -------------------------------------------------------------------------------- /gotchas/structure.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // This file demonstrates gotchas related to width mismatches in structural 5 | // architectures. 6 | 7 | module add 8 | #(parameter WIDTH) 9 | ( 10 | input logic [WIDTH-1:0] in0, in1, 11 | output logic [WIDTH-1:0] sum 12 | ); 13 | 14 | assign sum = in0 + in1; 15 | endmodule // add 16 | 17 | 18 | // Module: structure 19 | // Description: A synthetic module to demonstrate what happens with width 20 | // mismatches in structure architectures. 21 | 22 | module structure 23 | ( 24 | input logic [31:0] in0, in1, 25 | output logic [31:0] result1, result2, result3 26 | ); 27 | 28 | logic [15:0] sum1; 29 | logic [63:0] sum2; 30 | logic [63:0] sum3; 31 | 32 | // Actual parameters with larger inputs (32 bits -> 16 bits) 33 | // This simply truncates the inputs, but will likely not give a simulation 34 | // warning. Synthesis will likely give a warning. 35 | add #(.WIDTH(16)) add1 (.in0(in0), .in1(in1), .sum(sum1)); 36 | 37 | // Actual parameters with smaller inputs (32 bits -> 64 bits) 38 | // In Modelsim, the upper 32 bits of the inputs are Z because they are 39 | // essentially disconnected. As a result, the sum is undefined. However, 40 | // synthesis tools report just a warning, so the behavior is going to differ 41 | // between simulation and synthesis, which is what we want to avoid at all 42 | // costs. 43 | add #(.WIDTH(64)) add2 (.in0(in0), .in1(in1), .sum(sum2)); 44 | 45 | // Actual output parameter with more bits (64 bits -> 32 bits) 46 | // Unlike when then formal parameter has more bits and the extra bits are 47 | // disconnected, in this case, the extra bits in the actual parameter are 48 | // extended. 49 | add #(.WIDTH(32)) add3 (.in0(in0), .in1(in1), .sum(sum3)); 50 | 51 | assign result1 = sum1; 52 | assign result2 = sum2; 53 | assign result3 = sum3; 54 | 55 | endmodule 56 | 57 | 58 | // Module: structure2 59 | // Description: A small modification to the previous module that uses the 60 | // wildcard connections. Interestingly, when you start the simulation, 61 | // Modelsim reports errors in this case instead of warnings, which in my 62 | // opinion is the preferred behavior. 63 | // 64 | // My guess is this is actually defined by the standard, where they are 65 | // assuming that if you explicitly connect a specific signal, you are aware of 66 | // the width difference. Similarly, if you are using a wildcare, it is looking 67 | // for a signal with the exact same name and width. TODO: Confirm this is how 68 | // the standard defines it. 69 | 70 | module structure2 71 | ( 72 | input logic [31:0] in0, in1, 73 | output logic [31:0] result1, result2, result3 74 | ); 75 | 76 | logic [15:0] sum1; 77 | logic [63:0] sum2; 78 | logic [63:0] sum3; 79 | 80 | add #(.WIDTH(16)) add1 (.sum(sum1), .*); 81 | add #(.WIDTH(64)) add2 (.sum(sum2), .*); 82 | add #(.WIDTH(32)) add3 (.sum(sum3), .*); 83 | 84 | assign result1 = sum1; 85 | assign result2 = sum2; 86 | assign result3 = sum3; 87 | 88 | endmodule 89 | -------------------------------------------------------------------------------- /gotchas/structure_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 10 ps 5 | 6 | // Module: structure_tb 7 | // Description: A super basic testbench to provide stimuli to the structure 8 | // module. This testbench does not check for errors. It is intended to be 9 | // used to generate a waveform to see how the width mismatches behave. 10 | 11 | module structure_tb; 12 | 13 | logic [31:0] in0, in1; 14 | logic [31:0] result1, result2, result3; 15 | 16 | structure DUT (.*); 17 | 18 | initial begin 19 | for (int i=0; i < 100; i++) begin 20 | in0 = $random; 21 | in1 = $random; 22 | #10; 23 | end 24 | end 25 | endmodule 26 | -------------------------------------------------------------------------------- /ram/README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This directory contains a number of simple dual-port (SDP) and true dual-port (TDP) inference templates 4 | that work on most FPGAs from Xilinx/AMD and Intel/Altera. 5 | 6 | Note that the code as provided will only work in Quartus Prime Pro. You can extend it to 7 | work in other Quartus versions, but those versions don't have great support for SystemVerilog, 8 | and I didn't want to write all the code for the tool with the weakest support. 9 | 10 | # Suggested Study Order 11 | 12 | 1. [ram_sdp](ram_sdp.sv) 13 | - Illustrates a variety of single dual-port inference templates with different features. 14 | - Demonstrates a generalized, configurable template that combines these features. 15 | - Includes Vivado and Quartus-specialized templates. 16 | - Includes a [testbench](ram_sdp_tb.sv) for the generalized template. 17 | 18 | 1. [ram_sdp_with_reset_vivado](ram_sdp_with_reset_vivado.sv) 19 | - Illustrates how to modify the Vivado template with reset for some of the optional registers. 20 | - Explains the risks of using resets improperly in RAM templates. 21 | 22 | 1. [ram_tdp](ram_tdp.sv) 23 | - Explains a generalized true dual-port RAM inference template. 24 | 25 | 1. [ram_tdp_quartus](ram_tdp_quartus.sv) 26 | - Demonstrates how to specialize the TDP template for Quartus Prime Pro and Intel/Altera FPGAs. 27 | 28 | 1. [ram_tdp_vivado](ram_tdp_vivado.sv) 29 | - Demonstrates how to specialize the TDP template for Vivado and AMD/Xilinx FPGAs. -------------------------------------------------------------------------------- /ram/ram_tdp.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // StittHub (www.stitt-hub.com) 3 | 4 | // The following shows a somewhat reusable true dual-port (TDP) RAM template. 5 | // It works on some Xilinx/AMD and Intel/Altera FPGAs, as long as you don't need 6 | // to specify the specific RAM resource, which I add in other examples. 7 | // 8 | // The tricky part of creating a reusable TDP template is that different FPGAs, 9 | // even those from the same vendor, and even those within the same FPGA, can 10 | // provide variations of behavior. In my attempt at a unified template, I 11 | // explored the commonality across most FPGAs. 12 | // 13 | // It is very important to understand the exact TDP behaviors of your specific 14 | // FPGA. Some FPGAs prohibit certain actions (e.g., reading from one port and 15 | // writing to another port using the same address). If you don't comply with 16 | // those requirements, you can get undefined behaviors. As added protection, I 17 | // usually supplement this template with assertions that check for prohibited 18 | // behaviors on my targeted FPGA. 19 | // 20 | // In addition, different RAM resources might provide unique behaviors that are 21 | // ommitted from this template, but can be quite useful. 22 | // 23 | // Note that there is no WRITE_FIRST parameter here. I omitteed it because of 24 | // wide differences of read-during-write behaviors on different FPGAs. 25 | // 26 | // Note: This has not been tested in the non-pro versions of Quartus. 27 | // SystemVerilog support is not great in those versions, so I have abandonded 28 | // trying to support it. 29 | 30 | module ram_tdp #( 31 | parameter int DATA_WIDTH = 4, 32 | parameter int ADDR_WIDTH = 8, 33 | parameter bit REG_RD_DATA = 1'b1 34 | ) ( 35 | input logic clk, 36 | 37 | // Port A 38 | input logic en_a, 39 | input logic wr_en_a, 40 | input logic [ADDR_WIDTH-1:0] addr_a, 41 | input logic [DATA_WIDTH-1:0] wr_data_a, 42 | output logic [DATA_WIDTH-1:0] rd_data_a, 43 | 44 | // Port B 45 | input logic en_b, 46 | input logic wr_en_b, 47 | input logic [ADDR_WIDTH-1:0] addr_b, 48 | input logic [DATA_WIDTH-1:0] wr_data_b, 49 | output logic [DATA_WIDTH-1:0] rd_data_b 50 | ); 51 | logic [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH]; 52 | logic [DATA_WIDTH-1:0] rd_data_ram_a, rd_data_ram_b; 53 | 54 | always @(posedge clk) begin 55 | if (en_a) begin 56 | if (wr_en_a) ram[addr_a] <= wr_data_a; 57 | else rd_data_ram_a <= ram[addr_a]; 58 | end 59 | end 60 | 61 | always @(posedge clk) begin 62 | if (en_b) begin 63 | if (wr_en_b) ram[addr_b] <= wr_data_b; 64 | else rd_data_ram_b <= ram[addr_b]; 65 | end 66 | end 67 | 68 | if (REG_RD_DATA) begin : l_reg_rd_data 69 | always_ff @(posedge clk) begin 70 | if (en_a) rd_data_a <= rd_data_ram_a; 71 | if (en_b) rd_data_b <= rd_data_ram_b; 72 | end 73 | end else begin : l_no_reg_rd_data 74 | assign rd_data_a = rd_data_ram_a; 75 | assign rd_data_b = rd_data_ram_b; 76 | end 77 | endmodule 78 | -------------------------------------------------------------------------------- /ram/ram_tdp_quartus.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // StittHub (www.stitt-hub.com) 3 | 4 | // Note: This has not been tested in the non-pro versions of Quartus. 5 | // SystemVerilog support is not great in those versions, so I have abandonded 6 | // trying to support it. 7 | 8 | module ram_tdp_quartus #( 9 | parameter int DATA_WIDTH = 4, 10 | parameter int ADDR_WIDTH = 8, 11 | parameter bit REG_RD_DATA = 1'b1, 12 | parameter string STYLE = "" 13 | ) ( 14 | input logic clk, 15 | 16 | // Port A 17 | input logic en_a, 18 | input logic wr_en_a, 19 | input logic [ADDR_WIDTH-1:0] addr_a, 20 | input logic [DATA_WIDTH-1:0] wr_data_a, 21 | output logic [DATA_WIDTH-1:0] rd_data_a, 22 | 23 | // Port B 24 | input logic en_b, 25 | input logic wr_en_b, 26 | input logic [ADDR_WIDTH-1:0] addr_b, 27 | input logic [DATA_WIDTH-1:0] wr_data_b, 28 | output logic [DATA_WIDTH-1:0] rd_data_b 29 | ); 30 | (* ramstyle = STYLE *) logic [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH]; 31 | logic [DATA_WIDTH-1:0] rd_data_ram_a, rd_data_ram_b; 32 | 33 | always @(posedge clk) begin 34 | if (en_a) begin 35 | if (wr_en_a) ram[addr_a] <= wr_data_a; 36 | else rd_data_ram_a <= ram[addr_a]; 37 | end 38 | end 39 | 40 | always @(posedge clk) begin 41 | if (en_b) begin 42 | if (wr_en_b) ram[addr_b] <= wr_data_b; 43 | else rd_data_ram_b <= ram[addr_b]; 44 | end 45 | end 46 | 47 | if (REG_RD_DATA) begin : l_reg_rd_data 48 | always_ff @(posedge clk) begin 49 | if (en_a) rd_data_a <= rd_data_ram_a; 50 | if (en_b) rd_data_b <= rd_data_ram_b; 51 | end 52 | end else begin : l_no_reg_rd_data 53 | assign rd_data_a = rd_data_ram_a; 54 | assign rd_data_b = rd_data_ram_b; 55 | end 56 | endmodule 57 | -------------------------------------------------------------------------------- /ram/ram_tdp_vivado.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // StittHub (www.stitt-hub.com) 3 | 4 | module ram_tdp_vivado #( 5 | parameter int DATA_WIDTH = 4, 6 | parameter int ADDR_WIDTH = 8, 7 | parameter bit REG_RD_DATA = 1'b1, 8 | parameter string STYLE = "" 9 | ) ( 10 | input logic clk, 11 | 12 | // Port A 13 | input logic en_a, 14 | input logic wr_en_a, 15 | input logic [ADDR_WIDTH-1:0] addr_a, 16 | input logic [DATA_WIDTH-1:0] wr_data_a, 17 | output logic [DATA_WIDTH-1:0] rd_data_a, 18 | 19 | // Port B 20 | input logic en_b, 21 | input logic wr_en_b, 22 | input logic [ADDR_WIDTH-1:0] addr_b, 23 | input logic [DATA_WIDTH-1:0] wr_data_b, 24 | output logic [DATA_WIDTH-1:0] rd_data_b 25 | ); 26 | if (STYLE == "block") begin : l_ram 27 | (* ram_style = "block" *) logic [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH]; 28 | end else if (STYLE == "ultra") begin : l_ram 29 | (* ram_style = "ultra" *) logic [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH]; 30 | end else if (STYLE == "mixed") begin : l_ram 31 | (* ram_style = "mixed" *) logic [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH]; 32 | end else if (STYLE == "auto") begin : l_ram 33 | (* ram_style = "auto" *) logic [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH]; 34 | end else if (STYLE == "") begin : l_ram 35 | logic [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH]; 36 | end else begin : l_ram 37 | initial begin 38 | $fatal(1, "Invalid STYLE value %s", STYLE); 39 | end 40 | end 41 | 42 | logic [DATA_WIDTH-1:0] rd_data_ram_a, rd_data_ram_b; 43 | 44 | // BlockRAM can use different clocks on each port, but UltraRAM can't so 45 | // we use a single clock to support both. 46 | always @(posedge clk) begin 47 | if (en_a) begin 48 | if (wr_en_a) l_ram.ram[addr_a] <= wr_data_a; 49 | else rd_data_ram_a <= l_ram.ram[addr_a]; 50 | end 51 | end 52 | 53 | always @(posedge clk) begin 54 | if (en_b) begin 55 | if (wr_en_b) l_ram.ram[addr_b] <= wr_data_b; 56 | else rd_data_ram_b <= l_ram.ram[addr_b]; 57 | end 58 | end 59 | 60 | if (REG_RD_DATA) begin : l_reg_rd_data 61 | always_ff @(posedge clk) begin 62 | if (en_a) rd_data_a <= rd_data_ram_a; 63 | if (en_b) rd_data_b <= rd_data_ram_b; 64 | end 65 | end else begin : l_no_reg_rd_data 66 | assign rd_data_a = rd_data_ram_a; 67 | assign rd_data_b = rd_data_ram_b; 68 | end 69 | endmodule 70 | -------------------------------------------------------------------------------- /sequential/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This directory provides a tutorial on how to create behavioral descriptions of sequential logic. Technically, sequential logic only includes flip-flops and registers 4 | (possibly latches in rare case or for ASICs), so the tutorial actually shows how to model circuits that are a combination of both sequential logic and 5 | combinational logic. The ultimate purpose of this tutorial is to understand how registers get synthesized, and where they get placed in relation to other logic. One of the most 6 | common mistakes when writing RTL is accidentally introducing an incorrect number of registers. 7 | 8 | # Methodology: design the circuit, then write the code. 9 | 10 | For circuits with sequential logic, designing the circuit means deciding exactly how many registers you want, and what those registers should be connected to. Although synthesis 11 | optimizations may change this some (e.g. via retiming), use of registers is a critical design decision because it affects the timing of your design, which is something RTL 12 | synthesis cannot change. Similar to structural architectures, "designing the circuit" for sequential logic usually means creating a schematic that illustrates the exact number 13 | and placement of all registers. With this schematic, you can easily apply the guidelines given below to ensure your design synthesizes as intended. 14 | 15 | # Suggested Study Order 16 | 17 | 1. [Register](register.sv) 18 | - Illustrates how to create an asynchronous reset, a synchronous reset, an enable/load, and a highly parameterized register with different reset types and activiation levels. 19 | 1. [Examples of Synthesizing Behavioral Code to a Specific Structure](seq_example.sv) 20 | - See [architectures.pdf](architectures.pdf) for different example circuits. Each one has a corresponding module in [seq_example.sv](seq_example.sv). 21 | - Illustrates common mistakes with sequential logic. 22 | - Goes over the use of non-blocking assignments and blocking assignments to accomplish different goals. 23 | - Suggestion: synthesize each module and use an RTL viewer to ensure the schematic matches the architecture in the pdf. There are no provided testbenches for these modules. 24 | 1. [Delay (TBD)]() 25 | 26 | 27 | -------------------------------------------------------------------------------- /sequential/architectures.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/sequential/architectures.pdf -------------------------------------------------------------------------------- /sequential/architectures.vsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/sequential/architectures.vsd -------------------------------------------------------------------------------- /sequential/register_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 10 ps 5 | 6 | // Module: register_tb 7 | 8 | module register_tb #( 9 | parameter int NUM_TESTS = 10000, 10 | parameter int WIDTH = 8 11 | ); 12 | 13 | logic clk, rst, en; 14 | logic [WIDTH-1:0] in, out; 15 | 16 | register #(.WIDTH(WIDTH)) DUT (.*); 17 | 18 | initial begin : generate_clock 19 | clk <= 1'b0; 20 | forever #5 clk <= ~clk; 21 | end 22 | 23 | initial begin : drive_inputs 24 | $timeformat(-9, 0, " ns"); 25 | 26 | rst <= 1'b1; 27 | in <= 1'b0; 28 | en <= 1'b0; 29 | 30 | repeat (5) @(posedge clk); 31 | 32 | rst <= 1'b0; 33 | 34 | for (int i = 0; i < NUM_TESTS; i++) begin 35 | in <= $urandom; 36 | if (DUT.USE_ENABLE) en <= $urandom; 37 | else en <= 1'b1; 38 | @(posedge clk); 39 | end 40 | 41 | disable generate_clock; 42 | $display("Tests completed."); 43 | end 44 | 45 | assert property (@(posedge clk) disable iff (rst) en |=> out == $past(in, 1)); 46 | assert property (@(posedge clk) disable iff (rst) !en |=> $stable(out)); 47 | assert property (@(posedge clk) rst |=> out == '0); 48 | if (DUT.USE_ASYNC_RST) always @(rst) #1 assert (out == 1'b0); 49 | endmodule 50 | -------------------------------------------------------------------------------- /structural/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This directory provides a tutorial on how to create structural descriptions, which are generally just code representations of a schematic. A schematic is just an interconnection 4 | of existing components, which can be any type of logic, and any level of granularity. 5 | 6 | # Methodology: design the circuit, then write the code. 7 | 8 | For structural descriptions, designing the circuit means creating the schematic. For every component in the schematic, you will simply instantiate an existing module 9 | (or create one if necessary), and then connect them together as shown in the schematic. The primary creativity in structural descriptions is identifying patterns (or exceptions) 10 | in the structure that can be described with generate constructs, as the examples will show. 11 | 12 | # Suggested Study Order 13 | 14 | 1. [4:1 mux](mux4x1.sv) 15 | - Illustrates the basic techniques for converting a schematic (in this case [mux4x1.pdf](mux4x1.pdf)) into SystemVerilog code. 16 | 1. [Ripple-Carry Adder](ripple_carry_adder.sv) 17 | - Introduces parameters and the for-generate construct. 18 | - See the schematic [ripple_carry_adder.pdf](ripple_carry_adder.pdf) for reference. 19 | - ***Important point:*** Use the "for generate" statement anytime that there is a pattern in a structural description. This construct will allow you to specify very large structures with very little code. 20 | 1. [Delay](delay.sv) 21 | - Introduces unpacked arrays, if generate, and parameter validation techniques. 22 | - See the schematic [delay.pdf](delay.pdf) for reference. 23 | 1. Recursive Architectures (advanced) 24 | - [Adder Tree Explanation](https://stitt-hub.com/you-can-and-should-write-recursive-rtl-code/) 25 | - [Optimized Adder Tree](https://stitt-hub.com/you-can-and-should-write-recursive-rtl-part-2/) 26 | -------------------------------------------------------------------------------- /structural/delay.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/structural/delay.docx -------------------------------------------------------------------------------- /structural/delay.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/structural/delay.pdf -------------------------------------------------------------------------------- /structural/delay_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 100 ps 5 | 6 | // Module: delay_tb 7 | // Description: Testbench for the delay entity. This is a more complicated 8 | // testbench that preserves the correct outputs in an array. 9 | 10 | module delay_tb #( 11 | parameter int NUM_TESTS = 1000, 12 | parameter int CYCLES = 3, 13 | parameter int WIDTH = 8 14 | ); 15 | 16 | logic clk = 1'b0; 17 | logic rst; 18 | logic en; 19 | logic [WIDTH-1:0] in; 20 | logic [WIDTH-1:0] out; 21 | 22 | delay #( 23 | .CYCLES(CYCLES), 24 | .WIDTH (WIDTH) 25 | ) DUT ( 26 | .* 27 | ); 28 | 29 | initial begin : generate_clock 30 | forever #5 clk = ~clk; 31 | end 32 | 33 | initial begin 34 | $timeformat(-9, 0, " ns"); 35 | 36 | // Initialize the circuit. 37 | rst <= 1'b1; 38 | en <= 1'b0; 39 | in <= '0; 40 | repeat (5) @(posedge clk); 41 | 42 | rst <= 1'b0; 43 | 44 | // Genereate NUM_TESTS random tests. 45 | for (int i = 0; i < NUM_TESTS; i++) begin 46 | in <= $urandom; 47 | en <= $urandom; 48 | @(posedge clk); 49 | end 50 | 51 | disable generate_clock; 52 | $display("Tests completed."); 53 | end 54 | 55 | assert property (@(posedge clk) disable iff (rst) en [-> CYCLES] |=> out == $past(in, CYCLES, en)); 56 | assert property (@(posedge clk) !en |=> $stable(out)); 57 | assert property (@(posedge clk) rst |=> out == '0); 58 | 59 | endmodule // delay_tb 60 | -------------------------------------------------------------------------------- /structural/mux4x1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/structural/mux4x1.pdf -------------------------------------------------------------------------------- /structural/mux4x1.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/structural/mux4x1.pptx -------------------------------------------------------------------------------- /structural/mux4x1.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // This file illustrates how to create a basic structural architecture by 5 | // combining three 2x1 muxes to create a 4x1 mux. 6 | // 7 | // For any structural architecture, the most critical first step is to draw 8 | // out a schematic of the architecture in terms of modules that have already 9 | // been defined. The structural description is then simply a text representation 10 | // of that schematic. 11 | // 12 | // See mux4x1.pdf for the corresponding schematic. 13 | 14 | 15 | // Module: mux2x1 16 | // Description: A basic 2x1 mux. We'll be using this to create a structural 17 | // 4x1 mux. 18 | 19 | module mux2x1 ( 20 | input logic in0, 21 | input logic in1, 22 | input logic sel, 23 | output logic out 24 | ); 25 | 26 | assign out = sel == 1'b1 ? in1 : in0; 27 | 28 | endmodule // mux2x1 29 | 30 | 31 | // Module: mux4x1 32 | // Description: A structural implementation of a 4x1 mux using 3 separate 2x1 33 | // muxes. See mux4x1.pdf for a schematic of the architecture. 34 | 35 | module mux4x1 ( 36 | input logic [3:0] inputs, 37 | input logic [1:0] sel, 38 | output logic out 39 | ); 40 | // Create internal signals for the connections between modules. Technically 41 | // this isn't needed because any signal that appears in an instantiation 42 | // will be automatically delcared with a width of 1 bit. However, I 43 | // strongly recommend against relying on that functionality and instead 44 | // just declare all signals. See the gotchas section to see why this can 45 | // be so problematic. 46 | logic mux1_out, mux2_out; 47 | 48 | // Instantiate the three muxes from the schematic and connect them together 49 | // as shown in schematic. 50 | // 51 | // Grammar description: 52 | // The first word is the name of the module being instantianted (mux2x1) 53 | // The second word is a label for the particular instance. I chose labels 54 | // that match the schematic, but you can choose any meaningful name. 55 | // The list in parantheses is the list of I/O connections. 56 | // 57 | // The I/O connections can be specified by order, or by name. Order is not 58 | // recommended because that order might change, or it might be easy to mess 59 | // up the order for large amounts of I/O. 60 | // 61 | // Specifying ports by name is less concise, but much less error prone. 62 | // The name of the port for the module is specified with a . prefix. 63 | // The name of the signal connected to the port is specified in parantheses. 64 | // Ports can be left open with empty parantheses. 65 | // 66 | // IMPORTANT: if the port name matches the signal name, you can use a 67 | // wildcard to automatically establish connections. e.g. .* 68 | // Most people recommend against using .* as it hides the interface. 69 | // I only use it my own testbenches where I always follow a convention 70 | // that names local variables the same as the DUT I/O. This allows me to 71 | // use .* to quickly set up my testbench. However, I wouldn't use it for 72 | // code that other might need to modify because they might not be aware of 73 | // my convention. 74 | 75 | mux2x1 MUX1 ( 76 | .in1(inputs[3]), 77 | .in0(inputs[2]), 78 | .sel(sel[0]), 79 | .out(mux1_out) 80 | ); 81 | mux2x1 MUX2 ( 82 | .in1(inputs[1]), 83 | .in0(inputs[0]), 84 | .sel(sel[0]), 85 | .out(mux2_out) 86 | ); 87 | mux2x1 MUX3 ( 88 | .in1(mux1_out), 89 | .in0(mux2_out), 90 | .sel(sel[1]), 91 | .out(out) 92 | ); 93 | endmodule 94 | 95 | -------------------------------------------------------------------------------- /structural/mux4x1_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 100 ps 5 | 6 | // Module: mux4x1_tb 7 | // Description: Testbench for the mux4x1 module. 8 | 9 | module mux4x1_tb; 10 | 11 | logic [3:0] inputs; 12 | logic [1:0] sel; 13 | logic out; 14 | 15 | mux4x1 DUT (.*); 16 | 17 | initial begin 18 | $timeformat(-9, 0, " ns"); 19 | 20 | // Iterate over all inputs and select values. 21 | for (int i = 0; i < 2 ** $bits(inputs); i++) begin 22 | inputs <= i; 23 | 24 | for (int j = 0; j < 2 ** $bits(sel); j++) begin 25 | sel <= j; 26 | #10; 27 | if (out != inputs[sel]) 28 | $error("[%0t] out = %b instead of %b.", $realtime, out, inputs[sel]); 29 | end 30 | end 31 | 32 | $display("Tests completed."); 33 | end 34 | endmodule 35 | 36 | -------------------------------------------------------------------------------- /structural/ripple_carry_adder.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/structural/ripple_carry_adder.docx -------------------------------------------------------------------------------- /structural/ripple_carry_adder.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARC-Lab-UF/sv-tutorial/d4ea7d5e1e0953c05f0a4462a912523f32a0a996/structural/ripple_carry_adder.pdf -------------------------------------------------------------------------------- /structural/ripple_carry_adder.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // 4 | // This file demonstrates how to use a loop to generate a structural pattern 5 | // in a circuit. Specifically, it creates a ripple carry adder with a 6 | // parameterized width by instantiating full adders in a loop. 7 | // 8 | // See ripple_carry_adder.pdf for an illustration of the schematic being 9 | // created. Remember that all strucutural architectures should start from a 10 | // schematic. 11 | 12 | 13 | // Module: full_adder 14 | // Description: A basic behavioral implementation of a full adder. 15 | 16 | module full_adder ( 17 | input logic x, 18 | input logic y, 19 | input logic cin, 20 | output logic s, 21 | output logic cout 22 | ); 23 | // Specify the sum and carry out logic equations for a full adder. 24 | assign s = x ^ y ^ cin; 25 | assign cout = (x & y) | (cin & (x ^ y)); 26 | 27 | endmodule // full_adder 28 | 29 | 30 | // Module: ripple_carry_adder 31 | // Description: A structural ripple carry adder with a parameter for width, 32 | // built from the preceding full_adder module. Demonstrates how to use a 33 | // generate statement and for loop. 34 | 35 | module ripple_carry_adder #( 36 | parameter int WIDTH = 8 37 | ) ( 38 | input logic [WIDTH-1:0] x, 39 | input logic [WIDTH-1:0] y, 40 | input logic cin, 41 | output logic [WIDTH-1:0] sum, 42 | output logic cout 43 | ); 44 | // Create an internal signal to store the carries between all full adders. 45 | // Note that this is WIDTH+1 bits to account for the overall carry out. 46 | logic [WIDTH:0] carry; 47 | 48 | // Connect the first carry to the carry in. 49 | assign carry[0] = cin; 50 | 51 | // Instantiate WIDTH separate full adders using a for loop, and connect them 52 | // into a ripple-carry by connecting the carry out from one full adder into 53 | // the carry in of the next. 54 | // 55 | // You can also use an if statement within a generate, but keep in mind that 56 | // the condition must be a function of constants and parameters. No dynamic 57 | // values can be used because the synthesis tool must resolve the condition 58 | // at compile time. 59 | generate 60 | for (genvar i = 0; i < WIDTH; i++) begin : ripple_carry_l 61 | full_adder FA ( 62 | .x (x[i]), 63 | .y (y[i]), 64 | .s (sum[i]), 65 | .cin (carry[i]), 66 | .cout(carry[i+1]) 67 | ); 68 | end 69 | endgenerate 70 | 71 | // Connect the last carry to the carry out. 72 | assign cout = carry[WIDTH]; 73 | 74 | endmodule 75 | 76 | 77 | module ripple_carry_adder2 #( 78 | parameter int WIDTH = 8 79 | ) ( 80 | input logic [WIDTH-1:0] x, 81 | input logic [WIDTH-1:0] y, 82 | input logic cin, 83 | output logic [WIDTH-1:0] sum, 84 | output logic cout 85 | ); 86 | logic [WIDTH:0] carry; 87 | assign carry[0] = cin; 88 | 89 | // The generate statement is actually completely optional, but the for loop 90 | // must use a genvar. Keep in mind that this for loop is not inside an 91 | // always block, so the body can only make continuous assignments or module 92 | // instantiations. 93 | for (genvar i = 0; i < WIDTH; i++) begin : ripple_carry 94 | full_adder FA ( 95 | .x (x[i]), 96 | .y (y[i]), 97 | .s (sum[i]), 98 | .cin (carry[i]), 99 | .cout(carry[i+1]) 100 | ); 101 | end 102 | 103 | assign cout = carry[WIDTH]; 104 | 105 | endmodule 106 | 107 | -------------------------------------------------------------------------------- /structural/ripple_carry_adder_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `timescale 1 ns / 100 ps 5 | 6 | module ripple_carry_adder_tb; 7 | 8 | localparam int NUM_TESTS = 1000; 9 | localparam int WIDTH = 8; 10 | logic [WIDTH-1:0] x, y, sum, correct_sum; 11 | logic cin, cout, correct_cout; 12 | 13 | ripple_carry_adder UUT (.*); 14 | 15 | initial begin 16 | $timeformat(-9, 0, " ns"); 17 | 18 | for (int i = 0; i < NUM_TESTS; i++) begin 19 | x <= $urandom; 20 | y <= $urandom; 21 | cin <= $urandom; 22 | #10; 23 | {correct_cout, correct_sum} = x + y + cin; 24 | if (sum != correct_sum) 25 | $display("ERROR (time %0t): sum = %d instead of %d.", $realtime, sum, correct_sum); 26 | 27 | if (cout != correct_cout) 28 | $display("ERROR (time %0t): cout = %b instead of %b.", $realtime, cout, correct_cout); 29 | end 30 | 31 | $display("Tests completed."); 32 | end 33 | 34 | endmodule // ripple_carry_adder_tb 35 | -------------------------------------------------------------------------------- /testbenches/assertions/delay.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | module delay #( 5 | parameter int CYCLES = 8, 6 | parameter int WIDTH = 16 7 | ) ( 8 | input logic clk, 9 | input logic rst, 10 | input logic en, 11 | input logic [WIDTH-1:0] data_in, 12 | output logic [WIDTH-1:0] data_out 13 | ); 14 | initial begin 15 | if (CYCLES < 0) $fatal(1, "Cycles must be positive."); 16 | if (WIDTH < 1) $fatal(1, "Width must be >= 1"); 17 | end 18 | 19 | generate 20 | if (CYCLES == 0) begin : no_delay_l 21 | assign data_out = data_in; 22 | end else begin : delay_l 23 | logic [WIDTH-1:0] delay_r[CYCLES]; 24 | 25 | always_ff @(posedge clk or posedge rst) begin 26 | if (rst) begin 27 | delay_r <= '{default: '0}; 28 | end else if (en) begin 29 | delay_r[0] <= data_in; 30 | for (int i = 1; i < CYCLES; i++) delay_r[i] <= delay_r[i-1]; 31 | end 32 | end 33 | 34 | assign data_out = delay_r[CYCLES-1]; 35 | end 36 | endgenerate 37 | endmodule 38 | -------------------------------------------------------------------------------- /testbenches/assertions/ff.sv: -------------------------------------------------------------------------------- 1 | module ff ( 2 | input logic clk, 3 | input logic rst, 4 | input logic en, 5 | input logic in, 6 | output logic out 7 | ); 8 | always_ff @(posedge clk or posedge rst) 9 | if (rst) out <= 1'b0; 10 | else if (en) out <= in; 11 | endmodule 12 | -------------------------------------------------------------------------------- /testbenches/assertions/fifo.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // This file provides a basic FIFO module with a 1-cycle read latency. Note that 4 | // it includes assertions at the bottom of the module. 5 | 6 | module fifo #( 7 | parameter int WIDTH = 16, 8 | parameter int DEPTH = 32 9 | ) ( 10 | input logic clk, 11 | input logic rst, 12 | output logic full, 13 | input logic wr_en, 14 | input logic [WIDTH-1:0] wr_data, 15 | output logic empty, 16 | input logic rd_en, 17 | output logic [WIDTH-1:0] rd_data 18 | ); 19 | logic [WIDTH-1:0] ram[DEPTH]; 20 | logic valid_wr, valid_rd; 21 | 22 | localparam int ADDR_WIDTH = $clog2(DEPTH) + 1; 23 | logic [ADDR_WIDTH-1:0] wr_addr_r, rd_addr_r; 24 | 25 | always_ff @(posedge clk) begin 26 | if (valid_wr) ram[wr_addr_r[ADDR_WIDTH-2:0]] <= wr_data; 27 | rd_data <= ram[rd_addr_r[ADDR_WIDTH-2:0]]; 28 | end 29 | 30 | always_ff @(posedge clk or posedge rst) begin 31 | if (rst) begin 32 | rd_addr_r <= '0; 33 | wr_addr_r <= '0; 34 | end else begin 35 | if (valid_wr) wr_addr_r <= wr_addr_r + 1'b1; 36 | if (valid_rd) rd_addr_r <= rd_addr_r + 1'b1; 37 | end 38 | end 39 | 40 | assign valid_wr = wr_en && !full; 41 | assign valid_rd = rd_en && !empty; 42 | 43 | assign full = rd_addr_r[ADDR_WIDTH-2:0] == wr_addr_r[ADDR_WIDTH-2:0] && rd_addr_r[ADDR_WIDTH-1] != wr_addr_r[ADDR_WIDTH-1]; 44 | assign empty = rd_addr_r == wr_addr_r; 45 | 46 | // For important properties of a module, it is often a good idea to include 47 | // assertions in the synthesizable code. These will be ignored by synthesis 48 | // tools, but will still work in simulation. Note that some synthesis tools 49 | // might require an attribute or annotation to ignore this. 50 | assert property (@(posedge clk) valid_wr |-> !full); 51 | assert property (@(posedge clk) valid_rd |-> !empty); 52 | 53 | endmodule 54 | -------------------------------------------------------------------------------- /testbenches/assertions/mux2x1.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | module mux2x1_assign ( 5 | input logic in0, 6 | input logic in1, 7 | input logic sel, 8 | output logic out 9 | ); 10 | assign out = sel == 1'b1 ? in1 : in0; 11 | endmodule // mux2x1_assign 12 | 13 | 14 | module mux2x1_if ( 15 | input logic in0, 16 | input logic in1, 17 | input logic sel, 18 | output logic out 19 | ); 20 | always_comb begin 21 | if (sel == 1'b0) out = in0; 22 | else out = in1; 23 | end 24 | endmodule // mux2x1_if 25 | 26 | 27 | module mux2x1_if2 ( 28 | input logic in0, 29 | input logic in1, 30 | input logic sel, 31 | output logic out 32 | ); 33 | always @(*) begin 34 | if (sel == 1'b0) out <= in0; 35 | else out <= in1; 36 | end 37 | endmodule // mux2x1_if2 38 | 39 | 40 | module mux2x1_case ( 41 | input logic in0, 42 | input logic in1, 43 | input logic sel, 44 | output logic out 45 | ); 46 | 47 | always_comb begin 48 | case (sel) 49 | 1'b0: out = in0; 50 | 1'b1: out = in1; 51 | endcase 52 | end 53 | endmodule // mux2x1_case 54 | 55 | 56 | // Module: mux2x1 57 | // Description: Top-level module, which is only required if this file is 58 | // specified as the top-level module in a synthesis tool. In that case, 59 | // synthesis tools look for a module with the same name as the file. 60 | 61 | module mux2x1 ( 62 | input logic in0, 63 | input logic in1, 64 | input logic sel, 65 | output logic out 66 | ); 67 | 68 | // Change the module name here to synthesize other modules. 69 | // NOTE: This syntax will be explained in structurual architectures. Feel 70 | // free to ignore for now. 71 | 72 | mux2x1_assign mux (.*); 73 | // mux2x1_if mux (.*); 74 | // mux2x1_if2 mux (.*); 75 | // mux2x1_case mux (.*); 76 | 77 | endmodule // mux2x1 78 | -------------------------------------------------------------------------------- /testbenches/assertions/mux2x1_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // Module: mux_2x1_tb 4 | // Description: This module illustrates a very basic SV testbench for the 5 | // mux2x1 module that uses assertions. 6 | 7 | `timescale 1 ns/10 ps 8 | 9 | module mux2x1_tb; 10 | 11 | logic in0, in1, sel; 12 | logic out_assign, out_if, out_if2, out_case; 13 | logic correct_out; 14 | 15 | // Instantiate the 4 DUTs. 16 | mux2x1_assign DUT_ASSIGN ( 17 | .out(out_assign), 18 | .* 19 | ); 20 | mux2x1_if DUT_IF ( 21 | .out(out_if), 22 | .* 23 | ); 24 | mux2x1_if2 DUT_IF2 ( 25 | .out(out_if2), 26 | .* 27 | ); 28 | mux2x1_case DUT_CASE ( 29 | .out(out_case), 30 | .* 31 | ); 32 | 33 | // We use a function here to avoid copying and pasting for the different DUTs 34 | function void check_output(string name, logic actual, logic correct); 35 | 36 | // Use an assertion to check for the correct output. Assertions 37 | // specify a condition that should always be true. 38 | // The error message can be printed with different severity levels: 39 | // $error, $warning, $fatal, $display 40 | // Each level can have different behaviors in different simulators. 41 | // Use $fatal to end the simulation immediately. 42 | // 43 | // This assertion is an "immediate" assertion because it occurs within 44 | // a function (or always block). Concurrent assertions can be used outside 45 | // of always blocks but require some notion of a clock signal, which isn't 46 | // appropriate for combinational logic. 47 | assert(actual == correct) else $error("[%0t] %s = %b instead of %d.", $realtime, name, actual, correct); 48 | 49 | endfunction 50 | 51 | initial begin 52 | $timeformat(-9, 0, " ns"); 53 | 54 | for (int i = 0; i < 8; i++) begin 55 | 56 | in0 <= i[0]; 57 | in1 <= i[1]; 58 | sel <= i[2]; 59 | #10; 60 | 61 | // Verify all the outputs. 62 | correct_out = sel ? in1 : in0; 63 | check_output("out_assign", out_assign, correct_out); 64 | check_output("out_if", out_if, correct_out); 65 | check_output("out_if2", out_if2, correct_out); 66 | check_output("out_case", out_case, correct_out); 67 | end 68 | 69 | $display("Tests completed."); 70 | end 71 | endmodule 72 | 73 | -------------------------------------------------------------------------------- /testbenches/assertions/register.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // Module: register 5 | // Description: Implements a register with an active high, asynchronous reset 6 | // and an enable signal. 7 | 8 | module register #( 9 | parameter int WIDTH 10 | ) ( 11 | input logic clk, 12 | input logic rst, 13 | input logic en, 14 | input logic [WIDTH-1:0] in, 15 | output logic [WIDTH-1:0] out 16 | ); 17 | 18 | always_ff @(posedge clk or posedge rst) begin 19 | if (rst) out <= '0; 20 | else if (en) out <= in; 21 | end 22 | 23 | endmodule 24 | -------------------------------------------------------------------------------- /testbenches/assertions/simple_pipeline_with_en.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // Module: simple_pipeline_with_en 5 | // Description: This module is identical to simple_pipeline, but adds an enable 6 | // that can stall the pipeline. 7 | 8 | //=================================================================== 9 | // Parameter Description 10 | // WIDTH : The data width (number of bits) of the input and output 11 | //=================================================================== 12 | 13 | //=================================================================== 14 | // Interface Description 15 | // clk : Clock input 16 | // rst : Reset input (active high) 17 | // en : Enable signal (pipeline active when 1, stalled when 0) 18 | // in : An array of 8 WIDTH-bit inputs 19 | // valid_in : User should assert any time the input data on "in" is valid. 20 | // out : The output of the multiply accumulate computation. 21 | // valid_out : Asserted whenever "out" contains valid data. 22 | //=================================================================== 23 | 24 | module simple_pipeline_with_en #( 25 | parameter int WIDTH = 16 26 | ) ( 27 | input logic clk, 28 | input logic rst, 29 | input logic en, 30 | input logic [WIDTH-1:0] data_in [8], 31 | input logic valid_in, 32 | output logic [WIDTH-1:0] data_out, 33 | output logic valid_out 34 | ); 35 | // Specifies the cycle latency of the pipeline. 36 | localparam int LATENCY = 4; 37 | 38 | logic [WIDTH-1:0] data_in_r[8]; 39 | logic [WIDTH-1:0] mult_r[4]; 40 | logic [WIDTH-1:0] add_r[2]; 41 | logic [WIDTH-1:0] data_out_r; 42 | logic [LATENCY-1:0] valid_delay_r; 43 | 44 | assign data_out = data_out_r; 45 | 46 | always_ff @(posedge clk or posedge rst) begin 47 | if (rst) begin 48 | // Reset all the registers. 49 | data_in_r <= '{default: '0}; 50 | mult_r <= '{default: '0}; 51 | add_r <= '{default: '0}; 52 | data_out_r <= '0; 53 | end else begin 54 | if (en == 1'b1) begin 55 | // Register the inputs. 56 | for (int i = 0; i < 8; i++) data_in_r[i] <= data_in[i]; 57 | // Perform the multiplications. 58 | for (int i = 0; i < 4; i++) mult_r[i] <= data_in_r[i*2] * data_in_r[i*2+1]; 59 | // Create the first level of adders. 60 | for (int i = 0; i < 2; i++) add_r[i] <= mult_r[i*2] + mult_r[i*2+1]; 61 | // Create the final adder. 62 | data_out_r <= add_r[0] + add_r[1]; 63 | end 64 | end 65 | end 66 | 67 | // Delay that determines when out is valid based on the pipeline latency. 68 | always_ff @(posedge clk or posedge rst) begin 69 | if (rst) begin 70 | valid_delay_r <= '{default: '0}; 71 | end else if (en) begin 72 | valid_delay_r[0] <= valid_in; 73 | for (int i = 1; i < LATENCY; i++) valid_delay_r[i] <= valid_delay_r[i-1]; 74 | end 75 | end 76 | 77 | assign valid_out = valid_delay_r[LATENCY-1]; 78 | endmodule 79 | 80 | -------------------------------------------------------------------------------- /testbenches/basic/mux2x1.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // Module: mux2x1_assign 5 | // Description: behavioral 2x1 mux using an assign statement. 6 | 7 | module mux2x1_assign ( 8 | input logic in0, 9 | input logic in1, 10 | input logic sel, 11 | output logic out 12 | ); 13 | assign out = sel ? in1 : in0; 14 | 15 | endmodule // mux2x1_assign 16 | 17 | 18 | // Module: mux2x1_if 19 | // Description: behavioral 2x1 mux using an if statement. 20 | 21 | module mux2x1_if ( 22 | input logic in0, 23 | input logic in1, 24 | input logic sel, 25 | output logic out 26 | ); 27 | 28 | always_comb begin 29 | if (sel == 1'b0) begin 30 | out = in0; 31 | end else begin 32 | out = in1; 33 | end 34 | end 35 | 36 | endmodule // mux2x1_if 37 | 38 | 39 | // Module: mux2x1_if2 40 | // Description: Alternative behavioral 2x1 mux using an if statement. 41 | // Demonstrates different SV constructs. 42 | 43 | module mux2x1_if2 ( 44 | input logic in0, 45 | input logic in1, 46 | input logic sel, 47 | output logic out 48 | ); 49 | 50 | always @(*) begin 51 | if (sel == 1'b0) out <= in0; 52 | else out <= in1; 53 | end 54 | endmodule // mux2x1_if2 55 | 56 | 57 | // Module: mux2x1_case 58 | // Description: behavioral 2x1 mux using a case statement. 59 | 60 | module mux2x1_case ( 61 | input logic in0, 62 | input logic in1, 63 | input logic sel, 64 | output logic out 65 | ); 66 | 67 | always_comb begin 68 | case (sel) 69 | 1'b0: out = in0; 70 | 1'b1: out = in1; 71 | endcase 72 | end 73 | endmodule // mux2x1_case 74 | 75 | 76 | // Module: mux2x1 77 | // Description: Top-level module, which is only required if this file is 78 | // specified as the top-level module in a synthesis tool. In that case, 79 | // synthesis tools look for a module with the same name as the file. 80 | 81 | module mux2x1 ( 82 | input logic in0, 83 | input logic in1, 84 | input logic sel, 85 | output logic out 86 | ); 87 | 88 | // Change the module name here to synthesize other modules. 89 | 90 | mux2x1_assign mux (.*); 91 | // mux2x1_if mux (.*); 92 | // mux2x1_if2 mux (.*); 93 | // mux2x1_case mux (.*); 94 | 95 | endmodule // mux2x1 96 | 97 | -------------------------------------------------------------------------------- /testbenches/basic/register.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // Module: register 5 | // Description: Implements a register with an active high, asynchronous reset 6 | // and an enable signal. 7 | 8 | module register #( 9 | parameter int WIDTH 10 | ) ( 11 | input logic clk, 12 | input logic rst, 13 | input logic en, 14 | input logic [WIDTH-1:0] in, 15 | output logic [WIDTH-1:0] out 16 | ); 17 | 18 | always_ff @(posedge clk or posedge rst) begin 19 | if (rst) out <= '0; 20 | else if (en) out <= in; 21 | end 22 | 23 | endmodule 24 | -------------------------------------------------------------------------------- /testbenches/coverage/add.sv: -------------------------------------------------------------------------------- 1 | module add #( 2 | parameter int WIDTH 3 | ) ( 4 | input logic [WIDTH-1:0] in0, 5 | in1, 6 | input logic carry_in, 7 | output logic [WIDTH-1:0] sum, 8 | output logic carry_out 9 | ); 10 | 11 | assign {carry_out, sum} = in0 + in1 + carry_in; 12 | 13 | endmodule 14 | -------------------------------------------------------------------------------- /testbenches/coverage/fifo.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | // This file provides a basic FIFO module with a 1-cycle read latency. Note that 4 | // it includes assertions at the bottom of the module. 5 | 6 | module fifo #( 7 | parameter int WIDTH = 16, 8 | parameter int DEPTH = 32 9 | ) ( 10 | input logic clk, 11 | input logic rst, 12 | output logic full, 13 | input logic wr_en, 14 | input logic [WIDTH-1:0] wr_data, 15 | output logic empty, 16 | input logic rd_en, 17 | output logic [WIDTH-1:0] rd_data 18 | ); 19 | logic [WIDTH-1:0] ram[DEPTH]; 20 | logic valid_wr, valid_rd; 21 | 22 | localparam int ADDR_WIDTH = $clog2(DEPTH) + 1; 23 | logic [ADDR_WIDTH-1:0] wr_addr_r, rd_addr_r; 24 | 25 | always_ff @(posedge clk) begin 26 | if (valid_wr) ram[wr_addr_r[ADDR_WIDTH-2:0]] <= wr_data; 27 | rd_data <= ram[rd_addr_r[ADDR_WIDTH-2:0]]; 28 | end 29 | 30 | always_ff @(posedge clk or posedge rst) begin 31 | if (rst) begin 32 | rd_addr_r <= '0; 33 | wr_addr_r <= '0; 34 | end else begin 35 | if (valid_wr) wr_addr_r <= wr_addr_r + 1'b1; 36 | if (valid_rd) rd_addr_r <= rd_addr_r + 1'b1; 37 | end 38 | end 39 | 40 | assign valid_wr = wr_en && !full; 41 | assign valid_rd = rd_en && !empty; 42 | 43 | assign full = rd_addr_r[ADDR_WIDTH-2:0] == wr_addr_r[ADDR_WIDTH-2:0] && rd_addr_r[ADDR_WIDTH-1] != wr_addr_r[ADDR_WIDTH-1]; 44 | assign empty = rd_addr_r == wr_addr_r; 45 | 46 | // For important properties of a module, it is often a good idea to include 47 | // assertions in the synthesizable code. These will be ignored by synthesis 48 | // tools, but will still work in simulation. Note that some synthesis tools 49 | // might require an attribute or annotation to ignore this. 50 | assert property (@(posedge clk) valid_wr |-> !full); 51 | assert property (@(posedge clk) valid_rd |-> !empty); 52 | 53 | endmodule 54 | -------------------------------------------------------------------------------- /testbenches/crv/add.sv: -------------------------------------------------------------------------------- 1 | module add #( 2 | parameter int WIDTH 3 | ) ( 4 | input logic [WIDTH-1:0] in0, 5 | in1, 6 | input logic carry_in, 7 | output logic [WIDTH-1:0] sum, 8 | output logic carry_out 9 | ); 10 | 11 | assign {carry_out, sum} = in0 + in1 + carry_in; 12 | 13 | endmodule 14 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/bit_diff_bfm.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | interface bit_diff_bfm #(parameter int WIDTH) (input logic clk); 5 | logic rst, go, done; 6 | logic [WIDTH-1:0] data; 7 | logic signed [$clog2(2*WIDTH+1)-1:0] result; 8 | 9 | task automatic wait_for_done(); 10 | @(posedge clk iff (done == 1'b0)); 11 | @(posedge clk iff (done == 1'b1)); 12 | endtask 13 | 14 | task automatic reset(int cycles); 15 | rst <= 1'b1; 16 | go <= 1'b0; 17 | for (int i=0; i < cycles; i++) @(posedge clk); 18 | @(negedge clk); 19 | rst <= 1'b0; 20 | @(posedge clk); 21 | endtask 22 | 23 | task automatic start(input logic [WIDTH-1:0] data_); 24 | data <= data_; 25 | go <= 1'b1; 26 | @(posedge clk); 27 | go <= 1'b0; 28 | endtask // start 29 | 30 | // Helper code to detect when the DUT starts executing. This task internally 31 | // tracks the active status of the DUT and sends an event every time it 32 | // becomes active. With this strategy, the implementation specific details 33 | // are limited to the BFM and are hidden from the testbench. 34 | event active_event; 35 | task automatic monitor(); 36 | logic is_active; 37 | is_active = 1'b0; 38 | 39 | forever begin 40 | @(posedge clk); 41 | if (rst) is_active = 1'b0; 42 | else begin 43 | if (done) is_active = 1'b0; 44 | if (!is_active && go) begin 45 | is_active = 1'b1; 46 | // The event is needed because there will be times in the 47 | // simulation where go and done are asserted at the same time. 48 | // If the code simply used @(posedge is_active) to detect the 49 | // start of a test, it would miss these instances because 50 | // there wouldn't be a rising edge on is_active. It would simply 51 | // remain active between two consecutive tests. 52 | -> active_event; 53 | end 54 | end 55 | end 56 | endtask // monitor 57 | endinterface 58 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/bit_diff_item.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _BIT_DIFF_ITEM_SVH_ 5 | `define _BIT_DIFF_ITEM_SVH_ 6 | 7 | class bit_diff_item #(WIDTH); 8 | rand bit [WIDTH-1:0] data; 9 | rand bit go; 10 | bit signed [$clog2(WIDTH*2+1)-1:0] result; 11 | 12 | // A uniform distribution of go values probably isn't what we want, so 13 | // we'll make sure go is 1'b0 90% of the time. 14 | constraint c_go_dist { go dist{0 :/ 90, 1:/ 10 }; } 15 | endclass 16 | 17 | `endif 18 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/bit_diff_tb.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `include "test.svh" 5 | 6 | module bit_diff_tb; 7 | 8 | localparam NUM_RANDOM_TESTS = 1000; 9 | localparam NUM_CONSECUTIVE_TESTS = 200; 10 | localparam NUM_REPEATS = 4; 11 | localparam WIDTH = 16; 12 | logic clk; 13 | 14 | bit_diff_bfm #(.WIDTH(WIDTH)) bfm (.clk(clk)); 15 | bit_diff #(.WIDTH(WIDTH)) DUT (.clk(clk), .rst(bfm.rst), .go(bfm.go), 16 | .done(bfm.done), .data(bfm.data), 17 | .result(bfm.result)); 18 | 19 | random_test #(.WIDTH(WIDTH)) test_random = new(bfm, "Random Test"); 20 | consecutive_test #(.WIDTH(WIDTH)) test_consecutive = new(bfm, "Consecutive Test"); 21 | 22 | initial begin : generate_clock 23 | clk = 1'b0; 24 | while(1) #5 clk = ~clk; 25 | end 26 | 27 | initial begin 28 | $timeformat(-9, 0, " ns"); 29 | test_random.run(NUM_RANDOM_TESTS, NUM_REPEATS); 30 | test_consecutive.run(NUM_CONSECUTIVE_TESTS, NUM_REPEATS); 31 | test_random.report_status(); 32 | test_consecutive.report_status(); 33 | disable generate_clock; 34 | end 35 | 36 | assert property (@(posedge bfm.clk) disable iff (bfm.rst) bfm.go && bfm.done |=> !bfm.done); 37 | assert property (@(posedge bfm.clk) disable iff (bfm.rst) $fell(bfm.done) |-> $past(bfm.go,1)); 38 | 39 | endmodule 40 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/driver.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _DRIVER_SVH_ 5 | `define _DRIVER_SVH_ 6 | 7 | `include "bit_diff_item.svh" 8 | 9 | virtual class base_driver #(int WIDTH); 10 | virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm; 11 | mailbox driver_mailbox; 12 | event driver_done_event; 13 | 14 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm); 15 | this.bfm = bfm; 16 | driver_mailbox = new; 17 | endfunction // new 18 | 19 | pure virtual task run(); 20 | endclass // base_driver 21 | 22 | 23 | class nonblocking_driver #(int WIDTH) extends base_driver #(.WIDTH(WIDTH)); 24 | 25 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm); 26 | super.new(bfm); 27 | endfunction // new 28 | 29 | virtual task run(); 30 | bit_diff_item #(.WIDTH(WIDTH)) item; 31 | $display("Time %0t [Driver]: Driver starting.", $time); 32 | 33 | forever begin 34 | driver_mailbox.get(item); 35 | //$display("Time %0t [Driver]: Driving data=h%h, go=%0b.", $time, item.data, item.go); 36 | bfm.data = item.data; 37 | bfm.go = item.go; 38 | @(posedge bfm.clk); 39 | -> driver_done_event; 40 | end 41 | endtask 42 | endclass 43 | 44 | 45 | class blocking_driver #(int WIDTH) extends base_driver #(.WIDTH(WIDTH)); 46 | 47 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm); 48 | super.new(bfm); 49 | endfunction // new 50 | 51 | task run(); 52 | bit_diff_item #(.WIDTH(WIDTH)) item; 53 | $display("Time %0t [Driver]: Driver starting.", $time); 54 | 55 | forever begin 56 | driver_mailbox.get(item); 57 | bfm.start(item.data); 58 | bfm.wait_for_done(); 59 | $display("Time %0t [Driver]: Detected done.", $time); 60 | -> driver_done_event; 61 | end 62 | endtask 63 | endclass 64 | 65 | `endif 66 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/environment.svh: -------------------------------------------------------------------------------- 1 | // Gerg Stitt 2 | // University of Florida 3 | 4 | `ifndef _ENVIRONMENT_SVH_ 5 | `define _ENVIRONMENT_SVH_ 6 | 7 | `include "driver.svh" 8 | `include "generator.svh" 9 | `include "monitor.svh" 10 | `include "scoreboard.svh" 11 | 12 | class environment #(int WIDTH); 13 | 14 | // The environment doesn't know what type of generator and driver it will 15 | // use, so it contains a handle to the base class for each. Use of virtual 16 | // methods then allows us to use any class derived from the base version 17 | // without requiring knowledge of the specific class here. 18 | base_generator #(.WIDTH(WIDTH)) genarator_h; 19 | base_driver #(.WIDTH(WIDTH)) driver_h; 20 | done_monitor #(.WIDTH(WIDTH)) done_monitor_h; 21 | start_monitor #(.WIDTH(WIDTH)) start_monitor_h; 22 | scoreboard #(.WIDTH(WIDTH)) scoreboard_h; 23 | 24 | mailbox scoreboard_data_mailbox; 25 | mailbox scoreboard_result_mailbox; 26 | mailbox driver_mailbox; 27 | 28 | event driver_done_event; 29 | 30 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm, 31 | base_generator #(.WIDTH(WIDTH)) gen_h, 32 | base_driver #(.WIDTH(WIDTH)) drv_h); 33 | scoreboard_data_mailbox = new; 34 | scoreboard_result_mailbox = new; 35 | driver_mailbox = new; 36 | 37 | // We no longer instantiate these here because they are created in the 38 | // test class and passed in to this constructor. 39 | genarator_h = gen_h; 40 | driver_h = drv_h; 41 | done_monitor_h = new(bfm, scoreboard_result_mailbox); 42 | start_monitor_h = new(bfm, scoreboard_data_mailbox); 43 | scoreboard_h = new(scoreboard_data_mailbox, scoreboard_result_mailbox); 44 | endfunction // new 45 | 46 | function void report_status(); 47 | scoreboard_h.report_status(); 48 | endfunction 49 | 50 | virtual task run(int num_tests); 51 | fork 52 | genarator_h.run(); 53 | driver_h.run(); 54 | done_monitor_h.run(); 55 | start_monitor_h.run(); 56 | scoreboard_h.run(num_tests); 57 | join_any 58 | 59 | disable fork; 60 | endtask 61 | endclass 62 | 63 | `endif 64 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/generator.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _GENERATOR_SVH_ 5 | `define _GENERATOR_SVH_ 6 | 7 | `include "driver.svh" 8 | 9 | virtual class base_generator #(int WIDTH); 10 | 11 | mailbox driver_mailbox; 12 | event driver_done_event; 13 | 14 | function new(base_driver #(.WIDTH(WIDTH)) driver_h); 15 | this.driver_mailbox = driver_h.driver_mailbox; 16 | this.driver_done_event = driver_h.driver_done_event; 17 | endfunction // new 18 | 19 | pure virtual task run(); 20 | endclass 21 | 22 | 23 | class random_generator #(int WIDTH) extends base_generator #(.WIDTH(WIDTH)); 24 | 25 | function new(base_driver #(.WIDTH(WIDTH)) driver_h); 26 | super.new(driver_h); 27 | endfunction // new 28 | 29 | virtual task run(); 30 | bit_diff_item #(.WIDTH(WIDTH)) item; 31 | 32 | // Start the consecutive sequence at 0. This could also be modified with 33 | // another configuration parameter. 34 | bit [WIDTH-1:0] data = '0; 35 | 36 | forever begin 37 | item = new; 38 | if (!item.randomize()) $display("Randomize failed"); 39 | driver_mailbox.put(item); 40 | @(driver_done_event); 41 | end 42 | endtask 43 | endclass 44 | 45 | 46 | class consecutive_generator #(int WIDTH) extends base_generator #(.WIDTH(WIDTH)); 47 | 48 | function new(base_driver #(.WIDTH(WIDTH)) driver_h); 49 | super.new(driver_h); 50 | endfunction // new 51 | 52 | task run(); 53 | bit_diff_item #(.WIDTH(WIDTH)) item; 54 | bit [WIDTH-1:0] data = '0; 55 | 56 | forever begin 57 | item = new; 58 | item.data = data; 59 | data ++; 60 | driver_mailbox.put(item); 61 | @(driver_done_event); 62 | end 63 | endtask 64 | endclass 65 | 66 | `endif 67 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/monitor.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _MONITOR_SVH_ 5 | `define _MONITOR_SVH_ 6 | 7 | `include "bit_diff_item.svh" 8 | 9 | virtual class base_monitor #(int WIDTH); 10 | virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm; 11 | 12 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm); 13 | this.bfm = bfm; 14 | endfunction // new 15 | 16 | pure virtual task run(); 17 | endclass 18 | 19 | 20 | class done_monitor #(int WIDTH) extends base_monitor #(.WIDTH(WIDTH)); 21 | mailbox scoreboard_result_mailbox; 22 | 23 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm, 24 | mailbox _scoreboard_result_mailbox); 25 | super.new(bfm); 26 | scoreboard_result_mailbox = _scoreboard_result_mailbox; 27 | endfunction // new 28 | 29 | virtual task run(); 30 | $display("Time %0t [Monitor]: Monitor starting.", $time); 31 | 32 | forever begin 33 | bit_diff_item #(.WIDTH(WIDTH)) item = new; 34 | bfm.wait_for_done(); 35 | item.result = bfm.result; 36 | $display("Time %0t [Monitor]: Monitor detected result=%0d.", $time, bfm.result); 37 | scoreboard_result_mailbox.put(item); 38 | end 39 | endtask 40 | endclass 41 | 42 | 43 | class start_monitor #(int WIDTH) extends base_monitor #(.WIDTH(WIDTH)); 44 | mailbox scoreboard_data_mailbox; 45 | 46 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm, 47 | mailbox _scoreboard_data_mailbox); 48 | super.new(bfm); 49 | scoreboard_data_mailbox = _scoreboard_data_mailbox; 50 | endfunction // new 51 | 52 | virtual task run(); 53 | fork 54 | // Start the BFM monitor to track the active status. 55 | bfm.monitor(); 56 | detect_start(); 57 | join_any 58 | endtask 59 | 60 | task detect_start(); 61 | forever begin 62 | bit_diff_item #(.WIDTH(WIDTH)) item = new; 63 | 64 | // Wait until the DUT becomes active. 65 | @(bfm.active_event); 66 | item.data = bfm.data; 67 | $display("Time %0t [start_monitor]: Sending start of test for data=h%h.", $time, item.data); 68 | scoreboard_data_mailbox.put(item); 69 | end 70 | endtask 71 | endclass 72 | 73 | `endif 74 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/scoreboard.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _SCOREBOARD_SVH_ 5 | `define _SCOREBOARD_SVH_ 6 | 7 | `include "bit_diff_item.svh" 8 | 9 | class scoreboard #(int WIDTH); 10 | mailbox scoreboard_result_mailbox; 11 | mailbox scoreboard_data_mailbox; 12 | int passed, failed, reference; 13 | 14 | function new(mailbox scoreboard_data_mailbox, mailbox scoreboard_result_mailbox); 15 | this.scoreboard_data_mailbox = scoreboard_data_mailbox; 16 | this.scoreboard_result_mailbox = scoreboard_result_mailbox; 17 | 18 | passed = 0; 19 | failed = 0; 20 | endfunction // new 21 | 22 | function int model(int data, int width); 23 | automatic int diff = 0; 24 | 25 | for (int i=0; i < width; i++) begin 26 | diff = data[0] ? diff+1 : diff-1; 27 | data = data >> 1; 28 | end 29 | 30 | return diff; 31 | endfunction 32 | 33 | task run(int num_tests); 34 | bit_diff_item #(.WIDTH(WIDTH)) in_item; 35 | bit_diff_item #(.WIDTH(WIDTH)) out_item; 36 | 37 | for (int i=0; i < num_tests; i++) begin 38 | 39 | // First wait until the driver informs us of a new test. 40 | scoreboard_data_mailbox.get(in_item); 41 | $display("Time %0t [Scoreboard]: Received start of test for data=h%h.", $time, in_item.data); 42 | 43 | // Then, wait until the monitor tells us that test is complete. 44 | scoreboard_result_mailbox.get(out_item); 45 | $display("Time %0t [Scoreboard]: Received result=%0d for data=h%h.", $time, out_item.result, in_item.data); 46 | 47 | // Get the correct result based on the input at the start of the test. 48 | reference = model(in_item.data, WIDTH); 49 | if (out_item.result == reference) begin 50 | $display("Time %0t [Scoreboard] Test passed for data=h%h", $time, in_item.data); 51 | passed ++; 52 | end 53 | else begin 54 | $display("Time %0t [Scoredboard] Test failed: result = %0d instead of %0d for data = h%h.", $time, out_item.result, reference, in_item.data); 55 | failed ++; 56 | end 57 | end // for (int i=0; i < num_tests; i++) 58 | 59 | // Remove any leftover messages that might be in the mailbox upon 60 | // completion. This is needed for the repeat functionality to work. 61 | // If data is left in the mailbox when repeating a test, that data 62 | // will be detected as part of the current test. 63 | while(scoreboard_data_mailbox.try_get(in_item)); 64 | while(scoreboard_result_mailbox.try_get(out_item)); 65 | endtask 66 | 67 | function void report_status(); 68 | $display("Test status: %0d passed, %0d failed", passed, failed); 69 | endfunction 70 | 71 | endclass 72 | 73 | `endif 74 | -------------------------------------------------------------------------------- /testbenches/crv/bit_diff_oop/test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _TEST_SVH_ 5 | `define _TEST_SVH_ 6 | 7 | `include "environment.svh" 8 | 9 | virtual class base_test #(int WIDTH); 10 | 11 | virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm; 12 | string name; 13 | environment #(.WIDTH(WIDTH)) env_h; 14 | 15 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm, 16 | string name = "default_test_name"); 17 | 18 | // Ideally we would also create the environment here, but we don't 19 | // have all the parameters we need for the constructor yet. 20 | // TODO: Find a cleaner way of doing this. 21 | this.bfm = bfm; 22 | this.name = name; 23 | endfunction // new 24 | 25 | virtual function void report_status(); 26 | $display("Results for Test %0s", name); 27 | env_h.report_status(); 28 | endfunction 29 | 30 | virtual task run(int num_tests, int num_repeats=0); 31 | $display("Time %0t [Test]: Starting test %0s.", $time, name); 32 | 33 | for (int i=0; i < num_repeats+1; i++) begin 34 | if (i > 0) $display("Time %0t [Test]: Repeating test %0s (pass %0d).", $time, name, i+1); 35 | bfm.reset(5); 36 | env_h.run(num_tests); 37 | @(posedge bfm.clk); 38 | end 39 | $display("Time %0t [Test]: Test completed.", $time); 40 | endtask 41 | endclass 42 | 43 | 44 | class random_test #(int WIDTH) extends base_test #(.WIDTH(WIDTH)); 45 | 46 | nonblocking_driver #(.WIDTH(WIDTH)) drv_h; 47 | random_generator #(.WIDTH(WIDTH)) gen_h; 48 | 49 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm, string name); 50 | super.new(bfm, name); 51 | 52 | // These should really be passed to the base constructor, but super.new 53 | // must be called first in the constructor, which makes it impossible 54 | // to create the generator and driver before calling super.new(). 55 | // So, we use this workaround. 56 | drv_h = new(bfm); 57 | gen_h = new(drv_h); 58 | env_h = new(bfm, gen_h, drv_h); 59 | endfunction // new 60 | 61 | endclass 62 | 63 | class consecutive_test #(int WIDTH) extends base_test #(.WIDTH(WIDTH)); 64 | 65 | blocking_driver #(.WIDTH(WIDTH)) drv_h; 66 | consecutive_generator #(.WIDTH(WIDTH)) gen_h; 67 | 68 | function new(virtual bit_diff_bfm #(.WIDTH(WIDTH)) bfm, string name); 69 | super.new(bfm, name); 70 | 71 | // These should really be passed to the base constructor, but super.new 72 | // must be called first in the constructor, which makes it impossible 73 | // to create the generator and driver before calling super.new(). 74 | // So, we use this workaround. 75 | // This is also non-ideal because now we have repeated code in the 76 | // constructors for both derived classes. There is almost always a 77 | // better way when this situation occurs. 78 | // TODO: Find a cleaner approach. 79 | drv_h = new(bfm); 80 | gen_h = new(drv_h); 81 | env_h = new(bfm, gen_h, drv_h); 82 | endfunction // new 83 | 84 | endclass 85 | 86 | 87 | `endif 88 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Questa SystemVerilog simulation with UVM 2 | 3 | # Check if vsim exists in PATH 4 | ifeq (,$(shell which vsim)) 5 | $(error "vsim not found in PATH. Please ensure Questa is properly installed and added to PATH") 6 | endif 7 | 8 | # Tool and library configuration 9 | VLOG = vlog 10 | VSIM = vsim 11 | VOPT = vopt 12 | 13 | # Project configuration 14 | WORK_DIR = work 15 | TOP_MODULE = mult_tb 16 | OPTIMIZED_TOP = $(TOP_MODULE)_opt 17 | 18 | # UVM configuration 19 | UVM_TESTNAME ?= mult_simple_test 20 | UVM_FLAGS = +UVM_TESTNAME=$(UVM_TESTNAME) 21 | 22 | # Compilation flags 23 | VLOG_FLAGS = -sv \ 24 | -mfcu \ 25 | -lint \ 26 | +acc=pr \ 27 | -suppress 2275 \ 28 | -timescale "1ns/1ps" \ 29 | +define+UVM_PACKER_MAX_BYTES=1500000 \ 30 | +define+UVM_DISABLE_AUTO_ITEM_RECORDING \ 31 | -work $(WORK_DIR) 32 | 33 | # Optimization flags (preserve full visibility with +acc) 34 | VOPT_FLAGS = +acc \ 35 | -o $(OPTIMIZED_TOP) 36 | 37 | # Simulation flags 38 | VSIM_FLAGS = -c \ 39 | -debugDB \ 40 | -voptargs="+acc" \ 41 | +UVM_NO_RELNOTES \ 42 | +UVM_VERBOSITY=UVM_MEDIUM \ 43 | $(UVM_FLAGS) \ 44 | -do "run -all" 45 | 46 | # GUI simulation flags 47 | VSIM_GUI_FLAGS = -gui \ 48 | -debugDB \ 49 | -voptargs="+acc" \ 50 | +UVM_NO_RELNOTES \ 51 | +UVM_VERBOSITY=UVM_MEDIUM \ 52 | $(UVM_FLAGS) 53 | 54 | # Default target 55 | all: compile optimize 56 | 57 | # Create work library 58 | $(WORK_DIR): 59 | vlib $(WORK_DIR) 60 | vmap work $(WORK_DIR) 61 | 62 | # Read sources from file and compile 63 | compile: $(WORK_DIR) 64 | $(VLOG) $(VLOG_FLAGS) -f sources.txt 65 | 66 | # Optimize design while maintaining full visibility 67 | optimize: compile 68 | $(VOPT) $(TOP_MODULE) $(VOPT_FLAGS) 69 | 70 | # Run simulation in command-line mode 71 | sim: optimize 72 | @if [ "$(UVM_TESTNAME)" = "" ]; then \ 73 | echo "Error: UVM_TESTNAME is not set. Usage: make sim UVM_TESTNAME="; \ 74 | exit 1; \ 75 | fi 76 | $(VSIM) $(VSIM_FLAGS) $(OPTIMIZED_TOP) 77 | 78 | # Open GUI for interactive simulation 79 | gui: optimize 80 | @if [ "$(UVM_TESTNAME)" = "" ]; then \ 81 | echo "Error: UVM_TESTNAME is not set. Usage: make gui UVM_TESTNAME="; \ 82 | exit 1; \ 83 | fi 84 | $(VSIM) $(VSIM_GUI_FLAGS) $(OPTIMIZED_TOP) & 85 | 86 | # Clean up generated files 87 | clean: 88 | rm -rf $(WORK_DIR) 89 | rm -rf transcript 90 | rm -rf vsim.wlf 91 | rm -rf *.db 92 | rm -rf *.dbg 93 | rm -rf *.vstf 94 | rm -rf modelsim.ini 95 | 96 | .PHONY: all compile optimize sim gui clean 97 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/axi4_stream_agent.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This file implements a basic AXI4 stream agent for the subset of the AXI4 5 | // stream signals shown in axi4_stream_if.svh. 6 | // 7 | // The key point of this entire example is to recognize the potential reuse 8 | // of this agent. Now that you have an AXI4 stream agent, you can use it for 9 | // any DUT using AXI4 streaming, or across multiple interfaces instances of the 10 | // same DUT. The latter is exactly what we are doing in this testbench. The 11 | // environment creates three instance--1 for each input, and 1 for the output-- 12 | // with the output using a different width. 13 | // 14 | // Once you have an agent for all of your DUT's interfaces, you solely have to 15 | // change the DUT-specific classes, such as tests, environments, sequences, etc. 16 | 17 | `ifndef _AXI4_STREAM_AGENT_SVH_ 18 | `define _AXI4_STREAM_AGENT_SVH_ 19 | 20 | `include "uvm_macros.svh" 21 | import uvm_pkg::*; 22 | 23 | `include "axi4_stream_sequencer.svh" 24 | `include "axi4_stream_driver.svh" 25 | `include "axi4_stream_monitor.svh" 26 | 27 | // To handle different AXI4 stream instances using different widths, we need 28 | // to parameterize this class. 29 | class axi4_stream_agent #( 30 | parameter int DATA_WIDTH = 32 31 | ) extends uvm_agent; 32 | // When using a class with parameters, it has to be registered using 33 | // uvm_component_param_utils instead of uvm_component_utils. The reason 34 | // for this is that UVM treats each different parameter value as a 35 | // separate type. 36 | `uvm_component_param_utils(axi4_stream_agent#(DATA_WIDTH)) 37 | 38 | // Declare the parameterized sequencer, driver, and monitor. 39 | axi4_stream_sequencer #(DATA_WIDTH) sequencer; 40 | axi4_stream_driver #(DATA_WIDTH) driver; 41 | axi4_stream_monitor #(DATA_WIDTH) monitor; 42 | 43 | function new(string name, uvm_component parent); 44 | super.new(name, parent); 45 | endfunction 46 | 47 | function void build_phase(uvm_phase phase); 48 | super.build_phase(phase); 49 | // Instantiate the sequencer, driver, and monitor. Note the use of 50 | // parameters here. 51 | sequencer = axi4_stream_sequencer#(DATA_WIDTH)::type_id::create("sequencer", this); 52 | driver = axi4_stream_driver#(DATA_WIDTH)::type_id::create("driver", this); 53 | monitor = axi4_stream_monitor#(DATA_WIDTH)::type_id::create("monitor", this); 54 | endfunction 55 | 56 | function void connect_phase(uvm_phase phase); 57 | // Connect the driver to the sequencer. 58 | driver.seq_item_port.connect(sequencer.seq_item_export); 59 | endfunction 60 | endclass 61 | 62 | `endif 63 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/axi4_stream_driver.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _AXI4_STREAM_DRIVER_SVH_ 5 | `define _AXI4_STREAM_DRIVER_SVH_ 6 | 7 | `include "uvm_macros.svh" 8 | import uvm_pkg::*; 9 | 10 | `include "axi4_stream_seq_item.svh" 11 | 12 | // To handle different AXI4 stream instances using different widths, we need 13 | // to parameterize this class. 14 | // 15 | // In addition, because uvm_driver has a parameter specifying the sequence 16 | // item, we must provide axi4_stream_seq_item. However, since that seq item 17 | // is now parameterized, we must also provide its parameter value. 18 | class axi4_stream_driver #( 19 | parameter int DATA_WIDTH = 32 20 | ) extends uvm_driver #(axi4_stream_seq_item #(DATA_WIDTH)); 21 | // When using a class with parameters, it has to be registered using 22 | // uvm_component_param_utils instead of uvm_component_utils. The reason 23 | // for this is that UVM treats each different parameter value as a 24 | // separate type. 25 | `uvm_component_param_utils(axi4_stream_driver#(DATA_WIDTH)) 26 | 27 | // We now have a parameterized virtual interface to support different widths. 28 | virtual axi4_stream_if #(.DATA_WIDTH(DATA_WIDTH)) vif; 29 | 30 | // Configuration parameters. 31 | int min_delay, max_delay; 32 | 33 | function new(string name, uvm_component parent); 34 | super.new(name, parent); 35 | min_delay = 1; 36 | max_delay = 1; 37 | endfunction 38 | 39 | function void build_phase(uvm_phase phase); 40 | super.build_phase(phase); 41 | endfunction 42 | 43 | function void set_delay(int min, int max); 44 | min_delay = min; 45 | max_delay = max; 46 | endfunction 47 | 48 | // Main driving logic. 49 | virtual task run_phase(uvm_phase phase); 50 | axi4_stream_seq_item #(DATA_WIDTH) req; 51 | 52 | // According to AXI spec, tvalid must be cleared on reset. 53 | vif.tvalid <= 1'b0; 54 | 55 | // Wait until reset has cleared to start driving. 56 | @(posedge vif.aclk iff !vif.aresetn); 57 | @(posedge vif.aclk iff vif.aresetn); 58 | @(posedge vif.aclk); 59 | 60 | forever begin 61 | // Get the sequence item. 62 | seq_item_port.get_next_item(req); 63 | 64 | // Drive the data onto the tdata port and assert tvalid. 65 | vif.tdata <= req.data; 66 | vif.tvalid <= 1'b1; 67 | 68 | // Hold the data and tvalid until tready is asserted. This is 69 | // required by the AXI spec. 70 | @(posedge vif.aclk iff vif.tready); 71 | 72 | // Clear tvalid for a random amount of cycles to enable more 73 | // thorough testing. 74 | vif.tvalid <= 1'b0; 75 | repeat ($urandom_range(min_delay-1, max_delay-1)) @(posedge vif.aclk); 76 | 77 | // Tell the sequencer we are done with the seq item. 78 | seq_item_port.item_done(); 79 | end 80 | endtask 81 | endclass 82 | 83 | 84 | `endif 85 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/axi4_stream_if.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // Wes Piard 3 | // University of Florida 4 | 5 | // A simplified subset of the AXI4 streaming interface. I've included all the 6 | // signal in the normal interface, but commented out the optional ones we won't 7 | // be supporting in this example. 8 | 9 | `ifndef _AXI4_STREAM_IF_ 10 | `define _AXI4_STREAM_IF_ 11 | 12 | interface axi4_stream_if #( 13 | parameter int DATA_WIDTH = 32 14 | /*parameter int ID_WIDTH = 4, 15 | parameter int DEST_WIDTH = 4, 16 | parameter int USER_WIDTH = 1*/ 17 | ) ( 18 | input logic aclk, 19 | input logic aresetn 20 | ); 21 | logic tvalid; 22 | logic tready; 23 | logic [DATA_WIDTH-1:0] tdata; 24 | /* logic [DATA_WIDTH/8-1:0] tstrb; 25 | logic [DATA_WIDTH/8-1:0] tkeep; 26 | logic tlast; 27 | logic [ID_WIDTH-1:0] tid; 28 | logic [DEST_WIDTH-1:0] tdest; 29 | logic [USER_WIDTH-1:0] tuser;*/ 30 | 31 | // AXI requires byte aligned data widths, so confirm compliance here. 32 | initial begin 33 | if (DATA_WIDTH % 8 != 0) $fatal(1, $sformatf("AXI DATA_WIDTH=%0d is not byte aligned", DATA_WIDTH)); 34 | end 35 | 36 | // If using the interface for synthesis, this will probably cause errors. We 37 | // use it here so we can use `uvm_error in the interface assertion. 38 | `include "uvm_macros.svh" 39 | import uvm_pkg::*; 40 | 41 | // Validate required properties of AXI: once tvalid is asserted, it must remain asserted until 42 | // tready is asserted. 43 | assert property (@(posedge aclk) disable iff (!aresetn) $fell(tvalid) |-> $past(tready, 1)) 44 | else `uvm_error("ASSERT", "tvalid must be asserted continuously until tready is asserted."); 45 | 46 | endinterface 47 | 48 | `endif 49 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/axi4_stream_monitor.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _AXI4_STREAM_MONITOR_SVH_ 5 | `define _AXI4_STREAM_MONITOR_SVH_ 6 | 7 | `include "uvm_macros.svh" 8 | import uvm_pkg::*; 9 | 10 | `include "axi4_stream_seq_item.svh" 11 | 12 | // To handle different AXI4 stream instances using different widths, we need 13 | // to parameterize this class. 14 | // 15 | // In addition, because uvm_monitor has a parameter specifying the sequence 16 | // item, we must provide axi4_stream_seq_item. However, since that seq item 17 | // is now parameterized, we must also provide its parameter value. 18 | class axi4_stream_monitor #( 19 | parameter int DATA_WIDTH = 32 20 | ) extends uvm_monitor; 21 | // When using a class with parameters, it has to be registered using 22 | // uvm_component_param_utils instead of uvm_component_utils. The reason 23 | // for this is that UVM treats each different parameter value as a 24 | // separate type. 25 | `uvm_component_param_utils(axi4_stream_monitor#(DATA_WIDTH)) 26 | 27 | // We now have a parameterized virtual interface to support different widths. 28 | virtual axi4_stream_if #(.DATA_WIDTH(DATA_WIDTH)) vif; 29 | 30 | // We use an analysis port (ap) instead of a blocking put port in this example. 31 | // Analysis ports are more flexible because they can have multiple 32 | // consumers. Note that the analysis port is not connected to anything here. 33 | // In fact, we have nothing to connect it to because the monitor is part of 34 | // an agent that is intended to be used across multiple DUTs, multiple 35 | // instances of the same DUT, different environments for the same DUT, etc. 36 | // So, we leave it disconnected and pass the responsibility elsewhere, with 37 | // the environment normally setting up this connection. 38 | uvm_analysis_port #(logic[DATA_WIDTH-1:0]) ap; 39 | 40 | function new(string name, uvm_component parent); 41 | super.new(name, parent); 42 | 43 | // Create the anaylsis port. 44 | ap = new("ap", this); 45 | endfunction 46 | 47 | function void build_phase(uvm_phase phase); 48 | super.build_phase(phase); 49 | endfunction 50 | 51 | // Note how simple this monitor is. It simply follows the AXI4 stream spec 52 | // which states that a transfer occurs when tvalid and tready are both 53 | // asserted at the same time. When that handshake occurs, we write the 54 | // corresponding data to the analyisis port, where it will be later read 55 | // by the scoreboard. However, it could be read by anything. We don't care 56 | // what is reading it here. We just send the data and let the environment 57 | // figure out the different consumers. 58 | task run_phase(uvm_phase phase); 59 | forever begin 60 | @(posedge vif.aclk iff vif.tvalid && vif.tready); 61 | ap.write(vif.tdata); 62 | end 63 | endtask 64 | endclass 65 | 66 | `endif 67 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/axi4_stream_seq_item.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _AXI4_STREAM_ITEM_SVH_ 5 | `define _AXI4_STREAM_ITEM_SVH_ 6 | 7 | `include "uvm_macros.svh" 8 | import uvm_pkg::*; 9 | 10 | // To handle different AXI4 stream instances using different widths, we need 11 | // to parameterize this class. 12 | class axi4_stream_seq_item #( 13 | parameter int DATA_WIDTH = 32 14 | ) extends uvm_sequence_item; 15 | // When using a class with parameters, it has to be registered using 16 | // uvm_component_param_utils instead of uvm_component_utils. The reason 17 | // for this is that UVM treats each different parameter value as a 18 | // separate type. 19 | `uvm_object_param_utils(axi4_stream_seq_item#(DATA_WIDTH)) 20 | 21 | // Since we are only using a subset of the AXI4 stream signals, the 22 | // transaction only needs the data being transferred. 23 | rand logic [DATA_WIDTH-1:0] data; 24 | 25 | function new(string name = "axi4_stream_seq_item"); 26 | super.new(name); 27 | endfunction 28 | endclass 29 | 30 | `endif 31 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/axi4_stream_sequencer.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _AXI4_STREAM_SEQUENCER_SVH_ 5 | `define _AXI4_STREAM_SEQUENCER_SVH_ 6 | 7 | `include "uvm_macros.svh" 8 | import uvm_pkg::*; 9 | 10 | `include "axi4_stream_seq_item.svh" 11 | 12 | // To handle different AXI4 stream instances using different widths, we need 13 | // to parameterize this class. 14 | // 15 | // In addition, because uvm_sequencer has a parameter specifying the sequence 16 | // item, we must provide axi4_stream_seq_item. However, since that seq item 17 | // is now parameterized, we must also provide its parameter value. 18 | class axi4_stream_sequencer #( 19 | parameter int DATA_WIDTH = 32 20 | ) extends uvm_sequencer #(axi4_stream_seq_item #(DATA_WIDTH)); 21 | // When using a class with parameters, it has to be registered using 22 | // uvm_component_param_utils instead of uvm_component_utils. The reason 23 | // for this is that UVM treats each different parameter value as a 24 | // separate type. 25 | `uvm_component_param_utils(axi4_stream_sequencer#(DATA_WIDTH)) 26 | 27 | function new(string name, uvm_component parent); 28 | super.new(name, parent); 29 | endfunction 30 | endclass 31 | 32 | `endif 33 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/mult_base_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This file provides a base class for other tests. 5 | 6 | `ifndef _MULT_BASE_TEST_SVH_ 7 | `define _MULT_BASE_TEST_SVH_ 8 | 9 | `include "uvm_macros.svh" 10 | import uvm_pkg::*; 11 | 12 | `include "mult_env.svh" 13 | 14 | class mult_base_test extends uvm_test; 15 | `uvm_component_utils(mult_base_test) 16 | 17 | mult_env env; 18 | 19 | function new(string name = "mult_base_test", uvm_component parent = null); 20 | super.new(name, parent); 21 | endfunction 22 | 23 | virtual function void build_phase(uvm_phase phase); 24 | super.build_phase(phase); 25 | env = mult_env::type_id::create("env", this); 26 | endfunction 27 | 28 | virtual function void end_of_elaboration(); 29 | // Prints the UVM topology. 30 | print(); 31 | endfunction 32 | 33 | function void report_phase(uvm_phase phase); 34 | uvm_report_server svr; 35 | super.report_phase(phase); 36 | 37 | // The report server provides statistics about the simulation. 38 | svr = uvm_report_server::get_server(); 39 | 40 | // If there were any instances of uvm_fatal or uvm_error, then we will 41 | // consider that to be a failed test. 42 | if (svr.get_severity_count(UVM_FATAL) + svr.get_severity_count(UVM_ERROR) > 0) begin 43 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 44 | `uvm_info(get_type_name(), "--- TEST FAILED ---", UVM_NONE) 45 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 46 | end else begin 47 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 48 | `uvm_info(get_type_name(), "--- TEST PASSED ---", UVM_NONE) 49 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 50 | end 51 | 52 | // Add coverage summary 53 | $display("=== Coverage Summary ===\n"); 54 | $display("Input Bin Coverage: %.2f%%", env.input_coverage.input_coverage.get_coverage()); 55 | $display(" In0 Coverage: %.2f%%", env.input_coverage.input_coverage.in0_cp.get_coverage()); 56 | $display(" In0 Extremes Coverage: %.2f%%", env.input_coverage.input_coverage.in0_extremes_cp.get_coverage()); 57 | $display(" In1 Coverage: %.2f%%", env.input_coverage.input_coverage.in1_cp.get_coverage()); 58 | $display(" In1 Extremes Coverage: %.2f%%", env.input_coverage.input_coverage.in1_extremes_cp.get_coverage()); 59 | $display(" Cross Coverage: %.2f%%", env.input_coverage.input_coverage.in_cross.get_coverage()); 60 | 61 | $display("\nOutput Bin Coverage: %.2f%%", env.output_coverage.output_coverage.get_coverage()); 62 | 63 | $display("\nToggle Coverage"); 64 | $display(" In0 Toggle Coverage: %.2f%%", env.input_coverage.in0_toggle_coverage.toggle_cp.get_coverage()); 65 | $display(" In1 Toggle Coverage: %.2f%%", env.input_coverage.in1_toggle_coverage.toggle_cp.get_coverage()); 66 | 67 | 68 | endfunction 69 | 70 | endclass 71 | 72 | 73 | `endif 74 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/mult_sequence.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This class defines a sequence to test each multiplier input. It simply 5 | // creates random axi4_stream_seq_items. 6 | 7 | `ifndef _MULT_SEQUENCE_SVH_ 8 | `define _MULT_SEQUENCE_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | `include "axi4_stream_seq_item.svh" 14 | 15 | import mult_tb_pkg::*; 16 | 17 | // This syntax can be confusing. Mult_sequence is derived from uvm_sequence, but 18 | // uvm_sequence is parameterized based on the seq item. So, we have to specify 19 | // the axi4_stream_seq_item. But, that sequenece item is also parameterized based 20 | // on the input width specified in mult_tb_pkg. This confusing parameterization 21 | // is one reason people avoid this strategy for parameterized interfaces. 22 | // However, it is important to understand before trying other methods. 23 | class mult_sequence extends uvm_sequence #(axi4_stream_seq_item #(mult_tb_pkg::INPUT_WIDTH)); 24 | `uvm_object_utils(mult_sequence) 25 | 26 | int num_tests; 27 | 28 | function new(string name = "mult_sequence"); 29 | super.new(name); 30 | if (!uvm_config_db#(int)::get(this, "", "num_tests", num_tests)) `uvm_fatal("NO_NUM_TESTS", "num_tests not specified."); 31 | endfunction 32 | 33 | virtual task body(); 34 | for (int i = 0; i < num_tests; i++) begin 35 | req = axi4_stream_seq_item#(mult_tb_pkg::INPUT_WIDTH)::type_id::create($sformatf("req%0d", i)); 36 | wait_for_grant(); 37 | 38 | // Create a custom distribution to ensure that we achieve 100% 39 | // coverage, which requires testing with 0 and the maximum sized 40 | // values. 41 | // 42 | // IMPORTANT: Note that we are doing the randomization here instead 43 | // of in the sequence item class like before. The reason for this 44 | // is that the sequence item is AXI specific, so it doesn't make 45 | // sense to put application-specific constraints on the interface. 46 | void'(req.randomize() with { 47 | data dist { 48 | '0 :/ 2, 49 | '1 :/ 2, 50 | [0 : 2 ** mult_tb_pkg::INPUT_WIDTH - 2] :/ 96 51 | }; 52 | }); 53 | 54 | send_request(req); 55 | wait_for_item_done(); 56 | end 57 | endtask 58 | endclass 59 | 60 | 61 | `endif 62 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/mult_simple_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // A simple test for the multiplier that generates two sequences and sends them 5 | // to the DUT in parallel by using a fork. 6 | 7 | `ifndef _MULT_SIMPLE_TEST_SVH_ 8 | `define _MULT_SIMPLE_TEST_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | import mult_tb_pkg::*; 14 | 15 | `include "mult_sequence.svh" 16 | `include "mult_base_test.svh" 17 | 18 | class mult_simple_test extends mult_base_test; 19 | `uvm_component_utils(mult_simple_test) 20 | 21 | function new(string name = "mult_simple_test", uvm_component parent = null); 22 | super.new(name, parent); 23 | endfunction 24 | 25 | virtual function void build_phase(uvm_phase phase); 26 | super.build_phase(phase); 27 | endfunction 28 | 29 | task run_phase(uvm_phase phase); 30 | mult_sequence seq_in0, seq_in1; 31 | 32 | phase.raise_objection(this); 33 | 34 | // Since the DUT uses separate interfaces for each input, we'll want 35 | // separate sequence instances for each. 36 | // 37 | // Note that since there is no sequence for the output interface, the 38 | // output agent's DUT does nothing. In later examples, we'll see a way 39 | // fully disable the driver. An agent that uses the driver is usually 40 | // known as a active agent, whereas an agent that doesn't is referred 41 | // to as a passive agent. 42 | // 43 | // Passive agents are not limited to output interfaces. We'll see 44 | // examples where we'll integrate an existing UVM setup for a module 45 | // into a higher-level test, of which the module is just one part. 46 | // In that case, the module is driven by other modules, so we want to 47 | // disable the agent's driver (make it passive), while still using the 48 | // monitor to check for errors. This is one of the biggest advantages of 49 | // UVM. When you have a UVM testbench for one module, you can resuse 50 | // within higher level tests to perform the same verification on each 51 | // individual module. 52 | seq_in0 = mult_sequence::type_id::create("seq_in0"); 53 | seq_in1 = mult_sequence::type_id::create("seq_in1"); 54 | 55 | // IMPORTANT: start() blocks until the sequence has finished, so we 56 | // can't call these sequentially otherwise we'll send all the test 57 | // values to one input without sending anything to the other. By 58 | // forking, each call to start() runs in parallel, so the sequences 59 | // arrive at the DUT at the same time. 60 | // 61 | // Note that as synchronization of multiple sequences becomes more 62 | // complex, you might want to consider a virtual sequence, which is 63 | // intended to handle complex situations involving synchronization of 64 | // multiple sequences, including different types of sequences. 65 | fork 66 | seq_in0.start(env.agent_in0.sequencer); 67 | seq_in1.start(env.agent_in1.sequencer); 68 | join 69 | 70 | phase.drop_objection(this); 71 | endtask 72 | 73 | endclass 74 | 75 | `endif 76 | -------------------------------------------------------------------------------- /testbenches/uvm/agents/mult_tb_pkg.sv: -------------------------------------------------------------------------------- 1 | package mult_tb_pkg; 2 | 3 | localparam int INPUT_WIDTH = 8; 4 | 5 | endpackage -------------------------------------------------------------------------------- /testbenches/uvm/agents/sources.txt: -------------------------------------------------------------------------------- 1 | axi4_stream_if.sv 2 | mult_tb_pkg.sv 3 | mult.sv 4 | mult_tb.sv 5 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Questa SystemVerilog simulation with UVM 2 | 3 | # Check if vsim exists in PATH 4 | ifeq (,$(shell which vsim)) 5 | $(error "vsim not found in PATH. Please ensure Questa is properly installed and added to PATH") 6 | endif 7 | 8 | # Tool and library configuration 9 | VLOG = vlog 10 | VSIM = vsim 11 | VOPT = vopt 12 | 13 | # Project configuration 14 | WORK_DIR = work 15 | TOP_MODULE = mult_tb 16 | OPTIMIZED_TOP = $(TOP_MODULE)_opt 17 | 18 | # UVM configuration 19 | UVM_TESTNAME ?= mult_simple_test 20 | UVM_FLAGS = +UVM_TESTNAME=$(UVM_TESTNAME) 21 | 22 | # Compilation flags 23 | VLOG_FLAGS = -sv \ 24 | -mfcu \ 25 | -lint \ 26 | +acc=pr \ 27 | -suppress 2275 \ 28 | -timescale "1ns/1ps" \ 29 | +define+UVM_PACKER_MAX_BYTES=1500000 \ 30 | +define+UVM_DISABLE_AUTO_ITEM_RECORDING \ 31 | -work $(WORK_DIR) 32 | 33 | # Optimization flags (preserve full visibility with +acc) 34 | VOPT_FLAGS = +acc \ 35 | -o $(OPTIMIZED_TOP) 36 | 37 | # Simulation flags 38 | VSIM_FLAGS = -c \ 39 | -debugDB \ 40 | -voptargs="+acc" \ 41 | +UVM_NO_RELNOTES \ 42 | +UVM_VERBOSITY=UVM_MEDIUM \ 43 | $(UVM_FLAGS) \ 44 | -do "run -all" 45 | 46 | # GUI simulation flags 47 | VSIM_GUI_FLAGS = -gui \ 48 | -debugDB \ 49 | -voptargs="+acc" \ 50 | +UVM_NO_RELNOTES \ 51 | +UVM_VERBOSITY=UVM_MEDIUM \ 52 | $(UVM_FLAGS) 53 | 54 | # Default target 55 | all: compile optimize 56 | 57 | # Create work library 58 | $(WORK_DIR): 59 | vlib $(WORK_DIR) 60 | vmap work $(WORK_DIR) 61 | 62 | # Read sources from file and compile 63 | compile: $(WORK_DIR) 64 | $(VLOG) $(VLOG_FLAGS) -f sources.txt 65 | 66 | # Optimize design while maintaining full visibility 67 | optimize: compile 68 | $(VOPT) $(TOP_MODULE) $(VOPT_FLAGS) 69 | 70 | # Run simulation in command-line mode 71 | sim: optimize 72 | @if [ "$(UVM_TESTNAME)" = "" ]; then \ 73 | echo "Error: UVM_TESTNAME is not set. Usage: make sim UVM_TESTNAME="; \ 74 | exit 1; \ 75 | fi 76 | $(VSIM) $(VSIM_FLAGS) $(OPTIMIZED_TOP) 77 | 78 | # Open GUI for interactive simulation 79 | gui: optimize 80 | @if [ "$(UVM_TESTNAME)" = "" ]; then \ 81 | echo "Error: UVM_TESTNAME is not set. Usage: make gui UVM_TESTNAME="; \ 82 | exit 1; \ 83 | fi 84 | $(VSIM) $(VSIM_GUI_FLAGS) $(OPTIMIZED_TOP) & 85 | 86 | # Clean up generated files 87 | clean: 88 | rm -rf $(WORK_DIR) 89 | rm -rf transcript 90 | rm -rf vsim.wlf 91 | rm -rf *.db 92 | rm -rf *.dbg 93 | rm -rf *.vstf 94 | rm -rf modelsim.ini 95 | 96 | .PHONY: all compile optimize sim gui clean 97 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/axi4_stream_agent.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This file implements a basic AXI4 stream agent for the interface 5 | // in axi4_stream_if.svh. We've now fully parameterized the agent to support 6 | // different widths for the data and sideband signals. 7 | 8 | `ifndef _AXI4_STREAM_AGENT_SVH_ 9 | `define _AXI4_STREAM_AGENT_SVH_ 10 | 11 | `include "uvm_macros.svh" 12 | import uvm_pkg::*; 13 | 14 | // In this example, 15 | class axi4_stream_agent #( 16 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 17 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 18 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 19 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 20 | ) extends uvm_agent; 21 | // We have to provide all the parameters when registering the class. 22 | `uvm_component_param_utils(axi4_stream_agent#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 23 | 24 | // Use all the parameters when declaring the sequencer, driver, and monitor. 25 | axi4_stream_sequencer #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) sequencer; 26 | axi4_stream_driver #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) driver; 27 | axi4_stream_monitor #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) monitor; 28 | 29 | function new(string name, uvm_component parent); 30 | super.new(name, parent); 31 | endfunction 32 | 33 | function void build_phase(uvm_phase phase); 34 | super.build_phase(phase); 35 | // Instantiate the sequencer, driver, and monitor. Note the use of 36 | // parameters here. 37 | sequencer = axi4_stream_sequencer#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)::type_id::create("sequencer", this); 38 | driver = axi4_stream_driver#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)::type_id::create("driver", this); 39 | monitor = axi4_stream_monitor#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)::type_id::create("monitor", this); 40 | endfunction 41 | 42 | function void connect_phase(uvm_phase phase); 43 | // Connect the driver to the sequencer. 44 | driver.seq_item_port.connect(sequencer.seq_item_export); 45 | endfunction 46 | endclass 47 | 48 | `endif 49 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/axi4_stream_driver.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // In this example, we fully parameterize the driver to handle different widths 5 | // for the data and sideband signals. 6 | 7 | `ifndef _AXI4_STREAM_DRIVER_SVH_ 8 | `define _AXI4_STREAM_DRIVER_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | class axi4_stream_driver #( 14 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 15 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 16 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 17 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 18 | ) extends uvm_driver #(axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)); 19 | // We need to provide all paramaeters when registering the class. 20 | `uvm_component_param_utils(axi4_stream_driver#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 21 | 22 | // We now have a fully parameterized virtual interface. 23 | virtual axi4_stream_if #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) vif; 24 | 25 | // Configuration parameters. 26 | int min_delay, max_delay; 27 | 28 | function new(string name, uvm_component parent); 29 | super.new(name, parent); 30 | min_delay = 1; 31 | max_delay = 1; 32 | endfunction 33 | 34 | function void build_phase(uvm_phase phase); 35 | super.build_phase(phase); 36 | endfunction 37 | 38 | function void set_delay(int min, int max); 39 | min_delay = min; 40 | max_delay = max; 41 | endfunction 42 | 43 | // Main driving logic. 44 | virtual task run_phase(uvm_phase phase); 45 | // The sequence item also requires all parameters. 46 | axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) req; 47 | 48 | // According to AXI spec, tvalid must be cleared on reset. 49 | vif.tvalid <= 1'b0; 50 | 51 | // Wait until reset has cleared to start driving. 52 | @(posedge vif.aclk iff !vif.aresetn); 53 | @(posedge vif.aclk iff vif.aresetn); 54 | @(posedge vif.aclk); 55 | 56 | forever begin 57 | // Get the sequence item. 58 | seq_item_port.get_next_item(req); 59 | 60 | // Drive the data onto the tdata port and assert tvalid. 61 | vif.tvalid <= 1'b1; 62 | vif.tdata <= req.tdata; 63 | vif.tstrb <= req.tstrb; 64 | vif.tkeep <= req.tkeep; 65 | vif.tlast <= req.tlast; 66 | vif.tid <= req.tid; 67 | vif.tdest <= req.tdest; 68 | vif.tuser <= req.tuser; 69 | 70 | // Hold the data and tvalid until tready is asserted. This is 71 | // required by the AXI spec. 72 | @(posedge vif.aclk iff vif.tready); 73 | 74 | // Clear tvalid for a random amount of cycles to enable more 75 | // thorough testing. 76 | vif.tvalid <= 1'b0; 77 | repeat ($urandom_range(min_delay - 1, max_delay - 1)) @(posedge vif.aclk); 78 | 79 | // Tell the sequencer we are done with the seq item. 80 | seq_item_port.item_done(); 81 | end 82 | endtask 83 | endclass 84 | 85 | 86 | `endif 87 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/axi4_stream_if.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // Wes Piard 3 | // University of Florida 4 | 5 | // In this example, the interface now supports all AXI4 stream signals. 6 | 7 | `ifndef _AXI4_STREAM_IF_ 8 | `define _AXI4_STREAM_IF_ 9 | 10 | interface axi4_stream_if 11 | import axi4_stream_pkg::*; 12 | #( 13 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 14 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 15 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 16 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 17 | ) ( 18 | input logic aclk, 19 | input logic aresetn 20 | ); 21 | logic tvalid; 22 | logic tready; 23 | logic [DATA_WIDTH-1:0] tdata; 24 | logic [DATA_WIDTH/8-1:0] tstrb; 25 | logic [DATA_WIDTH/8-1:0] tkeep; 26 | logic tlast; 27 | logic [ID_WIDTH-1:0] tid; 28 | logic [DEST_WIDTH-1:0] tdest; 29 | logic [USER_WIDTH-1:0] tuser; 30 | 31 | // AXI requires byte-aligned data widths, so confirm compliance here. 32 | initial begin 33 | if (DATA_WIDTH % 8 != 0) $fatal(1, $sformatf("AXI DATA_WIDTH=%0d is not byte aligned", DATA_WIDTH)); 34 | end 35 | 36 | // If using the interface for synthesis, this will probably cause errors. We 37 | // use it here so we can use `uvm_error in the interface assertion. 38 | `include "uvm_macros.svh" 39 | import uvm_pkg::*; 40 | 41 | // Validate required properties of AXI: once tvalid is asserted, it must remain asserted until 42 | // tready is asserted. 43 | assert property (@(posedge aclk) disable iff (!aresetn) $fell(tvalid) |-> $past(tready, 1)) 44 | else `uvm_error("ASSERT", "tvalid must be asserted continuously until tready is asserted."); 45 | 46 | endinterface 47 | 48 | `endif 49 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/axi4_stream_monitor.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // In this example, we fully parameterize the monitor to handle different widths 5 | // for the data and sideband signals. 6 | 7 | `ifndef _AXI4_STREAM_MONITOR_SVH_ 8 | `define _AXI4_STREAM_MONITOR_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | class axi4_stream_monitor #( 14 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 15 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 16 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 17 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 18 | ) extends uvm_monitor; 19 | // We have to pass all parameters when registering the class. 20 | `uvm_component_param_utils(axi4_stream_monitor#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 21 | 22 | // We now have a fully parameterized virtual interface. 23 | virtual axi4_stream_if #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) vif; 24 | 25 | // In the previous example, the monitor only sent the data, but now it has 26 | // to include all sideband information. Do support this, we send a sequence 27 | // item through the analysis port in case the sideband information is needed. 28 | uvm_analysis_port #(axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) ap; 29 | 30 | function new(string name, uvm_component parent); 31 | super.new(name, parent); 32 | 33 | // Create the anaylsis port. 34 | ap = new("ap", this); 35 | endfunction 36 | 37 | function void build_phase(uvm_phase phase); 38 | super.build_phase(phase); 39 | endfunction 40 | 41 | task run_phase(uvm_phase phase); 42 | // We need all the parameters when creating a sequence item. 43 | axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) item; 44 | 45 | forever begin 46 | @(posedge vif.aclk iff vif.tvalid && vif.tready); 47 | 48 | // The new has to be done within the loop. The write essentially 49 | // sends a pointer instead of a copy, so if we change the data 50 | // on the next iteration, it could corrupt what has been sent 51 | // through the analysis port. Instead, we need to make sure that 52 | // every item sent is a new item. SystemVerilog has garbage 53 | // collection, so you don't need to worry about deleting the items. 54 | item = new(); 55 | item.tdata = vif.tdata; 56 | item.tstrb = vif.tstrb; 57 | item.tkeep = vif.tkeep; 58 | item.tlast = vif.tlast; 59 | item.tid = vif.tid; 60 | item.tdest = vif.tdest; 61 | item.tuser = vif.tuser; 62 | ap.write(item); 63 | end 64 | endtask 65 | endclass 66 | 67 | `endif 68 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/axi4_stream_pkg.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This package provides default widths for the data and sideband signals. This 5 | // is very useful because all the components of the UVM agent will need a 6 | // default value. We don't want to hardcode those defaults in each component 7 | // because if one changes, it will cause problems. Using the package ensures 8 | // that all agent instances will use the same defaults. 9 | 10 | package axi4_stream_pkg; 11 | 12 | localparam int DEFAULT_DATA_WIDTH = 16; 13 | localparam int DEFAULT_ID_WIDTH = 4; 14 | localparam int DEFAULT_DEST_WIDTH = 4; 15 | localparam int DEFAULT_USER_WIDTH = 4; 16 | 17 | // Unlike the previous examples where we included the svh files everywhere 18 | // that a class was referenced (i.e., C++ style), we now instead use a 19 | // recommendation from the "UVM Cookbook" and include all corresponding 20 | // includes in the package. Note they have to be specified in the correct 21 | // compilation order. 22 | // 23 | // This style is convenient because it specifies all files related to the 24 | // agent in one place, and it eliminates the need to include the files 25 | // in other locations. Because the package file must be compiled first, 26 | // all the includes will now be compiled at the same time. 27 | `include "axi4_stream_seq_item.svh" 28 | `include "axi4_stream_monitor.svh" 29 | `include "axi4_stream_driver.svh" 30 | `include "axi4_stream_sequencer.svh" 31 | `include "axi4_stream_agent.svh" 32 | 33 | endpackage -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/axi4_stream_seq_item.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // In this example, we fully parameterize the sequence item to handle different 5 | // width for the data and sideband signals. 6 | 7 | `ifndef _AXI4_STREAM_ITEM_SVH_ 8 | `define _AXI4_STREAM_ITEM_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | // To handle different AXI4 stream instances using different widths, we need 14 | // to parameterize this class. 15 | class axi4_stream_seq_item #( 16 | parameter int DATA_WIDTH = 32, 17 | parameter int ID_WIDTH = 4, 18 | parameter int DEST_WIDTH = 4, 19 | parameter int USER_WIDTH = 4 20 | ) extends uvm_sequence_item; 21 | // // We need to provide all parameters when registering the class. 22 | `uvm_object_param_utils(axi4_stream_seq_item#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 23 | 24 | // We'll want to randomize the data, so we keep the rand keyword. 25 | rand logic [DATA_WIDTH-1:0] tdata; 26 | 27 | // The sideband signals have natural defaults, which we'll use in case the 28 | // corresponding test sequence doesn't assign them. 29 | logic [DATA_WIDTH/8-1:0] tstrb = '1; 30 | logic [DATA_WIDTH/8-1:0] tkeep = '1; 31 | logic tlast = 1'b0; 32 | logic [ID_WIDTH-1:0] tid = '0; 33 | logic [DEST_WIDTH-1:0] tdest = '0; 34 | logic [USER_WIDTH-1:0] tuser = '0; 35 | 36 | function new(string name = "axi4_stream_seq_item"); 37 | super.new(name); 38 | endfunction 39 | endclass 40 | 41 | `endif 42 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/axi4_stream_sequencer.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // In this example, we fully parameterize the sequencer to handle different widths 5 | // for the data and sideband signals. 6 | 7 | `ifndef _AXI4_STREAM_SEQUENCER_SVH_ 8 | `define _AXI4_STREAM_SEQUENCER_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | class axi4_stream_sequencer #( 14 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 15 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 16 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 17 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 18 | ) extends uvm_sequencer #(axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)); 19 | // We need to provide all parameters when registering the class. 20 | `uvm_component_param_utils(axi4_stream_sequencer#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 21 | 22 | function new(string name, uvm_component parent); 23 | super.new(name, parent); 24 | endfunction 25 | endclass 26 | 27 | `endif 28 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/mult_base_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This file provides a base class for other tests. 5 | 6 | `ifndef _MULT_BASE_TEST_SVH_ 7 | `define _MULT_BASE_TEST_SVH_ 8 | 9 | `include "uvm_macros.svh" 10 | import uvm_pkg::*; 11 | 12 | class mult_base_test extends uvm_test; 13 | `uvm_component_utils(mult_base_test) 14 | 15 | mult_env env; 16 | 17 | function new(string name = "mult_base_test", uvm_component parent = null); 18 | super.new(name, parent); 19 | endfunction 20 | 21 | virtual function void build_phase(uvm_phase phase); 22 | super.build_phase(phase); 23 | env = mult_env::type_id::create("env", this); 24 | endfunction 25 | 26 | virtual function void end_of_elaboration(); 27 | // Prints the UVM topology. 28 | print(); 29 | endfunction 30 | 31 | function void report_phase(uvm_phase phase); 32 | uvm_report_server svr; 33 | super.report_phase(phase); 34 | 35 | // The report server provides statistics about the simulation. 36 | svr = uvm_report_server::get_server(); 37 | 38 | // If there were any instances of uvm_fatal or uvm_error, then we will 39 | // consider that to be a failed test. 40 | if (svr.get_severity_count(UVM_FATAL) + svr.get_severity_count(UVM_ERROR) > 0) begin 41 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 42 | `uvm_info(get_type_name(), "--- TEST FAILED ---", UVM_NONE) 43 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 44 | end else begin 45 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 46 | `uvm_info(get_type_name(), "--- TEST PASSED ---", UVM_NONE) 47 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 48 | end 49 | 50 | // Add coverage summary 51 | $display("=== Coverage Summary ===\n"); 52 | $display("Input Bin Coverage: %.2f%%", env.input_coverage.input_coverage.get_coverage()); 53 | $display(" In0 Coverage: %.2f%%", env.input_coverage.input_coverage.in0_cp.get_coverage()); 54 | $display(" In0 Extremes Coverage: %.2f%%", env.input_coverage.input_coverage.in0_extremes_cp.get_coverage()); 55 | $display(" In1 Coverage: %.2f%%", env.input_coverage.input_coverage.in1_cp.get_coverage()); 56 | $display(" In1 Extremes Coverage: %.2f%%", env.input_coverage.input_coverage.in1_extremes_cp.get_coverage()); 57 | $display(" Cross Coverage: %.2f%%", env.input_coverage.input_coverage.in_cross.get_coverage()); 58 | 59 | $display("\nOutput Bin Coverage: %.2f%%", env.output_coverage.output_coverage.get_coverage()); 60 | 61 | $display("\nToggle Coverage"); 62 | $display(" In0 Toggle Coverage: %.2f%%", env.input_coverage.in0_toggle_coverage.toggle_cp.get_coverage()); 63 | $display(" In1 Toggle Coverage: %.2f%%", env.input_coverage.in1_toggle_coverage.toggle_cp.get_coverage()); 64 | 65 | 66 | endfunction 67 | 68 | endclass 69 | 70 | 71 | `endif 72 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/mult_sequence.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This class defines a sequence to test each multiplier input. It simply 5 | // creates random axi4_stream_seq_items. 6 | 7 | `ifndef _MULT_SEQUENCE_SVH_ 8 | `define _MULT_SEQUENCE_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | // Since the mult application does not require changing the default parameter values 14 | // of the interface (other than the DATA_WIDTH), this class requires no changes. 15 | class mult_sequence extends uvm_sequence #(axi4_stream_seq_item #(mult_tb_pkg::INPUT_WIDTH)); 16 | `uvm_object_utils(mult_sequence) 17 | 18 | int num_tests; 19 | 20 | function new(string name = "mult_sequence"); 21 | super.new(name); 22 | if (!uvm_config_db#(int)::get(this, "", "num_tests", num_tests)) `uvm_fatal("NO_NUM_TESTS", "num_tests not specified."); 23 | endfunction 24 | 25 | virtual task body(); 26 | for (int i = 0; i < num_tests; i++) begin 27 | req = axi4_stream_seq_item#(mult_tb_pkg::INPUT_WIDTH)::type_id::create($sformatf("req%0d", i)); 28 | wait_for_grant(); 29 | 30 | // Create a custom distribution to ensure that we achieve 100% 31 | // coverage, which requires testing with 0 and the maximum sized 32 | // values. 33 | // 34 | // IMPORTANT: Note that we are doing the randomization here instead 35 | // of in the sequence item class like before. The reason for this 36 | // is that the sequence item is AXI specific, so it doesn't make 37 | // sense to put application-specific constraints on the interface. 38 | void'(req.randomize() with { 39 | tdata dist { 40 | '0 :/ 2, 41 | '1 :/ 2, 42 | [0 : 2 ** mult_tb_pkg::INPUT_WIDTH - 2] :/ 96 43 | }; 44 | }); 45 | 46 | send_request(req); 47 | wait_for_item_done(); 48 | end 49 | endtask 50 | endclass 51 | 52 | 53 | `endif 54 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/mult_simple_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // A simple test for the multiplier that generates two sequences and sends them 5 | // to the DUT in parallel by using a fork. 6 | 7 | `ifndef _MULT_SIMPLE_TEST_SVH_ 8 | `define _MULT_SIMPLE_TEST_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | class mult_simple_test extends mult_base_test; 14 | `uvm_component_utils(mult_simple_test) 15 | 16 | function new(string name = "mult_simple_test", uvm_component parent = null); 17 | super.new(name, parent); 18 | endfunction 19 | 20 | virtual function void build_phase(uvm_phase phase); 21 | super.build_phase(phase); 22 | endfunction 23 | 24 | task run_phase(uvm_phase phase); 25 | mult_sequence seq_in0, seq_in1; 26 | 27 | phase.raise_objection(this); 28 | 29 | // Since the DUT uses separate interfaces for each input, we'll want 30 | // separate sequence instances for each. 31 | // 32 | // Note that since there is no sequence for the output interface, the 33 | // output agent's DUT does nothing. In later examples, we'll see a way 34 | // fully disable the driver. An agent that uses the driver is usually 35 | // known as a active agent, whereas an agent that doesn't is referred 36 | // to as a passive agent. 37 | // 38 | // Passive agents are not limited to output interfaces. We'll see 39 | // examples where we'll integrate an existing UVM setup for a module 40 | // into a higher-level test, of which the module is just one part. 41 | // In that case, the module is driven by other modules, so we want to 42 | // disable the agent's driver (make it passive), while still using the 43 | // monitor to check for errors. This is one of the biggest advantages of 44 | // UVM. When you have a UVM testbench for one module, you can resuse 45 | // within higher level tests to perform the same verification on each 46 | // individual module. 47 | seq_in0 = mult_sequence::type_id::create("seq_in0"); 48 | seq_in1 = mult_sequence::type_id::create("seq_in1"); 49 | 50 | // IMPORTANT: start() blocks until the sequence has finished, so we 51 | // can't call these sequentially otherwise we'll send all the test 52 | // values to one input without sending anything to the other. By 53 | // forking, each call to start() runs in parallel, so the sequences 54 | // arrive at the DUT at the same time. 55 | // 56 | // Note that as synchronization of multiple sequences becomes more 57 | // complex, you might want to consider a virtual sequence, which is 58 | // intended to handle complex situations involving synchronization of 59 | // multiple sequences, including different types of sequences. 60 | fork 61 | seq_in0.start(env.agent_in0.sequencer); 62 | seq_in1.start(env.agent_in1.sequencer); 63 | join 64 | 65 | phase.drop_objection(this); 66 | endtask 67 | 68 | endclass 69 | 70 | `endif 71 | -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/mult_tb_pkg.sv: -------------------------------------------------------------------------------- 1 | package mult_tb_pkg; 2 | 3 | localparam int INPUT_WIDTH = 16; 4 | 5 | import axi4_stream_pkg::*; 6 | 7 | // Like the axi4_stream_pkg, we imitate the strategy of including all 8 | // relevant includes in a package. Again, these must be specified in the 9 | // correct compilation order. 10 | `include "mult_sequence.svh" 11 | `include "mult_coverage.svh" 12 | `include "mult_scoreboard.svh" 13 | `include "mult_env.svh" 14 | `include "mult_base_test.svh" 15 | `include "mult_simple_test.svh" 16 | 17 | endpackage -------------------------------------------------------------------------------- /testbenches/uvm/agents_parameterized/sources.txt: -------------------------------------------------------------------------------- 1 | axi4_stream_if.sv 2 | axi4_stream_pkg.sv 3 | mult_tb_pkg.sv 4 | mult.sv 5 | mult_tb.sv 6 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Questa SystemVerilog simulation with UVM 2 | 3 | # Check if vsim exists in PATH 4 | ifeq (,$(shell which vsim)) 5 | $(error "vsim not found in PATH. Please ensure Questa is properly installed and added to PATH") 6 | endif 7 | 8 | # Tool and library configuration 9 | VLOG = vlog 10 | VSIM = vsim 11 | VOPT = vopt 12 | 13 | # Project configuration 14 | WORK_DIR = work 15 | TOP_MODULE = bit_diff_tb 16 | OPTIMIZED_TOP = $(TOP_MODULE)_opt 17 | 18 | # UVM configuration 19 | UVM_TESTNAME ?= bit_diff_simple_test 20 | UVM_FLAGS = +UVM_TESTNAME=$(UVM_TESTNAME) 21 | 22 | # Compilation flags 23 | VLOG_FLAGS = -sv \ 24 | -mfcu \ 25 | -lint \ 26 | +acc=pr \ 27 | -suppress 2275 \ 28 | -timescale "1ns/1ps" \ 29 | +define+UVM_PACKER_MAX_BYTES=1500000 \ 30 | +define+UVM_DISABLE_AUTO_ITEM_RECORDING \ 31 | -work $(WORK_DIR) 32 | 33 | # Optimization flags (preserve full visibility with +acc) 34 | VOPT_FLAGS = +acc \ 35 | -o $(OPTIMIZED_TOP) 36 | 37 | # Simulation flags 38 | VSIM_FLAGS = -c \ 39 | -debugDB \ 40 | -voptargs="+acc" \ 41 | +UVM_NO_RELNOTES \ 42 | +UVM_VERBOSITY=UVM_MEDIUM \ 43 | $(UVM_FLAGS) \ 44 | -do "run -all" 45 | 46 | # GUI simulation flags 47 | VSIM_GUI_FLAGS = -gui \ 48 | -debugDB \ 49 | -voptargs="+acc" \ 50 | +UVM_NO_RELNOTES \ 51 | +UVM_VERBOSITY=UVM_MEDIUM \ 52 | $(UVM_FLAGS) 53 | 54 | # Default target 55 | all: compile optimize 56 | 57 | # Create work library 58 | $(WORK_DIR): 59 | vlib $(WORK_DIR) 60 | vmap work $(WORK_DIR) 61 | 62 | # Read sources from file and compile 63 | compile: $(WORK_DIR) 64 | $(VLOG) $(VLOG_FLAGS) -f sources.txt 65 | 66 | # Optimize design while maintaining full visibility 67 | optimize: compile 68 | $(VOPT) $(TOP_MODULE) $(VOPT_FLAGS) 69 | 70 | # Run simulation in command-line mode 71 | sim: optimize 72 | @if [ "$(UVM_TESTNAME)" = "" ]; then \ 73 | echo "Error: UVM_TESTNAME is not set. Usage: make sim UVM_TESTNAME="; \ 74 | exit 1; \ 75 | fi 76 | $(VSIM) $(VSIM_FLAGS) $(OPTIMIZED_TOP) 77 | 78 | # Open GUI for interactive simulation 79 | gui: optimize 80 | @if [ "$(UVM_TESTNAME)" = "" ]; then \ 81 | echo "Error: UVM_TESTNAME is not set. Usage: make gui UVM_TESTNAME="; \ 82 | exit 1; \ 83 | fi 84 | $(VSIM) $(VSIM_GUI_FLAGS) $(OPTIMIZED_TOP) & 85 | 86 | # Clean up generated files 87 | clean: 88 | rm -rf $(WORK_DIR) 89 | rm -rf transcript 90 | rm -rf vsim.wlf 91 | rm -rf *.db 92 | rm -rf *.dbg 93 | rm -rf *.vstf 94 | rm -rf modelsim.ini 95 | 96 | .PHONY: all compile optimize sim gui clean 97 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_agent.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // A UVM agent is essentially what most people would refer to as a BFM. It 5 | // combines monitors and drivers for a specific interface. While the advantages 6 | // of an agent might not seem obvious for an application-specific interface, 7 | // they become more clear when applied to a more general interface like AXI. 8 | // For something like AXI, once you have an AXI agent, you can reuse it to get 9 | // get monitoring and driving capabilities for any DUT that uses AXI. You might 10 | // also have a DUT with multiple AXI interfaces, for which you could just 11 | // instantiate multiple agents. Essentially, the advantage of an agent is the 12 | // ability to reuse an interface's monitors and drivers. 13 | 14 | `ifndef _BIT_DIFF_AGENT_SVH_ 15 | `define _BIT_DIFF_AGENT_SVH_ 16 | 17 | `include "uvm_macros.svh" 18 | import uvm_pkg::*; 19 | 20 | `include "bit_diff_item.svh" 21 | `include "bit_diff_sequencer.svh" 22 | `include "bit_diff_sequence.svh" 23 | `include "bit_diff_driver.svh" 24 | `include "bit_diff_monitor.svh" 25 | 26 | class bit_diff_agent extends uvm_agent; 27 | `uvm_component_utils(bit_diff_agent) 28 | 29 | // A UVM agent provides drivers, monitors, and a sequencer that will enable 30 | // a test to send sequences to the driver. 31 | bit_diff_driver driver; 32 | bit_diff_sequencer sequencer; 33 | bit_diff_start_monitor start_monitor; 34 | bit_diff_done_monitor done_monitor; 35 | 36 | function new(string name, uvm_component parent); 37 | super.new(name, parent); 38 | endfunction 39 | 40 | function void build_phase(uvm_phase phase); 41 | super.build_phase(phase); 42 | 43 | // Use the factory to create instances of the monitors, driver, and sequencer. 44 | start_monitor = bit_diff_start_monitor::type_id::create("start_monitor", this); 45 | done_monitor = bit_diff_done_monitor::type_id::create("done_monitor", this); 46 | driver = bit_diff_driver::type_id::create("driver", this); 47 | sequencer = bit_diff_sequencer::type_id::create("sequencer", this); 48 | endfunction 49 | 50 | // Connect the driver to the sequencer. 51 | function void connect_phase(uvm_phase phase); 52 | driver.seq_item_port.connect(sequencer.seq_item_export); 53 | endfunction 54 | 55 | endclass 56 | 57 | `endif 58 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_base_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // Finally, we get to the last class we need, which is a uvm_test. The test 5 | // is the top-level of the UVM hiearchy. In general, a test will instantiate 6 | // and configure the environment based on the needs of the test. It will then 7 | // create some number of sequences that get driven to the DUT, where those 8 | // sequences are configured based on the specifics of each test. 9 | // 10 | // In this file, we don't create an actual test, but instead create a test 11 | // base class that will provide the functionality we need for any other test. 12 | // In this case, that common functionality is creating the environment. It also 13 | // demonstrates other functionality, such as printing the UVM topology and 14 | // reporting statistics upon completion. 15 | 16 | `ifndef _BIT_DIFF_BASE_TEST_SVH_ 17 | `define _BIT_DIFF_BASE_TEST_SVH_ 18 | 19 | `include "uvm_macros.svh" 20 | import uvm_pkg::*; 21 | 22 | `include "bit_diff_env.svh" 23 | 24 | class bit_diff_base_test extends uvm_test; 25 | `uvm_component_utils(bit_diff_base_test) 26 | 27 | bit_diff_env env; 28 | 29 | function new(string name = "bit_diff_base_test", uvm_component parent = null); 30 | super.new(name, parent); 31 | endfunction 32 | 33 | virtual function void build_phase(uvm_phase phase); 34 | super.build_phase(phase); 35 | env = bit_diff_env::type_id::create("env", this); 36 | endfunction 37 | 38 | virtual function void end_of_elaboration(); 39 | // Prints the UVM topology. 40 | print(); 41 | endfunction 42 | 43 | function void report_phase(uvm_phase phase); 44 | uvm_report_server svr; 45 | super.report_phase(phase); 46 | 47 | // The report server provides statistics about the simulation. 48 | svr = uvm_report_server::get_server(); 49 | 50 | // If there were any instances of uvm_fatal or uvm_error, then we will 51 | // consider that to be a failed test. 52 | if (svr.get_severity_count(UVM_FATAL) + svr.get_severity_count(UVM_ERROR) > 0) begin 53 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 54 | `uvm_info(get_type_name(), "--- TEST FAILED ---", UVM_NONE) 55 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 56 | end else begin 57 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 58 | `uvm_info(get_type_name(), "--- TEST PASSED ---", UVM_NONE) 59 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 60 | end 61 | endfunction 62 | 63 | endclass 64 | 65 | 66 | `endif 67 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_driver.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _BIT_DIFF_DRIVER_SVH_ 5 | `define _BIT_DIFF_DRIVER_SVH_ 6 | 7 | `include "uvm_macros.svh" 8 | import uvm_pkg::*; 9 | 10 | `include "bit_diff_item.svh" 11 | 12 | class bit_diff_driver extends uvm_driver #(bit_diff_item); 13 | `uvm_component_utils(bit_diff_driver) 14 | 15 | // The driver also needs the virtual interface, which it obtains via the 16 | // uvm_config_db. 17 | virtual bit_diff_if vif; 18 | 19 | function new(string name, uvm_component parent); 20 | super.new(name, parent); 21 | endfunction 22 | 23 | function void build_phase(uvm_phase phase); 24 | super.build_phase(phase); 25 | if (!uvm_config_db#(virtual bit_diff_if)::get(this, "", "vif", vif)) `uvm_fatal("NO_VIF", {"Virtual interface must be set for: ", get_full_name()}); 26 | endfunction 27 | 28 | virtual task run_phase(uvm_phase phase); 29 | 30 | // Wait until the design has been reset before driving any transactions. 31 | @(posedge vif.clk iff vif.rst); 32 | @(posedge vif.clk iff !vif.rst); 33 | 34 | // The driver runs for ever, waiting for sequence items from a sequence/ 35 | // sequencer. Upon receiving a sequence item, it then drives that 36 | // transaction onto the DUT interface, and then notices the sequencer 37 | // that it is done with the transaction. 38 | forever begin 39 | // Request a new sequence item from the sequencer. 40 | seq_item_port.get_next_item(req); 41 | 42 | // Drive the DUT by converting transaction signals to DUT interface 43 | // signals. For our simple transaction, the sequence item provides 44 | // the data input for which the DUT should compute the bit_diff. 45 | // The only missing information is the go signal, which the DUT 46 | // drives automatically when receiving the data. 47 | vif.data <= req.data; 48 | vif.go <= 1'b1; 49 | @(posedge vif.clk); 50 | vif.go <= 1'b0; 51 | @(posedge vif.clk); 52 | 53 | // We need to wait for done before driving the next transaction 54 | // otherwise the DUT will ignore it. 55 | vif.wait_for_done(); 56 | 57 | // Wait some amount of time between tests. Not necessary, just for 58 | // demonstration. In many cases, it might be good to randomize this 59 | // amount, or to have it provided by the sequence item itself. 60 | repeat(5) @(posedge vif.clk); 61 | 62 | // Notify the sequencer that the current sequence item has been 63 | // completed, allowing the sequencer to proceed with the next item 64 | // in the sequence or handle other tasks. 65 | seq_item_port.item_done(); 66 | end 67 | endtask 68 | endclass 69 | 70 | 71 | `endif 72 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_env.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // A UVM environment is essentially the organizational unit that groups together 5 | // all the components involved in verification. In general, the environment 6 | // create an agents that are needed, creates the scoreboard, and the connects 7 | // the ports that are used within those components. In many cases, an 8 | // environment will also provide configuration options that will make more sense 9 | // once we get to more complex verification examples. 10 | // 11 | // For this example, the environment instantiates the agent and scoreboard (via 12 | // the UVM factory). It also instantiates to uvm_tlm_fifos to connect the ports 13 | // in the agent's monitors with the ports in the scoreboard. 14 | 15 | `ifndef _BIT_DIFF_ENV_SVH_ 16 | `define _BIT_DIFF_ENV_SVH_ 17 | 18 | `include "uvm_macros.svh" 19 | import uvm_pkg::*; 20 | 21 | `include "bit_diff_agent.svh" 22 | `include "bit_diff_scoreboard.svh" 23 | 24 | class bit_diff_env extends uvm_env; 25 | `uvm_component_utils(bit_diff_env) 26 | 27 | bit_diff_agent agent; 28 | bit_diff_scoreboard scoreboard; 29 | 30 | uvm_tlm_fifo #(int) start_fifo, done_fifo; 31 | 32 | function new(string name, uvm_component parent); 33 | super.new(name, parent); 34 | endfunction 35 | 36 | function void build_phase(uvm_phase phase); 37 | super.build_phase(phase); 38 | // Create the agent and scoreboard. 39 | agent = bit_diff_agent::type_id::create("bit_diff_agent", this); 40 | scoreboard = bit_diff_scoreboard::type_id::create("bit_diff_scoreboard", this); 41 | 42 | // Create the FIFOs used to communicate between the monitors and scoreboard. 43 | start_fifo = new("start_fifo", this, 8); 44 | done_fifo = new("done_fifo", this, 8); 45 | endfunction 46 | 47 | // Connect the FIFOs to the monitor and scoreboard ports. 48 | function void connect_phase(uvm_phase phase); 49 | agent.start_monitor.start_port.connect(start_fifo.put_export); 50 | scoreboard.start_port.connect(start_fifo.get_export); 51 | agent.done_monitor.done_port.connect(done_fifo.put_export); 52 | scoreboard.done_port.connect(done_fifo.get_export); 53 | endfunction 54 | 55 | endclass 56 | 57 | `endif 58 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_if.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // Interface for the bit_diff module. This has been cleanedup up from the 5 | // previous testbench examples do demonstrate what are generally considered 6 | // good practices. Also, I've removed the BFM name from earlier examples, even 7 | // though the interface does provide monitoring and driving tasks normally 8 | // associated with BFMs. I did this because the UVM agent will take over the 9 | // BFM responsibilties (with assistance from the interface methods). 10 | 11 | `ifndef _BIT_DIFF_IF_ 12 | `define _BIT_DIFF_IF_ 13 | 14 | import bit_diff_if_pkg::*; 15 | 16 | interface bit_diff_if #( 17 | // Providing the default width based off a package in order to simplify 18 | // use of a parameterized interface withing UVM. This can be convenient, but 19 | // only works when the UVM testbench needs all instances of the interface use 20 | // the same width. If different widths are needed for different instances, 21 | // we'll need to expand the testbench, as shown in later examples. 22 | parameter int WIDTH = bit_diff_if_pkg::WIDTH 23 | ) ( 24 | input logic clk 25 | ); 26 | logic rst, go, done; 27 | logic [WIDTH-1:0] data; 28 | logic signed [$clog2(2*WIDTH+1)-1:0] result; 29 | 30 | task automatic reset(int cycles); 31 | rst <= 1'b1; 32 | go <= 1'b0; 33 | repeat (cycles) @(posedge clk); 34 | @(negedge clk); 35 | rst <= 1'b0; 36 | @(posedge clk); 37 | endtask 38 | 39 | // Start the DUT with the specified data by creating a 1-cycle pulse on go. 40 | task automatic start(input logic [WIDTH-1:0] data_); 41 | data <= data_; 42 | go <= 1'b1; 43 | @(posedge clk); 44 | go <= 1'b0; 45 | endtask 46 | 47 | // Detect when an execution has completed. 48 | task automatic wait_for_done(); 49 | @(posedge clk iff (!done)); 50 | @(posedge clk iff (done)); 51 | endtask 52 | 53 | // Detect when the DUT starts executing. This task internally tracks the 54 | // active status of the DUT and returns every time it becomes active. 55 | task automatic wait_for_start(); 56 | static logic is_active = 1'b0; 57 | 58 | forever begin 59 | @(posedge clk); 60 | if (rst) is_active = 1'b0; 61 | else begin 62 | if (done) is_active = 1'b0; 63 | if (!is_active && go) begin 64 | is_active = 1'b1; 65 | break; 66 | end 67 | end 68 | end 69 | endtask 70 | 71 | endinterface 72 | 73 | `endif 74 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_if_pkg.sv: -------------------------------------------------------------------------------- 1 | // This package defines interface parameters that are needed for instantiations 2 | // of classes, iterfaces, and modules. All other parameters can be passed around 3 | // via the config DB, but the config DB cannot be used to provide class, module, 4 | // and interface parameters since those need to be known at compile time. 5 | // 6 | // We could make these parameters to the testbench module, but then we would 7 | // need to add parameters to the uvm_test classes. This is possible, but is 8 | // usually so tedious that using a package like this is an acceptable tradeoff. 9 | 10 | package bit_diff_if_pkg; 11 | localparam int WIDTH = 16; 12 | endpackage 13 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_item.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // In UVM, the transaction is generally defined by a "sequence item." The 5 | // sequence item contains all the fields that are needed to communicate with 6 | // the DUT, at whatever level of abstraction you choose. An abstract sequence 7 | // item will look nothing like the DUT's interface, whereas a low-level sequence 8 | // item might look nearly identical to the DUT's interface. 9 | 10 | `ifndef _BIT_DIFF_ITEM_SVH_ 11 | `define _BIT_DIFF_ITEM_SVH_ 12 | 13 | `include "uvm_macros.svh" 14 | import uvm_pkg::*; 15 | 16 | import bit_diff_if_pkg::*; 17 | 18 | class bit_diff_item extends uvm_sequence_item; 19 | // Get the width from the testbench package. This avoids having to parameterize 20 | // this class, which would in turn require parameters every place where this 21 | // class is used. 22 | localparam int WIDTH = bit_diff_if_pkg::WIDTH; 23 | 24 | // For now, our sequence item solely contains the data input for which the 25 | // DUT will compute the bit_diff. All other DUT I/O are omitted, with the 26 | // go input being generated automaticall by the driver. 27 | rand bit [WIDTH-1:0] data; 28 | 29 | // This macro is used to declare that the class bit_diff_item is a UVM 30 | // object. It sets up the necessary UVM internals to support object 31 | // management, such as object creation, printing, and comparison. It also 32 | // registers the class with the factory. 33 | `uvm_object_utils_begin(bit_diff_item) 34 | 35 | // This macro is used to register a member variable, data, as a field in 36 | // the bit_diff_item class. In this case, UVM_ALL_ON means that all 37 | // features related to this field are enabled (e.g., printing, comparing, 38 | // and copying of the field). 39 | `uvm_field_int(data, UVM_ALL_ON) 40 | `uvm_object_utils_end 41 | 42 | function new(string name = "bit_diff_item"); 43 | super.new(name); 44 | endfunction 45 | 46 | endclass 47 | 48 | `endif 49 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_monitor.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | `ifndef _BIT_DIFF_MONITOR_SVH_ 5 | `define _BIT_DIFF_MONITOR_SVH_ 6 | 7 | `include "uvm_macros.svh" 8 | import uvm_pkg::*; 9 | 10 | `include "bit_diff_item.svh" 11 | 12 | // Here we create a "virtual" base class that will contain functionality 13 | // required by all monitors. A virtual class cannot be instantiated directly 14 | // and is only use by other classes to inherit common functionality. 15 | // 16 | // This class contains the virtual interface, which it obtains via the 17 | // config_db during the build phase. 18 | virtual class bit_diff_base_monitor extends uvm_monitor; 19 | // The following macro is needed by every class derived from a uvm_compontent 20 | // in order to register the class with the UVM factory. 21 | `uvm_component_utils(bit_diff_base_monitor) 22 | 23 | virtual bit_diff_if vif; 24 | 25 | function new(string name, uvm_component parent); 26 | super.new(name, parent); 27 | endfunction 28 | 29 | function void build_phase(uvm_phase phase); 30 | super.build_phase(phase); 31 | if (!uvm_config_db#(virtual bit_diff_if)::get(this, "", "vif", vif)) `uvm_fatal("NO_VIF", {"Virtual interface must be set for: ", get_full_name()}); 32 | endfunction 33 | 34 | pure virtual task run_phase(uvm_phase phase); 35 | endclass 36 | 37 | 38 | // The done monitor inherits from the base monitor to acquire the interface. 39 | // It then declares a port that is used to communicate with the scoreboard, 40 | // while also implementing the detection of done events during the run phase. 41 | class bit_diff_done_monitor extends bit_diff_base_monitor; 42 | `uvm_component_utils(bit_diff_done_monitor) 43 | 44 | uvm_blocking_put_port #(int) done_port; 45 | 46 | function new(string name, uvm_component parent); 47 | super.new(name, parent); 48 | done_port = new("done_port", this); 49 | endfunction 50 | 51 | function void build_phase(uvm_phase phase); 52 | super.build_phase(phase); 53 | endfunction 54 | 55 | // The done monitor by using the interface's wait_for_done task. 56 | task run_phase(uvm_phase phase); 57 | forever begin 58 | // Use the interface's task to detect when an execution has finished. 59 | vif.wait_for_done(); 60 | //`uvm_info("DONE_MONITOR", "Detected completed execution", UVM_LOW) 61 | 62 | // Send the result to the scoreboard via through the done port. 63 | done_port.put(vif.result); 64 | end 65 | endtask 66 | endclass 67 | 68 | 69 | class bit_diff_start_monitor extends bit_diff_base_monitor; 70 | `uvm_component_utils(bit_diff_start_monitor) 71 | 72 | uvm_blocking_put_port #(int) start_port; 73 | 74 | function new(string name, uvm_component parent); 75 | super.new(name, parent); 76 | start_port = new("start_port", this); 77 | endfunction 78 | 79 | function void build_phase(uvm_phase phase); 80 | super.build_phase(phase); 81 | endfunction 82 | 83 | task run_phase(uvm_phase phase); 84 | forever begin 85 | // Use the interface's task to detect when an execution has started. 86 | vif.wait_for_start(); 87 | //`uvm_info("START_MONITOR", $sformatf("Detected new execution for data=%0h", vif.data), UVM_LOW) 88 | 89 | // Send the input to the scoreboard via the start_port. 90 | start_port.put(vif.data); 91 | end 92 | endtask 93 | endclass 94 | 95 | 96 | `endif 97 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_scoreboard.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // The scoreboard waits to receive an input from the start monitor and the 5 | // actual output from the done monitor. It then uses the input to compute the 6 | // expected output. Finally, it compares the expected output with the actual 7 | // output, and reports errors if there are any differences. 8 | 9 | `ifndef _BIT_DIFF_SCOREBOARD_SVH_ 10 | `define _BIT_DIFF_SCOREBOARD_SVH_ 11 | 12 | `include "uvm_macros.svh" 13 | import uvm_pkg::*; 14 | 15 | import bit_diff_if_pkg::*; 16 | 17 | class bit_diff_scoreboard extends uvm_scoreboard; 18 | `uvm_component_utils(bit_diff_scoreboard) 19 | 20 | // Ports for communicating with the monitors. 21 | uvm_blocking_get_port #(int) start_port, done_port; 22 | int passed, failed; 23 | 24 | function new(string name, uvm_component parent); 25 | super.new(name, parent); 26 | passed = 0; 27 | failed = 0; 28 | endfunction 29 | 30 | function void build_phase(uvm_phase phase); 31 | super.build_phase(phase); 32 | start_port = new("start_port", this); 33 | done_port = new("done_port", this); 34 | endfunction 35 | 36 | // Reference model for computing the expected output. 37 | function automatic int model(int data, int width = bit_diff_if_pkg::WIDTH); 38 | int diff = 0; 39 | for (int i = 0; i < width; i++) begin 40 | diff = data[0] ? diff + 1 : diff - 1; 41 | data = data >> 1; 42 | end 43 | return diff; 44 | endfunction 45 | 46 | virtual task run_phase(uvm_phase phase); 47 | int input_data, actual, expected; 48 | 49 | forever begin 50 | // Wait to receive the input from the start monitor and the actual 51 | // output from the done monitor. 52 | start_port.get(input_data); 53 | done_port.get(actual); 54 | 55 | expected = model(input_data); 56 | if (actual == expected) begin 57 | `uvm_info("SCOREBOARD", $sformatf("Test passed for data=h%h.", input_data), UVM_LOW) 58 | passed++; 59 | end else begin 60 | `uvm_error("SCOREBOARD", $sformatf("Test failed: result=%0d instead of %0d for data=h%h", actual, expected, input_data)) 61 | failed++; 62 | end 63 | end 64 | endtask 65 | 66 | endclass 67 | 68 | `endif 69 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_sequence.svh: -------------------------------------------------------------------------------- 1 | `ifndef _BIT_DIFF_SEQUENCE_SVH_ 2 | `define _BIT_DIFF_SEQUENCE_SVH_ 3 | 4 | `include "uvm_macros.svh" 5 | import uvm_pkg::*; 6 | 7 | `include "bit_diff_item.svh" 8 | `include "bit_diff_sequencer.svh" 9 | 10 | class bit_diff_sequence extends uvm_sequence#(bit_diff_item); 11 | `uvm_object_utils(bit_diff_sequence) 12 | 13 | int num_tests; 14 | 15 | function new(string name="bit_diff_sequence"); 16 | super.new(name); 17 | 18 | if (!uvm_config_db#(int)::get(this, "", "num_tests", num_tests)) `uvm_fatal("NO_NUM_TESTS", "num_tests not specified."); 19 | endfunction 20 | 21 | virtual task body(); 22 | for (int i=0; i < num_tests; i++) begin 23 | // Create a sequence item (i.e., transaction) to send to the driver. 24 | req = bit_diff_item::type_id::create($sformatf("req%0d", i)); 25 | 26 | // Wait for the sequencer to give permission to send a request. 27 | wait_for_grant(); 28 | 29 | // Randomize our transaction. This is normally where most of the 30 | // sequence logic goes, but in our case, we just want a sequence 31 | // of randomized transacations, based on the constraints specified 32 | // in bit_diff_item. 33 | req.randomize(); 34 | 35 | // Send the request to the sequencer to send to the driver. 36 | send_request(req); 37 | 38 | // Wait for the sequencer to tell us that the sequencer item 39 | // has completed. 40 | wait_for_item_done(); 41 | end 42 | endtask 43 | endclass 44 | 45 | 46 | `endif 47 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_sequencer.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // A UVM sequencer is responsible for managing the flow of sequence items from 5 | // potentially multiple sequences into a driver. It provides mechanisms to 6 | // control and manage the execution of these sequences. 7 | // 8 | // Sequencers can become quite complex when dealing with intricate interfaces 9 | // that require specific timings for sequence items, which may be of different 10 | // types. 11 | 12 | // Fortunately, in many cases, when you’re feeding a single type of sequence to 13 | // a driver, the uvm_sequencer class already provides all the necessary 14 | // functionality. This is why, in this example, the sequencer extends the 15 | // uvm_sequencer class without adding any additional functionality. 16 | 17 | `ifndef _BIT_DIFF_SEQUENCER_SVH_ 18 | `define _BIT_DIFF_SEQUENCER_SVH_ 19 | 20 | `include "uvm_macros.svh" 21 | import uvm_pkg::*; 22 | 23 | `include "bit_diff_item.svh" 24 | 25 | class bit_diff_sequencer extends uvm_sequencer #(bit_diff_item); 26 | `uvm_component_utils(bit_diff_sequencer) 27 | 28 | function new(string name, uvm_component parent); 29 | super.new(name, parent); 30 | endfunction 31 | endclass 32 | 33 | 34 | `endif 35 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/bit_diff_simple_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // Here we have our first actual test. This particular test gets all of its 5 | // functionality from the bit_diff_sequence. It simply creates and instance 6 | // of that sequence, starts it via the sequencer, and then wait for the 7 | // sequencer to finish sending it to the driver. 8 | 9 | `ifndef _BIT_DIFF_SIMPLE_TEST_SVH_ 10 | `define _BIT_DIFF_SIMPLE_TEST_SVH_ 11 | 12 | `include "uvm_macros.svh" 13 | import uvm_pkg::*; 14 | 15 | `include "bit_diff_base_test.svh" 16 | 17 | class bit_diff_simple_test extends bit_diff_base_test; 18 | `uvm_component_utils(bit_diff_simple_test) 19 | 20 | function new(string name = "bit_diff_simple_test", uvm_component parent = null); 21 | super.new(name, parent); 22 | endfunction 23 | 24 | virtual function void build_phase(uvm_phase phase); 25 | // We no longer need to build the environment because it is built by 26 | // the base test when we call this: 27 | super.build_phase(phase); 28 | endfunction 29 | 30 | task run_phase(uvm_phase phase); 31 | bit_diff_sequence seq; 32 | 33 | // The objection mechanism in UVM is a way to coordinate and synchronize 34 | // the completion of phases. It's essentially a mechanism to prevent the 35 | // simulation from progressing to the next phase until all the components 36 | // that need to be finished in the current phase are done. 37 | // 38 | // Raising an objection here tells UVM that this component is has not 39 | // finished with the current phase and that it is blocking the transition 40 | // to the next phase. 41 | phase.raise_objection(this); 42 | 43 | // Our test creates a single sequence, which as we saw earlier 44 | // sends num_tests bit_diff_items to the driver. 45 | seq = bit_diff_sequence::type_id::create("seq"); 46 | seq.start(env.agent.sequencer); 47 | 48 | // Dropping the objection allows the phase to end. 49 | phase.drop_objection(this); 50 | endtask 51 | 52 | endclass 53 | 54 | `endif 55 | -------------------------------------------------------------------------------- /testbenches/uvm/basics/sources.txt: -------------------------------------------------------------------------------- 1 | bit_diff_if_pkg.sv 2 | bit_diff_if.sv 3 | bit_diff.sv 4 | bit_diff_tb.sv 5 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Questa SystemVerilog simulation with UVM 2 | 3 | # Check if vsim exists in PATH 4 | ifeq (,$(shell which vsim)) 5 | $(error "vsim not found in PATH. Please ensure Questa is properly installed and added to PATH") 6 | endif 7 | 8 | # Tool and library configuration 9 | VLOG = vlog 10 | VSIM = vsim 11 | VOPT = vopt 12 | 13 | # Project configuration 14 | WORK_DIR = work 15 | TOP_MODULE = accum_tb 16 | OPTIMIZED_TOP = $(TOP_MODULE)_opt 17 | 18 | # UVM configuration 19 | UVM_TESTNAME ?= accum_single_beat_test 20 | UVM_FLAGS = +UVM_TESTNAME=$(UVM_TESTNAME) 21 | 22 | # Compilation flags 23 | VLOG_FLAGS = -sv \ 24 | -mfcu \ 25 | -lint \ 26 | +acc=pr \ 27 | -suppress 2275 \ 28 | -timescale "1ns/1ps" \ 29 | +define+UVM_PACKER_MAX_BYTES=1500000 \ 30 | +define+UVM_DISABLE_AUTO_ITEM_RECORDING \ 31 | -work $(WORK_DIR) 32 | 33 | # Optimization flags (preserve full visibility with +acc) 34 | VOPT_FLAGS = +acc \ 35 | -o $(OPTIMIZED_TOP) 36 | 37 | # Simulation flags 38 | VSIM_FLAGS = -c \ 39 | -debugDB \ 40 | -voptargs="+acc" \ 41 | +UVM_NO_RELNOTES \ 42 | +UVM_VERBOSITY=UVM_MEDIUM \ 43 | $(UVM_FLAGS) \ 44 | -do "run -all" 45 | 46 | # GUI simulation flags 47 | VSIM_GUI_FLAGS = -gui \ 48 | -debugDB \ 49 | -voptargs="+acc" \ 50 | +UVM_NO_RELNOTES \ 51 | +UVM_VERBOSITY=UVM_MEDIUM \ 52 | $(UVM_FLAGS) 53 | 54 | # Default target 55 | all: compile optimize 56 | 57 | # Create work library 58 | $(WORK_DIR): 59 | vlib $(WORK_DIR) 60 | vmap work $(WORK_DIR) 61 | 62 | # Read sources from file and compile 63 | compile: $(WORK_DIR) 64 | $(VLOG) $(VLOG_FLAGS) -f sources.txt 65 | 66 | # Optimize design while maintaining full visibility 67 | optimize: compile 68 | $(VOPT) $(TOP_MODULE) $(VOPT_FLAGS) 69 | 70 | # Run simulation in command-line mode 71 | sim: optimize 72 | @if [ "$(UVM_TESTNAME)" = "" ]; then \ 73 | echo "Error: UVM_TESTNAME is not set. Usage: make sim UVM_TESTNAME="; \ 74 | exit 1; \ 75 | fi 76 | $(VSIM) $(VSIM_FLAGS) $(OPTIMIZED_TOP) 77 | 78 | # Open GUI for interactive simulation 79 | gui: optimize 80 | @if [ "$(UVM_TESTNAME)" = "" ]; then \ 81 | echo "Error: UVM_TESTNAME is not set. Usage: make gui UVM_TESTNAME="; \ 82 | exit 1; \ 83 | fi 84 | $(VSIM) $(VSIM_GUI_FLAGS) $(OPTIMIZED_TOP) & 85 | 86 | # Clean up generated files 87 | clean: 88 | rm -rf $(WORK_DIR) 89 | rm -rf transcript 90 | rm -rf vsim.wlf 91 | rm -rf *.db 92 | rm -rf *.dbg 93 | rm -rf *.vstf 94 | rm -rf modelsim.ini 95 | 96 | .PHONY: all compile optimize sim gui clean 97 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/accum.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This module implements an AXI4 stream accumulator that accumulates separate 5 | // groups. Each grouop is provided in a packet where the last input in the 6 | // packet is specified by the assertion of tlast. 7 | 8 | // Like the previous example, this modules does not "break" the ready signal. 9 | // It chained together with multiple modules that also do not break ready, the 10 | // ready chain will become a timing bottleneck. 11 | 12 | module accum #( 13 | parameter int INPUT_WIDTH = 16, 14 | parameter int OUTPUT_WIDTH = 32 15 | ) ( 16 | input logic aclk, 17 | input logic arst_n, 18 | 19 | // AXI4 stream interface for the input. 20 | input logic in_tvalid, 21 | output logic in_tready, 22 | input logic [INPUT_WIDTH-1:0] in_tdata, 23 | input logic in_tlast, 24 | 25 | // AXI4 stream interface for the output 26 | output logic out_tvalid, 27 | input logic out_tready, 28 | output logic [OUTPUT_WIDTH-1:0] out_tdata, 29 | output logic out_tlast 30 | ); 31 | logic en; 32 | logic out_valid_r; 33 | logic out_tlast_r; 34 | logic first_r; 35 | logic [OUTPUT_WIDTH-1:0] accum_r; 36 | 37 | initial if (INPUT_WIDTH % 8 != 0) $fatal(1, $sformatf("AXI requires INPUT_WIDTH (%0d) to be byte aligned", INPUT_WIDTH)); 38 | initial if (OUTPUT_WIDTH % 8 != 0) $fatal(1, $sformatf("AXI requires OUTPUT_WIDTH (%0d) to be byte aligned", OUTPUT_WIDTH)); 39 | 40 | // Enable/disable the pipeline. AXI streaming is a little weird and can't 41 | // simply stall on !out_tready. The spec says that a transmitter cannot 42 | // wait for tready to assert tvalid, so here we enable the pipeline anytime 43 | // the output isn't valid. We only stall when !out_tready && out_tvalid. 44 | assign en = out_tready || !out_tvalid; 45 | 46 | // Ready is combinational logic, which can lead to timing problems. This 47 | // can easily be fixed with a register and FIFO/skid buffer, which I 48 | // avoided to keep the code simple. The main point of the example is to 49 | // illustrate various UVM concepts. 50 | assign in_tready = en; 51 | 52 | always_ff @(posedge aclk) begin 53 | if (en) begin 54 | out_valid_r <= in_tvalid; 55 | out_tlast_r <= in_tlast; 56 | 57 | // Don't accumulate unless the input is valid. 58 | if (in_tvalid) begin 59 | first_r <= in_tlast; 60 | 61 | // For the first input in a packet, don't add the previous value. 62 | if (first_r) accum_r <= OUTPUT_WIDTH'(in_tdata); 63 | else accum_r <= accum_r + OUTPUT_WIDTH'(in_tdata); 64 | end 65 | end 66 | 67 | if (!arst_n) begin 68 | first_r <= 1'b1; 69 | out_valid_r <= 1'b0; 70 | out_tlast_r <= 1'b0; 71 | end 72 | end 73 | 74 | assign out_tvalid = out_valid_r; 75 | assign out_tdata = accum_r; 76 | assign out_tlast = out_tlast_r; 77 | 78 | endmodule 79 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/accum_base_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This file provides a base class for other tests. 5 | 6 | `ifndef _ACCUM_BASE_TEST_SVH_ 7 | `define _ACCUM_BASE_TEST_SVH_ 8 | 9 | `include "uvm_macros.svh" 10 | import uvm_pkg::*; 11 | 12 | class accum_base_test extends uvm_test; 13 | `uvm_component_utils(accum_base_test) 14 | 15 | accum_env env; 16 | 17 | function new(string name = "accum_base_test", uvm_component parent = null); 18 | super.new(name, parent); 19 | endfunction 20 | 21 | virtual function void build_phase(uvm_phase phase); 22 | super.build_phase(phase); 23 | env = accum_env::type_id::create("env", this); 24 | endfunction 25 | 26 | virtual function void end_of_elaboration(); 27 | // Prints the UVM topology. 28 | print(); 29 | endfunction 30 | 31 | function void report_phase(uvm_phase phase); 32 | uvm_report_server svr; 33 | super.report_phase(phase); 34 | 35 | // The report server provides statistics about the simulation. 36 | svr = uvm_report_server::get_server(); 37 | 38 | // If there were any instances of uvm_fatal or uvm_error, then we will 39 | // consider that to be a failed test. 40 | if (svr.get_severity_count(UVM_FATAL) + svr.get_severity_count(UVM_ERROR) > 0) begin 41 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 42 | `uvm_info(get_type_name(), "--- TEST FAILED ---", UVM_NONE) 43 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 44 | end else begin 45 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 46 | `uvm_info(get_type_name(), "--- TEST PASSED ---", UVM_NONE) 47 | `uvm_info(get_type_name(), "---------------------------", UVM_NONE) 48 | end 49 | 50 | // Add coverage summary 51 | $display("=== Coverage Summary ===\n"); 52 | $display("Input Bin Coverage: %.2f%%", env.input_coverage.input_coverage.get_coverage()); 53 | $display(" In Coverage: %.2f%%", env.input_coverage.input_coverage.in_cp.get_coverage()); 54 | $display(" In Extremes Coverage: %.2f%%", env.input_coverage.input_coverage.in_extremes_cp.get_coverage()); 55 | 56 | $display("\nOutput Bin Coverage: %.2f%%", env.output_coverage.output_coverage.get_coverage()); 57 | 58 | $display("\nToggle Coverage"); 59 | $display(" In Toggle Coverage: %.2f%%", env.input_coverage.in_toggle_coverage.cg.toggle_cp.get_coverage()); 60 | $display(" Out Toggle Coverage: %.2f%%", env.output_coverage.out_toggle_coverage.cg.toggle_cp.get_coverage()); 61 | 62 | endfunction 63 | 64 | endclass 65 | 66 | 67 | `endif 68 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/accum_env.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // The environment is very similar to previous examples and has only been 5 | // updated for the new DUT and the different number of interfaces. It also 6 | // provides a function to configure the transaction abstraction level in each 7 | // agent. 8 | 9 | `ifndef _ACCUM_ENV_SVH_ 10 | `define _ACCUM_ENV_SVH_ 11 | 12 | `include "uvm_macros.svh" 13 | import uvm_pkg::*; 14 | 15 | class accum_env extends uvm_env; 16 | `uvm_component_utils(accum_env) 17 | 18 | axi4_stream_agent #(accum_tb_pkg::INPUT_WIDTH) agent_in; 19 | axi4_stream_agent #(accum_tb_pkg::OUTPUT_WIDTH) agent_out; 20 | 21 | accum_scoreboard scoreboard; 22 | 23 | accum_input_coverage input_coverage; 24 | accum_output_coverage output_coverage; 25 | 26 | virtual axi4_stream_if #(accum_tb_pkg::INPUT_WIDTH) in_vif; 27 | virtual axi4_stream_if #(accum_tb_pkg::OUTPUT_WIDTH) out_vif; 28 | 29 | // Configuration information for the drivers. 30 | int min_driver_delay; 31 | int max_driver_delay; 32 | 33 | function new(string name, uvm_component parent); 34 | super.new(name, parent); 35 | endfunction 36 | 37 | function automatic void configure_transaction_level(bit is_packet_level); 38 | agent_in.configure_transaction_level(is_packet_level); 39 | agent_out.configure_transaction_level(is_packet_level); 40 | scoreboard.is_packet_level = is_packet_level; 41 | endfunction 42 | 43 | function void build_phase(uvm_phase phase); 44 | super.build_phase(phase); 45 | agent_in = axi4_stream_agent#(accum_tb_pkg::INPUT_WIDTH)::type_id::create("agent_in", this); 46 | agent_out = axi4_stream_agent#(accum_tb_pkg::OUTPUT_WIDTH)::type_id::create("agent_out", this); 47 | scoreboard = accum_scoreboard::type_id::create("scoreboard", this); 48 | 49 | input_coverage = accum_input_coverage::type_id::create("input_coverage", this); 50 | output_coverage = accum_output_coverage::type_id::create("output_coverage", this); 51 | 52 | if (!uvm_config_db#(virtual axi4_stream_if #(accum_tb_pkg::INPUT_WIDTH))::get(this, "", "in_vif", in_vif)) `uvm_fatal("NO_VIF", {"Virtual interface must be set for: ", get_full_name()}); 53 | if (!uvm_config_db#(virtual axi4_stream_if #(accum_tb_pkg::OUTPUT_WIDTH))::get(this, "", "out_vif", out_vif)) `uvm_fatal("NO_VIF", {"Virtual interface must be set for: ", get_full_name()}); 54 | 55 | // Read the driver configuration information. 56 | if (!uvm_config_db#(int)::get(this, "", "min_driver_delay", min_driver_delay)) min_driver_delay = 1; 57 | if (!uvm_config_db#(int)::get(this, "", "max_driver_delay", max_driver_delay)) max_driver_delay = 1; 58 | endfunction 59 | 60 | function void connect_phase(uvm_phase phase); 61 | // Connect the virtual interfaces to each agent's driver and monitor. 62 | agent_in.driver.vif = in_vif; 63 | agent_in.monitor.vif = in_vif; 64 | agent_out.driver.vif = out_vif; 65 | agent_out.monitor.vif = out_vif; 66 | 67 | // Connect the analysis ports (ap) and exports (ae). Note that the 68 | // scoreboard internally uses analysis FIFOs, so we don't need to 69 | // setup the FIFOs here like we did in the previous example. 70 | agent_in.monitor.ap.connect(scoreboard.in_ae); 71 | agent_out.monitor.ap.connect(scoreboard.out_ae); 72 | 73 | // Configure the driver. 74 | agent_in.driver.set_delay(min_driver_delay, max_driver_delay); 75 | 76 | // Connect the coverage classes. Note that any analysis port can be 77 | // sent to any consumer. 78 | agent_in.monitor.ap.connect(input_coverage.in_ae); 79 | agent_out.monitor.ap.connect(output_coverage.out_ae); 80 | endfunction 81 | 82 | endclass 83 | 84 | `endif 85 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/accum_packet_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This class tests the DUT with packet-level transactions. 5 | 6 | `ifndef _ACCUM_PACKET_TEST_SVH_ 7 | `define _ACCUM_PACKET_TEST_SVH_ 8 | 9 | class accum_packet_test extends accum_base_test; 10 | `uvm_component_utils(accum_packet_test) 11 | 12 | function new(string name = "accum_packet_test", uvm_component parent = null); 13 | super.new(name, parent); 14 | endfunction 15 | 16 | virtual function void build_phase(uvm_phase phase); 17 | super.build_phase(phase); 18 | endfunction 19 | 20 | task run_phase(uvm_phase phase); 21 | accum_packet_sequence seq; 22 | phase.raise_objection(this); 23 | 24 | seq = accum_packet_sequence::type_id::create("seq"); 25 | seq.start(env.agent_in.sequencer); 26 | 27 | phase.drop_objection(this); 28 | endtask 29 | 30 | endclass 31 | 32 | `endif 33 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/accum_scoreboard.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This scoreboard is similar to the previous examples, but includes logic 5 | // to compute the expected output for both single beats and packets. 6 | 7 | `ifndef _ACCUM_SCOREBOARD_SVH_ 8 | `define _ACCUM_SCOREBOARD_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | class accum_scoreboard extends uvm_scoreboard; 14 | `uvm_component_utils(accum_scoreboard) 15 | 16 | uvm_analysis_export #(axi4_stream_seq_item #(accum_tb_pkg::INPUT_WIDTH)) in_ae; 17 | uvm_analysis_export #(axi4_stream_seq_item #(accum_tb_pkg::OUTPUT_WIDTH)) out_ae; 18 | 19 | uvm_tlm_analysis_fifo #(axi4_stream_seq_item #(accum_tb_pkg::INPUT_WIDTH)) in_fifo; 20 | uvm_tlm_analysis_fifo #(axi4_stream_seq_item #(accum_tb_pkg::OUTPUT_WIDTH)) out_fifo; 21 | 22 | int passed, failed; 23 | bit is_packet_level; 24 | 25 | function new(string name, uvm_component parent); 26 | super.new(name, parent); 27 | passed = 0; 28 | failed = 0; 29 | is_packet_level = 0; 30 | endfunction 31 | 32 | function void build_phase(uvm_phase phase); 33 | super.build_phase(phase); 34 | 35 | // Create the analysis exports. 36 | in_ae = new("in_ae", this); 37 | out_ae = new("out_ae", this); 38 | 39 | // Create the analysis FIFOs. 40 | in_fifo = new("in_fifo", this); 41 | out_fifo = new("out_fifo", this); 42 | endfunction 43 | 44 | function void connect_phase(uvm_phase phase); 45 | in_ae.connect(in_fifo.analysis_export); 46 | out_ae.connect(out_fifo.analysis_export); 47 | endfunction 48 | 49 | virtual task run_phase(uvm_phase phase); 50 | logic [accum_tb_pkg::OUTPUT_WIDTH-1:0] actual, expected; 51 | axi4_stream_seq_item #(accum_tb_pkg::INPUT_WIDTH) in_item; 52 | axi4_stream_seq_item #(accum_tb_pkg::OUTPUT_WIDTH) out_item; 53 | 54 | in_item = new(); 55 | out_item = new(); 56 | expected = '0; 57 | 58 | forever begin 59 | in_fifo.get(in_item); 60 | // Make sure that the scoreboard is configured to match the items. 61 | assert (in_item.is_packet_level == is_packet_level); 62 | out_fifo.get(out_item); 63 | 64 | if (is_packet_level) begin 65 | // Compute the expected and actual results across the entire 66 | // packet. 67 | expected = '0; 68 | actual = '0; 69 | foreach (in_item.tdata[i]) expected += in_item.tdata[i]; 70 | foreach (out_item.tdata[i]) actual += out_item.tdata[i]; 71 | end else begin 72 | // Compute the expected and actual results for each beat. 73 | actual = out_item.tdata[0]; 74 | expected += in_item.tdata[0]; 75 | end 76 | 77 | // Check for errors. 78 | if (actual === expected) begin 79 | `uvm_info("SCOREBOARD", $sformatf("Test passed."), UVM_LOW) 80 | passed++; 81 | end else begin 82 | `uvm_error("SCOREBOARD", $sformatf("Test failed: result=%0d instead of %0d.", actual, expected)) 83 | failed++; 84 | end 85 | 86 | // Make sure the last item of each packet asserts tlast. 87 | if ((is_packet_level || in_item.tlast) && out_item.tlast !== 1'b1) begin 88 | `uvm_error("SCOREBOARD", $sformatf("Test failed: tlast not asserted at end of packet.")) 89 | failed++; 90 | end 91 | 92 | // Clear the expected at the end of each packet. 93 | if (in_item.tlast) expected = 0; 94 | end 95 | endtask 96 | 97 | endclass 98 | 99 | `endif 100 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/accum_single_beat_test.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This class tests the accumulator using transactions consisting of single 5 | // beats, as opposed to entire packets. 6 | 7 | `ifndef _ACCUM_SINGLE_BEAT_TEST_SVH_ 8 | `define _ACCUM_SINGLE_BEAT_TEST_SVH_ 9 | 10 | class accum_single_beat_test extends accum_base_test; 11 | `uvm_component_utils(accum_single_beat_test) 12 | 13 | function new(string name = "accum_single_beat_test", uvm_component parent = null); 14 | super.new(name, parent); 15 | endfunction 16 | 17 | virtual function void build_phase(uvm_phase phase); 18 | super.build_phase(phase); 19 | endfunction 20 | 21 | task run_phase(uvm_phase phase); 22 | accum_beat_sequence seq; 23 | phase.raise_objection(this); 24 | 25 | seq = accum_beat_sequence::type_id::create("seq"); 26 | seq.start(env.agent_in.sequencer); 27 | 28 | phase.drop_objection(this); 29 | endtask 30 | 31 | endclass 32 | 33 | `endif 34 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/accum_tb_pkg.sv: -------------------------------------------------------------------------------- 1 | // Used to specify the parameter configuration for the DUT. 2 | 3 | package accum_tb_pkg; 4 | 5 | localparam int INPUT_WIDTH = 16; 6 | localparam int OUTPUT_WIDTH = 32; 7 | 8 | import axi4_stream_pkg::*; 9 | 10 | `include "accum_sequence.svh" 11 | `include "accum_coverage.svh" 12 | `include "accum_scoreboard.svh" 13 | `include "accum_env.svh" 14 | `include "accum_base_test.svh" 15 | `include "accum_single_beat_test.svh" 16 | `include "accum_packet_test.svh" 17 | 18 | endpackage 19 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/axi4_stream_agent.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This file is nearly identical to the previous example. It simply adds the 5 | // configure_transaction_level function to configure the driver and monitor. 6 | 7 | `ifndef _AXI4_STREAM_AGENT_SVH_ 8 | `define _AXI4_STREAM_AGENT_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | class axi4_stream_agent #( 14 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 15 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 16 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 17 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 18 | ) extends uvm_agent; 19 | `uvm_component_param_utils(axi4_stream_agent#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 20 | 21 | axi4_stream_sequencer #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) sequencer; 22 | axi4_stream_driver #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) driver; 23 | axi4_stream_monitor #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) monitor; 24 | 25 | function new(string name, uvm_component parent); 26 | super.new(name, parent); 27 | endfunction 28 | 29 | // Helper function to configure the transaction level of the driver and 30 | // monitor. 31 | function automatic void configure_transaction_level(bit is_packet_level); 32 | driver.is_packet_level = is_packet_level; 33 | monitor.is_packet_level = is_packet_level; 34 | endfunction 35 | 36 | function void build_phase(uvm_phase phase); 37 | super.build_phase(phase); 38 | sequencer = axi4_stream_sequencer#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)::type_id::create("sequencer", this); 39 | driver = axi4_stream_driver#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)::type_id::create("driver", this); 40 | monitor = axi4_stream_monitor#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)::type_id::create("monitor", this); 41 | endfunction 42 | 43 | function void connect_phase(uvm_phase phase); 44 | driver.seq_item_port.connect(sequencer.seq_item_export); 45 | endfunction 46 | endclass 47 | 48 | `endif 49 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/axi4_stream_driver.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // In this version of the driver, we add driving capabilities for both individual 5 | // AXI items/beats, and for entire packets of items. 6 | 7 | `ifndef _AXI4_STREAM_DRIVER_SVH_ 8 | `define _AXI4_STREAM_DRIVER_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | class axi4_stream_driver #( 14 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 15 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 16 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 17 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 18 | ) extends uvm_driver #(axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)); 19 | // We need to provide all paramaeters when registering the class. 20 | `uvm_component_param_utils(axi4_stream_driver#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 21 | 22 | // We now have a fully parameterized virtual interface. 23 | virtual axi4_stream_if #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) vif; 24 | 25 | // Configuration parameters. 26 | int min_delay, max_delay; 27 | 28 | // Specifies the transaction level. 29 | bit is_packet_level; 30 | 31 | function new(string name, uvm_component parent); 32 | super.new(name, parent); 33 | min_delay = 1; 34 | max_delay = 1; 35 | is_packet_level = 0; 36 | endfunction 37 | 38 | function void build_phase(uvm_phase phase); 39 | super.build_phase(phase); 40 | endfunction 41 | 42 | function void set_delay(int min, int max); 43 | min_delay = min; 44 | max_delay = max; 45 | endfunction 46 | 47 | // Main driving logic. 48 | virtual task run_phase(uvm_phase phase); 49 | // The sequence item also requires all parameters. 50 | axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) req; 51 | 52 | // According to AXI spec, tvalid must be cleared on reset. 53 | vif.tvalid <= 1'b0; 54 | 55 | // Wait until reset has cleared to start driving. 56 | @(posedge vif.aclk iff !vif.aresetn); 57 | @(posedge vif.aclk iff vif.aresetn); 58 | @(posedge vif.aclk); 59 | 60 | forever begin 61 | seq_item_port.get_next_item(req); 62 | 63 | // By carefully designing the sequence item, most of the differences 64 | // between beat-level and packet-level transactions are captured by 65 | // the size of the tdata array. 66 | for (int i = 0; i < req.tdata.size(); i++) begin 67 | vif.tvalid <= 1'b1; 68 | vif.tdata <= req.tdata[i]; 69 | vif.tstrb <= req.tstrb[i]; 70 | vif.tkeep <= req.tkeep[i]; 71 | // Set tlast differently based on the abstraction level. 72 | vif.tlast <= req.is_packet_level ? i == req.tdata.size()-1 : req.tlast; 73 | vif.tid <= req.tid; 74 | vif.tdest <= req.tdest; 75 | vif.tuser <= req.tuser; 76 | @(posedge vif.aclk iff vif.tready); 77 | 78 | vif.tvalid <= 1'b0; 79 | repeat ($urandom_range(min_delay - 1, max_delay - 1)) @(posedge vif.aclk); 80 | end 81 | 82 | seq_item_port.item_done(); 83 | end 84 | endtask 85 | endclass 86 | 87 | 88 | `endif 89 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/axi4_stream_if.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // Wes Piard 3 | // University of Florida 4 | 5 | interface axi4_stream_if 6 | import axi4_stream_pkg::*; 7 | #( 8 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 9 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 10 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 11 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 12 | ) ( 13 | input logic aclk, 14 | input logic aresetn 15 | ); 16 | logic tvalid; 17 | logic tready; 18 | logic [DATA_WIDTH-1:0] tdata; 19 | logic [DATA_WIDTH/8-1:0] tstrb; 20 | logic [DATA_WIDTH/8-1:0] tkeep; 21 | logic tlast; 22 | logic [ID_WIDTH-1:0] tid; 23 | logic [DEST_WIDTH-1:0] tdest; 24 | logic [USER_WIDTH-1:0] tuser; 25 | 26 | // AXI requires byte-aligned data widths, so confirm compliance here. 27 | initial begin 28 | if (DATA_WIDTH % 8 != 0) $fatal(1, $sformatf("AXI DATA_WIDTH=%0d is not byte aligned", DATA_WIDTH)); 29 | end 30 | 31 | // If using the interface for synthesis, this will probably cause errors. 32 | `include "uvm_macros.svh" 33 | import uvm_pkg::*; 34 | 35 | // Validate required properties of AXI: once tvalid is asserted, it must remain asserted until 36 | // tready is asserted. 37 | assert property (@(posedge aclk) disable iff (!aresetn) $fell(tvalid) |-> $past(tready, 1)) 38 | else `uvm_error("ASSERT", "tvalid must be asserted continuously until tready is asserted."); 39 | 40 | endinterface 41 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/axi4_stream_monitor.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // In this example, we add the capability of monitoring individual beats and 5 | // entire packets. 6 | 7 | `ifndef _AXI4_STREAM_MONITOR_SVH_ 8 | `define _AXI4_STREAM_MONITOR_SVH_ 9 | 10 | `include "uvm_macros.svh" 11 | import uvm_pkg::*; 12 | 13 | class axi4_stream_monitor #( 14 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 15 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 16 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 17 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 18 | ) extends uvm_monitor; 19 | `uvm_component_param_utils(axi4_stream_monitor#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 20 | 21 | virtual axi4_stream_if #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) vif; 22 | 23 | uvm_analysis_port #(axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) ap; 24 | 25 | // Specifies the transaction level. 26 | bit is_packet_level; 27 | 28 | function new(string name, uvm_component parent); 29 | super.new(name, parent); 30 | ap = new("ap", this); 31 | is_packet_level = 0; 32 | endfunction 33 | 34 | function void build_phase(uvm_phase phase); 35 | super.build_phase(phase); 36 | endfunction 37 | 38 | task run_phase(uvm_phase phase); 39 | axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) item; 40 | axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) packet_item; 41 | 42 | // A queue of items used to build a complete packet. 43 | axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) packet[$]; 44 | 45 | forever begin 46 | @(posedge vif.aclk iff vif.tvalid && vif.tready); 47 | 48 | // NOTE: The new has to be done within the loop. The write essentially 49 | // sends a pointer instead of a copy, so if we change the data 50 | // on the next iteration, it could corrupt what has been sent 51 | // through the analysis port. Instead, we need to make sure that 52 | // every item sent is a new item. SystemVerilog has garbage 53 | // collection, so you don't need to worry about deleting the items. 54 | 55 | // Store each individual beat as an item. 56 | item = new(); 57 | item.tdata[0] = vif.tdata; 58 | item.tstrb[0] = vif.tstrb; 59 | item.tkeep[0] = vif.tkeep; 60 | item.tlast = vif.tlast; 61 | item.tid = vif.tid; 62 | item.tdest = vif.tdest; 63 | item.tuser = vif.tuser; 64 | 65 | // If the simulation is at the packet level, push the individual 66 | // beat onto the queue until receiving the last beat of the packet. 67 | if (is_packet_level) begin 68 | packet.push_back(item); 69 | 70 | if (vif.tlast) begin 71 | // Create and send the entire packet at once. 72 | packet_item = new(); 73 | packet_item.init_from_queue(packet); 74 | ap.write(packet_item); 75 | 76 | // Clear the queue for the next packet. 77 | packet.delete(); 78 | end 79 | end else begin 80 | // If the transactions are single beats, then send each 81 | // individual item. 82 | ap.write(item); 83 | end 84 | end 85 | endtask 86 | endclass 87 | 88 | `endif 89 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/axi4_stream_pkg.sv: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This package provides default widths for the data and sideband signals. This 5 | // is very useful because all the components of the UVM agent will need a 6 | // default value. We don't want to hardcode those defaults in each component 7 | // because if one changes, it will cause problems. Using the package ensures 8 | // that all agent instances will use the same defaults. 9 | 10 | package axi4_stream_pkg; 11 | 12 | localparam int DEFAULT_DATA_WIDTH = 16; 13 | localparam int DEFAULT_ID_WIDTH = 4; 14 | localparam int DEFAULT_DEST_WIDTH = 4; 15 | localparam int DEFAULT_USER_WIDTH = 4; 16 | 17 | `include "axi4_stream_seq_item.svh" 18 | `include "axi4_stream_monitor.svh" 19 | `include "axi4_stream_driver.svh" 20 | `include "axi4_stream_sequencer.svh" 21 | `include "axi4_stream_agent.svh" 22 | 23 | endpackage -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/axi4_stream_seq_item.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // In this example, we modify the sequence item to be able to function at 5 | // multiple abstraction levels. The item will now support individual beats and 6 | // entire packets. 7 | 8 | `ifndef _AXI4_STREAM_ITEM_SVH_ 9 | `define _AXI4_STREAM_ITEM_SVH_ 10 | 11 | `include "uvm_macros.svh" 12 | import uvm_pkg::*; 13 | 14 | class axi4_stream_seq_item #( 15 | parameter int DATA_WIDTH = 32, 16 | parameter int ID_WIDTH = 4, 17 | parameter int DEST_WIDTH = 4, 18 | parameter int USER_WIDTH = 4 19 | ) extends uvm_sequence_item; 20 | `uvm_object_param_utils(axi4_stream_seq_item#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 21 | 22 | // In this example, we add a bit to specify if the sequence item captures a 23 | // transaction that is an entire packet, or an individual beat of a packet. 24 | bit is_packet_level; 25 | 26 | // We change these to dynamic arrays so they can store any number of beats. 27 | // For single-beat transactions, these will be allocated with a single element. 28 | rand logic [DATA_WIDTH-1:0] tdata[]; 29 | rand logic [DATA_WIDTH/8-1:0] tstrb[]; 30 | rand logic [DATA_WIDTH/8-1:0] tkeep[]; 31 | 32 | rand logic tlast = 1'b0; 33 | 34 | // These are not arrays under the assumption that all items in a packet 35 | // will have the same sideband values. 36 | rand logic [ID_WIDTH-1:0] tid = '0; 37 | rand logic [DEST_WIDTH-1:0] tdest = '0; 38 | rand logic [USER_WIDTH-1:0] tuser = '0; 39 | 40 | // Add a constraint to guarantee all three dynamic arrays are the same size. 41 | constraint size_c { 42 | tstrb.size() == tdata.size(); 43 | tkeep.size() == tdata.size(); 44 | } 45 | 46 | function new(string name = "axi4_stream_seq_item"); 47 | super.new(name); 48 | // By default, we'll use individual beats. 49 | is_packet_level = 1'b0; 50 | tdata = new[1]; 51 | tstrb = new[1]; 52 | tkeep = new[1]; 53 | tstrb[0] = '1; 54 | tkeep[0] = '1; 55 | endfunction 56 | 57 | // Helper function to build a packet from a queue of individual items. 58 | function automatic void init_from_queue(axi4_stream_seq_item#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH) q[$]); 59 | if (q.size() == 0) return; 60 | 61 | tdata = new[q.size()]; 62 | tstrb = new[q.size()]; 63 | tkeep = new[q.size()]; 64 | 65 | foreach (q[i]) begin 66 | tdata[i] = q[i].tdata[0]; 67 | tstrb[i] = q[i].tstrb[0]; 68 | tkeep[i] = q[i].tkeep[0]; 69 | end 70 | 71 | tid = q[0].tid; 72 | tdest = q[0].tid; 73 | tuser = q[0].tid; 74 | 75 | is_packet_level = 1'b1; 76 | endfunction 77 | endclass 78 | 79 | `endif 80 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/axi4_stream_sequencer.svh: -------------------------------------------------------------------------------- 1 | // Greg Stitt 2 | // University of Florida 3 | 4 | // This file is unchanged from the previous example. 5 | 6 | `ifndef _AXI4_STREAM_SEQUENCER_SVH_ 7 | `define _AXI4_STREAM_SEQUENCER_SVH_ 8 | 9 | `include "uvm_macros.svh" 10 | import uvm_pkg::*; 11 | 12 | class axi4_stream_sequencer #( 13 | parameter int DATA_WIDTH = axi4_stream_pkg::DEFAULT_DATA_WIDTH, 14 | parameter int ID_WIDTH = axi4_stream_pkg::DEFAULT_ID_WIDTH, 15 | parameter int DEST_WIDTH = axi4_stream_pkg::DEFAULT_DEST_WIDTH, 16 | parameter int USER_WIDTH = axi4_stream_pkg::DEFAULT_USER_WIDTH 17 | ) extends uvm_sequencer #(axi4_stream_seq_item #(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)); 18 | // We need to provide all parameters when registering the class. 19 | `uvm_component_param_utils(axi4_stream_sequencer#(DATA_WIDTH, ID_WIDTH, DEST_WIDTH, USER_WIDTH)) 20 | 21 | function new(string name, uvm_component parent); 22 | super.new(name, parent); 23 | endfunction 24 | endclass 25 | 26 | `endif 27 | -------------------------------------------------------------------------------- /testbenches/uvm/multiple_tests/sources.txt: -------------------------------------------------------------------------------- 1 | axi4_stream_if.sv 2 | axi4_stream_pkg.sv 3 | accum_tb_pkg.sv 4 | accum.sv 5 | accum_tb.sv 6 | --------------------------------------------------------------------------------