From ebe1a33be0d49cd148b44cf5e5810bb5a0b6d419 Mon Sep 17 00:00:00 2001 From: Harry Leddy <34631808+harL06@users.noreply.github.com> Date: Thu, 28 May 2026 15:39:05 +0100 Subject: [PATCH 1/8] Made top-level pipeline synchronous and updated verilog tb to reflect that --- rtl/top_pipeline.v | 66 +++++++++++++--- tb/verilog/message_parser_tb.v | 138 ++++++++++++++++++++++++++++++--- 2 files changed, 184 insertions(+), 20 deletions(-) diff --git a/rtl/top_pipeline.v b/rtl/top_pipeline.v index 75f2c17..ed82ca4 100644 --- a/rtl/top_pipeline.v +++ b/rtl/top_pipeline.v @@ -1,39 +1,85 @@ `timescale 1ns / 1ps module top_pipeline( + input clk, + input reset, + input input_valid, // should input be ignored input wire [31:0] message, - output wire [7:0] value_a, - output wire [7:0] value_b, - output wire [3:0] reject_reason + + output reg output_valid, // should output be ignored + output reg [7:0] value_a, + output reg [7:0] value_b, + output reg [3:0] reject_reason ); + reg [31:0] reg_message; + reg reg_message_valid; + always @ (posedge clk) begin // synchronous logic + // input sampling logic + if (reset) begin // reset HIGH + reg_message <= 0; + reg_message_valid <= 0; // current reg message is invalid + end + else if (input_valid) begin + reg_message <= message; // sample input on clk edge if its valid + reg_message_valid <= 1; // reg message is valid + end + else begin // if input invalid, ignore the input + reg_message <= 0; + reg_message_valid <= 0; + end + + // output sampling logic + output_valid <= reg_message_valid; // output valid based on prev message valid + + if (reset) begin // reset HIGH + output_valid <= 0; // output invalid during reset + value_a <= 0; + value_b <= 0; + reject_reason <= 0; + end + else if (reg_message_valid) begin // if current reg message is valid, sample output + value_a <= value_a_wire; //outputs will be based on prev cycle input + value_b <= value_b_wire; + reject_reason <= reject_reason_wire; + end + else begin //outputs zeroed if reg input is invalid + value_a <= 0; + value_b <= 0; + reject_reason <= 0; + end + end + // parse diferent vars from the input message wire [7:0] input_val_a, input_val_b; wire [3:0] message_type; message_parser parser ( - .message(message), + .message(reg_message), // input the most recent sampled input .value_a(input_val_a), .value_b(input_val_b), .message_type(message_type) ); // pass parsed values into message filter - wire message_valid; + wire message_accept; + wire [3:0] reject_reason_wire; message_filter filter ( .message_type(message_type), - .message_valid(message_valid), - .reject_reason(reject_reason), + .message_valid(message_accept), + .reject_reason(reject_reason_wire), .value_a(input_val_a), .value_b(input_val_b) ); + wire [7:0] value_a_wire; // wire values driven by combo logic, will be sampled on clk edge + wire [7:0] value_b_wire; // set outputs only if the message is valid output_formatter out_format ( .input_val_a(input_val_a), .input_val_b(input_val_b), - .message_valid(message_valid), - .value_a(value_a), - .value_b(value_b) + .message_valid(message_accept), + .value_a(value_a_wire), + .value_b(value_b_wire) ); endmodule diff --git a/tb/verilog/message_parser_tb.v b/tb/verilog/message_parser_tb.v index e64a36c..295771a 100644 --- a/tb/verilog/message_parser_tb.v +++ b/tb/verilog/message_parser_tb.v @@ -1,21 +1,139 @@ `timescale 1ns / 1ps -module message_parser_tb(); +module top_pipeline_tb(); - // generate some simple test messages, opnly changing values of a and b reg [31:0] message; + wire [7:0] value_a; + wire [7:0] value_b; + wire [3:0] reject_reason; + + reg [7:0] test_value_a; + reg [7:0] test_value_b; + reg [3:0] test_reject_reason; + wire output_valid; + + reg clk; + initial + clk = 0; + always + #5 clk = ~clk; //setup simple testbench clock + + reg reset, input_valid; + initial begin // set reset HIGH for one clock cycle on start + reset = 1; + #10 + + reset = 0; + end + + reg [7:0] fail_count; + reg [7:0] pass_count; initial begin - message = 32'b0000_0000_11111111_00001111_00000000; #10 - message = 32'b0000_0000_11110000_11110000_00000000; #10 - message = 32'b0000_0000_00000000_00000000_00000000; + fail_count = 0; + pass_count = 0; end - // read out the 2 values only for tb - wire [7:0] value_a; - wire [7:0] value_b; + always @ (*) begin // pass/fail checks sampled + if (!reset & output_valid) begin + if (value_a == test_value_a && + value_b == test_value_b && + reject_reason == test_reject_reason) + // keep track of num failing and passing test cases + + pass_count <= pass_count + 1; + else + fail_count <= fail_count + 1; + end + #20; + end + + initial begin + input_valid = 0; #10 + input_valid = 1; + + message = 32'b0000_0000_11111111_00001111_00000000; + + test_value_a = 8'b00000000; //255 + test_value_b = 8'b00000000; //15 + test_reject_reason = 4'b0010; + #10 // invalid - A too high + + input_valid = 0; #10 + input_valid = 1; + + + message = 32'b0000_0000_11110000_11110000_00000000; - // feed input and outputs relevant vars - message_parser uut (.value_a(value_a), .value_b(value_b), .message(message)); + test_value_a = 8'b00000000; //240 + test_value_b = 8'b00000000; //240 + test_reject_reason = 4'b0010; + #10 // invalid - A too high (checks A first) + + input_valid = 0; #10 + input_valid = 1; + + message = 32'b0000_0000_01010101_11001101_00000000; + + test_value_a = 8'b00000000; //85 + test_value_b = 8'b00000000; //205 + test_reject_reason = 4'b0011; + #10 // invalid - B too high + + input_valid = 0; #10 + input_valid = 1; + + message = 32'b0000_0000_00000101_00000011_00000000; + + test_value_a = 8'b00000101; //5 + test_value_b = 8'b00000011; //3 + test_reject_reason = 4'b0000; + #10 // valid - passes filter + + input_valid = 0; #10 + input_valid = 1; + + message = 32'b0000_0000_11001000_11001000_00000000; + + test_value_a = 8'b11001000; //200 + test_value_b = 8'b11001000; //200 + test_reject_reason = 4'b0000; + #10 // valid - passes filter + + input_valid = 0; #10 + input_valid = 1; + + message = 32'b0001_0000_01010101_10101010_00000000; + + test_value_a = 8'b00000000; + test_value_b = 8'b00000000; + test_reject_reason = 4'b0001; + #10 // invalid - wrong type + + input_valid = 0; #10 + input_valid = 1; + + message = 32'b0001_0000_11110000_00001111_00000000; + + test_value_a = 8'b00000000; + test_value_b = 8'b00000000; + test_reject_reason = 4'b0001; // invalid - wrong type + #10 + + input_valid = 0; #10 + + $display("Passes: %0d, Fails: %0d", pass_count, fail_count); // finish sim and display results + $finish; + end + top_pipeline uut ( + .clk(clk), + .reset(reset), + .input_valid(input_valid), + .output_valid(output_valid), + .message(message), + .value_a(value_a), + .value_b(value_b), + .reject_reason(reject_reason) + ); endmodule From 25b89194baa3bb8a5216bd950626527a74587296 Mon Sep 17 00:00:00 2001 From: Harry Leddy <34631808+harL06@users.noreply.github.com> Date: Thu, 28 May 2026 20:39:12 +0100 Subject: [PATCH 2/8] Improved Verilog TB for sync pipeline Added doc to store and display waveforms from Verilog testing --- docs/waveforms.md | 9 +++++++++ images/sync_tb_waveform.png | Bin 0 -> 78891 bytes tb/verilog/message_parser_tb.v | 5 ++--- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 docs/waveforms.md create mode 100644 images/sync_tb_waveform.png diff --git a/docs/waveforms.md b/docs/waveforms.md new file mode 100644 index 0000000..fe2ab1c --- /dev/null +++ b/docs/waveforms.md @@ -0,0 +1,9 @@ +# Waveforms + +This doc contains waveforms from RTL pipeline testing. + +## Synchronous Pipeline Test + +The waveform shows the synchronous version of the pipeline being tested with fixed input messages. + +![Synchronous pipeline waveform](../images/sync_tb_waveform.png) diff --git a/images/sync_tb_waveform.png b/images/sync_tb_waveform.png new file mode 100644 index 0000000000000000000000000000000000000000..474890ae28a8df2cef046c1b7b429fa637fa5974 GIT binary patch literal 78891 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 XS+}%yZ;e(Ql-(hRUoi*H{`UU>5=)sz literal 0 HcmV?d00001 diff --git a/tb/verilog/message_parser_tb.v b/tb/verilog/message_parser_tb.v index 295771a..e344f78 100644 --- a/tb/verilog/message_parser_tb.v +++ b/tb/verilog/message_parser_tb.v @@ -33,8 +33,8 @@ module top_pipeline_tb(); pass_count = 0; end - always @ (*) begin // pass/fail checks sampled - if (!reset & output_valid) begin + always @ (posedge output_valid) begin // pass/fail checks sampled when output valid + if (!reset) begin if (value_a == test_value_a && value_b == test_value_b && reject_reason == test_reject_reason) @@ -44,7 +44,6 @@ module top_pipeline_tb(); else fail_count <= fail_count + 1; end - #20; end initial begin From d66dbec7edac0c4461f55b1f7b22fc310b524224 Mon Sep 17 00:00:00 2001 From: Harry Leddy <34631808+harL06@users.noreply.github.com> Date: Thu, 28 May 2026 21:42:54 +0100 Subject: [PATCH 3/8] Added synthesis and timing reports. Updated readmes to reflect current status of verification --- README.md | 17 +- constraints/constraints.xdc | 1 + docs/synthesis.md | 18 ++ docs/timing_summary.txt | 332 ++++++++++++++++++++++++++++++++++++ docs/utilization_report.txt | 183 ++++++++++++++++++++ rtl/README.md | 4 +- tb/verilog/README.md | 2 +- 7 files changed, 547 insertions(+), 10 deletions(-) create mode 100644 constraints/constraints.xdc create mode 100644 docs/synthesis.md create mode 100644 docs/timing_summary.txt create mode 100644 docs/utilization_report.txt diff --git a/README.md b/README.md index 2bb387c..a2368f6 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,12 @@ flowchart LR ## Current Features -- 32-bit message parser -- Simple parameterised message filter -- Output formatter for accepted/rejected messages -- Top-level RTL pipeline -- Original Verilog testbench -- `cocotb` tests using fixed input cases -- Python reference model for checking expected behaviour +- Synchronous Verilog message-processing pipeline +- 32-bit parser, parameterised filter, and output formatter +- `cocotb` verification with fixed and randomised test cases +- Python reference model for expected behaviour +- GitHub Actions CI for automated tests +- Vivado synthesis and timing reports ## Message Format @@ -49,7 +48,9 @@ The project currently includes fixed-case and randomised cocotb tests. The cocot | `test_random_reference.py` | Randomised input cases checked against Python reference model | | `reference_model.py` | Python software model of the expected RTL behaviour | -Example Output: +Vivado synthesis and timing reports are included in [`docs/synthesis.md`](docs/synthesis.md). + +Example `cocotb` Output: ```text 1000000.00ns INFO test 1000000 tests ran successfully... diff --git a/constraints/constraints.xdc b/constraints/constraints.xdc new file mode 100644 index 0000000..4702f4c --- /dev/null +++ b/constraints/constraints.xdc @@ -0,0 +1 @@ +create_clock -period 10.000 -name clk [get_ports clk] \ No newline at end of file diff --git a/docs/synthesis.md b/docs/synthesis.md new file mode 100644 index 0000000..a4f8820 --- /dev/null +++ b/docs/synthesis.md @@ -0,0 +1,18 @@ +# Synthesis + +Vivado synthesis was run on the RTL pipeline targeting the Artix-7 XC7A35T device which is used on the Basys 3 board. + +## Summary + +| Item | Result | +| ----------------- | -------- | +| LUTs | 25 | +| Flip-Flops | 40 | +| Latches | 0 | +| Worst Setup Slack | 6.909 ns | +| Worst Hold Slack | 0.179 ns | + +## Full Reports + +- [`utilization_report.txt`](./utilization_report.txt) +- [`timing_summary.txt`](./timing_summary.txt) diff --git a/docs/timing_summary.txt b/docs/timing_summary.txt new file mode 100644 index 0000000..1698d9c --- /dev/null +++ b/docs/timing_summary.txt @@ -0,0 +1,332 @@ +Copyright 1986-2022 Xilinx, Inc. All Rights Reserved. Copyright 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved. +--------------------------------------------------------------------------------------------------------------------------------------------- +| Tool Version : Vivado v.2025.2 (win64) Build 6299465 Fri Nov 14 19:35:11 GMT 2025 +| Date : Thu May 28 21:11:00 2026 +| Host : Zenbook running 64-bit major release (build 9200) +| Command : report_timing_summary -file timing_summary.txt +| Design : top_pipeline +| Device : 7a35t-cpg236 +| Speed File : -1 PRODUCTION 1.23 2018-06-13 +| Design State : Synthesized +--------------------------------------------------------------------------------------------------------------------------------------------- + +Timing Summary Report + +------------------------------------------------------------------------------------------------ +| Timer Settings +| -------------- +------------------------------------------------------------------------------------------------ + + Enable Multi Corner Analysis : Yes + Enable Pessimism Removal : Yes + Pessimism Removal Resolution : Nearest Common Node + Enable Input Delay Default Clock : No + Enable Preset / Clear Arcs : No + Disable Flight Delays : No + Ignore I/O Paths : No + Timing Early Launch at Borrowing Latches : No + Borrow Time for Max Delay Exceptions : Yes + Merge Timing Exceptions : Yes + Inter-SLR Compensation : Conservative + + Corner Analyze Analyze + Name Max Paths Min Paths + ------ --------- --------- + Slow Yes Yes + Fast Yes Yes + + +------------------------------------------------------------------------------------------------ +| Report Methodology +| ------------------ +------------------------------------------------------------------------------------------------ + +No report available as report_methodology has not been run prior. Run report_methodology on the current design for the summary of methodology violations. + + + +check_timing report + +Table of Contents +----------------- +1. checking no_clock (0) +2. checking constant_clock (0) +3. checking pulse_width_clock (0) +4. checking unconstrained_internal_endpoints (0) +5. checking no_input_delay (22) +6. checking no_output_delay (19) +7. checking multiple_clock (0) +8. checking generated_clocks (0) +9. checking loops (0) +10. checking partial_input_delay (0) +11. checking partial_output_delay (0) +12. checking latch_loops (0) + +1. checking no_clock (0) +------------------------ + There are 0 register/latch pins with no clock. + + +2. checking constant_clock (0) +------------------------------ + There are 0 register/latch pins with constant_clock. + + +3. checking pulse_width_clock (0) +--------------------------------- + There are 0 register/latch pins which need pulse_width check + + +4. checking unconstrained_internal_endpoints (0) +------------------------------------------------ + There are 0 pins that are not constrained for maximum delay. + + There are 0 pins that are not constrained for maximum delay due to constant clock. + + +5. checking no_input_delay (22) +------------------------------- + There are 22 input ports with no input delay specified. (HIGH) + + There are 0 input ports with no input delay but user has a false path constraint. + + +6. checking no_output_delay (19) +-------------------------------- + There are 19 ports with no output delay specified. (HIGH) + + There are 0 ports with no output delay but user has a false path constraint + + There are 0 ports with no output delay but with a timing clock defined on it or propagating through it + + +7. checking multiple_clock (0) +------------------------------ + There are 0 register/latch pins with multiple clocks. + + +8. checking generated_clocks (0) +-------------------------------- + There are 0 generated clocks that are not connected to a clock source. + + +9. checking loops (0) +--------------------- + There are 0 combinational loops in the design. + + +10. checking partial_input_delay (0) +------------------------------------ + There are 0 input ports with partial input delay specified. + + +11. checking partial_output_delay (0) +------------------------------------- + There are 0 ports with partial output delay specified. + + +12. checking latch_loops (0) +---------------------------- + There are 0 combinational latch loops in the design through latch input + + + +------------------------------------------------------------------------------------------------ +| Design Timing Summary +| --------------------- +------------------------------------------------------------------------------------------------ + + WNS(ns) TNS(ns) TNS Failing Endpoints TNS Total Endpoints WHS(ns) THS(ns) THS Failing Endpoints THS Total Endpoints WPWS(ns) TPWS(ns) TPWS Failing Endpoints TPWS Total Endpoints + ------- ------- --------------------- ------------------- ------- ------- --------------------- ------------------- -------- -------- ---------------------- -------------------- + 6.909 0.000 0 19 0.179 0.000 0 19 4.500 0.000 0 41 + + +All user specified timing constraints are met. + + +------------------------------------------------------------------------------------------------ +| Clock Summary +| ------------- +------------------------------------------------------------------------------------------------ + +Clock Waveform(ns) Period(ns) Frequency(MHz) +----- ------------ ---------- -------------- +clk {0.000 5.000} 10.000 100.000 + + +------------------------------------------------------------------------------------------------ +| Intra Clock Table +| ----------------- +------------------------------------------------------------------------------------------------ + +Clock WNS(ns) TNS(ns) TNS Failing Endpoints TNS Total Endpoints WHS(ns) THS(ns) THS Failing Endpoints THS Total Endpoints WPWS(ns) TPWS(ns) TPWS Failing Endpoints TPWS Total Endpoints +----- ------- ------- --------------------- ------------------- ------- ------- --------------------- ------------------- -------- -------- ---------------------- -------------------- +clk 6.909 0.000 0 19 0.179 0.000 0 19 4.500 0.000 0 41 + + +------------------------------------------------------------------------------------------------ +| Inter Clock Table +| ----------------- +------------------------------------------------------------------------------------------------ + +From Clock To Clock WNS(ns) TNS(ns) TNS Failing Endpoints TNS Total Endpoints WHS(ns) THS(ns) THS Failing Endpoints THS Total Endpoints +---------- -------- ------- ------- --------------------- ------------------- ------- ------- --------------------- ------------------- + + +------------------------------------------------------------------------------------------------ +| Other Path Groups Table +| ----------------------- +------------------------------------------------------------------------------------------------ + +Path Group From Clock To Clock WNS(ns) TNS(ns) TNS Failing Endpoints TNS Total Endpoints WHS(ns) THS(ns) THS Failing Endpoints THS Total Endpoints +---------- ---------- -------- ------- ------- --------------------- ------------------- ------- ------- --------------------- ------------------- + + +------------------------------------------------------------------------------------------------ +| Timing Details +| -------------- +------------------------------------------------------------------------------------------------ + + +--------------------------------------------------------------------------------------------------- +From Clock: clk + To Clock: clk + +Setup : 0 Failing Endpoints, Worst Slack 6.909ns, Total Violation 0.000ns +Hold : 0 Failing Endpoints, Worst Slack 0.179ns, Total Violation 0.000ns +PW : 0 Failing Endpoints, Worst Slack 4.500ns, Total Violation 0.000ns +--------------------------------------------------------------------------------------------------- + + +Max Delay Paths +-------------------------------------------------------------------------------------- +Slack (MET) : 6.909ns (required time - arrival time) + Source: reg_message_reg[12]/C + (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@5.000ns period=10.000ns}) + Destination: reject_reason_reg[1]/D + (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@5.000ns period=10.000ns}) + Path Group: clk + Path Type: Setup (Max at Slow Process Corner) + Requirement: 10.000ns (clk rise@10.000ns - clk rise@0.000ns) + Data Path Delay: 2.940ns (logic 0.999ns (33.980%) route 1.941ns (66.020%)) + Logic Levels: 3 (LUT6=3) + Clock Path Skew: -0.145ns (DCD - SCD + CPR) + Destination Clock Delay (DCD): 2.079ns = ( 12.079 - 10.000 ) + Source Clock Delay (SCD): 2.402ns + Clock Pessimism Removal (CPR): 0.178ns + Clock Uncertainty: 0.035ns ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE + Total System Jitter (TSJ): 0.071ns + Total Input Jitter (TIJ): 0.000ns + Discrete Jitter (DJ): 0.000ns + Phase Error (PE): 0.000ns + + Location Delay type Incr(ns) Path(ns) Netlist Resource(s) + ------------------------------------------------------------------- ------------------- + (clock clk rise edge) 0.000 0.000 r + 0.000 0.000 r clk (IN) + net (fo=0) 0.000 0.000 clk + IBUF (Prop_ibuf_I_O) 0.923 0.923 r clk_IBUF_inst/O + net (fo=1, unplaced) 0.800 1.722 clk_IBUF + BUFG (Prop_bufg_I_O) 0.096 1.818 r clk_IBUF_BUFG_inst/O + net (fo=40, unplaced) 0.584 2.402 clk_IBUF_BUFG + FDRE r reg_message_reg[12]/C + ------------------------------------------------------------------- ------------------- + FDRE (Prop_fdre_C_Q) 0.456 2.858 r reg_message_reg[12]/Q + net (fo=2, unplaced) 0.976 3.834 input_val_b[4] + LUT6 (Prop_lut6_I0_O) 0.295 4.129 r reject_reason[0]_i_2/O + net (fo=2, unplaced) 0.460 4.589 reject_reason[0]_i_2_n_0 + LUT6 (Prop_lut6_I4_O) 0.124 4.713 f value_a[7]_i_2/O + net (fo=17, unplaced) 0.505 5.218 value_a[7]_i_2_n_0 + LUT6 (Prop_lut6_I0_O) 0.124 5.342 r reject_reason[1]_i_1/O + net (fo=1, unplaced) 0.000 5.342 reject_reason[1]_i_1_n_0 + FDRE r reject_reason_reg[1]/D + ------------------------------------------------------------------- ------------------- + + (clock clk rise edge) 10.000 10.000 r + 0.000 10.000 r clk (IN) + net (fo=0) 0.000 10.000 clk + IBUF (Prop_ibuf_I_O) 0.789 10.789 r clk_IBUF_inst/O + net (fo=1, unplaced) 0.760 11.549 clk_IBUF + BUFG (Prop_bufg_I_O) 0.091 11.640 r clk_IBUF_BUFG_inst/O + net (fo=40, unplaced) 0.439 12.079 clk_IBUF_BUFG + FDRE r reject_reason_reg[1]/C + clock pessimism 0.178 12.257 + clock uncertainty -0.035 12.222 + FDRE (Setup_fdre_C_D) 0.029 12.251 reject_reason_reg[1] + ------------------------------------------------------------------- + required time 12.251 + arrival time -5.342 + ------------------------------------------------------------------- + slack 6.909 + + + + + +Min Delay Paths +-------------------------------------------------------------------------------------- +Slack (MET) : 0.179ns (arrival time - required time) + Source: reg_message_valid_reg/C + (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@5.000ns period=10.000ns}) + Destination: output_valid_reg/D + (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@5.000ns period=10.000ns}) + Path Group: clk + Path Type: Hold (Min at Fast Process Corner) + Requirement: 0.000ns (clk rise@0.000ns - clk rise@0.000ns) + Data Path Delay: 0.307ns (logic 0.141ns (45.980%) route 0.166ns (54.020%)) + Logic Levels: 0 + Clock Path Skew: 0.145ns (DCD - SCD - CPR) + Destination Clock Delay (DCD): 0.983ns + Source Clock Delay (SCD): 0.629ns + Clock Pessimism Removal (CPR): 0.209ns + + Location Delay type Incr(ns) Path(ns) Netlist Resource(s) + ------------------------------------------------------------------- ------------------- + (clock clk rise edge) 0.000 0.000 r + 0.000 0.000 r clk (IN) + net (fo=0) 0.000 0.000 clk + IBUF (Prop_ibuf_I_O) 0.152 0.152 r clk_IBUF_inst/O + net (fo=1, unplaced) 0.337 0.489 clk_IBUF + BUFG (Prop_bufg_I_O) 0.026 0.515 r clk_IBUF_BUFG_inst/O + net (fo=40, unplaced) 0.114 0.629 clk_IBUF_BUFG + FDRE r reg_message_valid_reg/C + ------------------------------------------------------------------- ------------------- + FDRE (Prop_fdre_C_Q) 0.141 0.770 r reg_message_valid_reg/Q + net (fo=19, unplaced) 0.166 0.936 reg_message_valid + FDRE r output_valid_reg/D + ------------------------------------------------------------------- ------------------- + + (clock clk rise edge) 0.000 0.000 r + 0.000 0.000 r clk (IN) + net (fo=0) 0.000 0.000 clk + IBUF (Prop_ibuf_I_O) 0.340 0.340 r clk_IBUF_inst/O + net (fo=1, unplaced) 0.355 0.695 clk_IBUF + BUFG (Prop_bufg_I_O) 0.029 0.724 r clk_IBUF_BUFG_inst/O + net (fo=40, unplaced) 0.259 0.983 clk_IBUF_BUFG + FDRE r output_valid_reg/C + clock pessimism -0.209 0.774 + FDRE (Hold_fdre_C_D) -0.017 0.757 output_valid_reg + ------------------------------------------------------------------- + required time -0.757 + arrival time 0.936 + ------------------------------------------------------------------- + slack 0.179 + + + + + +Pulse Width Checks +-------------------------------------------------------------------------------------- +Clock Name: clk +Waveform(ns): { 0.000 5.000 } +Period(ns): 10.000 +Sources: { clk } + +Check Type Corner Lib Pin Reference Pin Required(ns) Actual(ns) Slack(ns) Location Pin +Min Period n/a BUFG/I n/a 2.155 10.000 7.845 clk_IBUF_BUFG_inst/I +Low Pulse Width Slow FDRE/C n/a 0.500 5.000 4.500 output_valid_reg/C +High Pulse Width Slow FDRE/C n/a 0.500 5.000 4.500 output_valid_reg/C + + + diff --git a/docs/utilization_report.txt b/docs/utilization_report.txt new file mode 100644 index 0000000..1a138b7 --- /dev/null +++ b/docs/utilization_report.txt @@ -0,0 +1,183 @@ +Copyright 1986-2022 Xilinx, Inc. All Rights Reserved. Copyright 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved. +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +| Tool Version : Vivado v.2025.2 (win64) Build 6299465 Fri Nov 14 19:35:11 GMT 2025 +| Date : Thu May 28 20:45:19 2026 +| Host : Zenbook running 64-bit major release (build 9200) +| Command : report_utilization -hierarchical_min_primitive_count 50 -file C:/Users/harry/code/personal/fpga-stream-processor/fpga-stream-processor/utilization_report.txt -name utilization_2 +| Design : top_pipeline +| Device : xc7a35tcpg236-1 +| Speed File : -1 +| Design State : Synthesized +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Utilization Design Information + +Table of Contents +----------------- +1. Slice Logic +1.1 Summary of Registers by Type +2. Memory +3. DSP +4. IO and GT Specific +5. Clocking +6. Specific Feature +7. Primitives +8. Black Boxes +9. Instantiated Netlists + +1. Slice Logic +-------------- + ++-------------------------+------+-------+------------+-----------+-------+ +| Site Type | Used | Fixed | Prohibited | Available | Util% | ++-------------------------+------+-------+------------+-----------+-------+ +| Slice LUTs* | 25 | 0 | 0 | 20800 | 0.12 | +| LUT as Logic | 25 | 0 | 0 | 20800 | 0.12 | +| LUT as Memory | 0 | 0 | 0 | 9600 | 0.00 | +| Slice Registers | 40 | 0 | 0 | 41600 | 0.10 | +| Register as Flip Flop | 40 | 0 | 0 | 41600 | 0.10 | +| Register as Latch | 0 | 0 | 0 | 41600 | 0.00 | +| F7 Muxes | 0 | 0 | 0 | 16300 | 0.00 | +| F8 Muxes | 0 | 0 | 0 | 8150 | 0.00 | +| Unique Control Sets | 1 | | 0 | 8150 | 0.01 | ++-------------------------+------+-------+------------+-----------+-------+ +* Warning! The Final LUT count, after physical optimizations and full implementation, is typically lower. Run opt_design after synthesis, if not already completed, for a more realistic count. +Warning! LUT value is adjusted to account for LUT combining. +Warning! For any ECO changes, please run place_design if there are unplaced instances +** Note: Available Control Sets calculated as Slice * 1, Review the Control Sets Report for more information regarding control sets. + + +1.1 Summary of Registers by Type +-------------------------------- + ++-------+--------------+-------------+--------------+ +| Total | Clock Enable | Synchronous | Asynchronous | ++-------+--------------+-------------+--------------+ +| 0 | _ | - | - | +| 0 | _ | - | Set | +| 0 | _ | - | Reset | +| 0 | _ | Set | - | +| 0 | _ | Reset | - | +| 0 | Yes | - | - | +| 0 | Yes | - | Set | +| 0 | Yes | - | Reset | +| 0 | Yes | Set | - | +| 40 | Yes | Reset | - | ++-------+--------------+-------------+--------------+ + + +2. Memory +--------- + ++----------------+------+-------+------------+-----------+-------+ +| Site Type | Used | Fixed | Prohibited | Available | Util% | ++----------------+------+-------+------------+-----------+-------+ +| Block RAM Tile | 0 | 0 | 0 | 50 | 0.00 | +| RAMB36/FIFO* | 0 | 0 | 0 | 50 | 0.00 | +| RAMB18 | 0 | 0 | 0 | 100 | 0.00 | ++----------------+------+-------+------------+-----------+-------+ +* Note: Each Block RAM Tile only has one FIFO logic available and therefore can accommodate only one FIFO36E1 or one FIFO18E1. However, if a FIFO18E1 occupies a Block RAM Tile, that tile can still accommodate a RAMB18E1 + + +3. DSP +------ + ++-----------+------+-------+------------+-----------+-------+ +| Site Type | Used | Fixed | Prohibited | Available | Util% | ++-----------+------+-------+------------+-----------+-------+ +| DSPs | 0 | 0 | 0 | 90 | 0.00 | ++-----------+------+-------+------------+-----------+-------+ + + +4. IO and GT Specific +--------------------- + ++-----------------------------+------+-------+------------+-----------+-------+ +| Site Type | Used | Fixed | Prohibited | Available | Util% | ++-----------------------------+------+-------+------------+-----------+-------+ +| Bonded IOB | 44 | 0 | 0 | 106 | 41.51 | +| Bonded IPADs | 0 | 0 | 0 | 10 | 0.00 | +| Bonded OPADs | 0 | 0 | 0 | 4 | 0.00 | +| PHY_CONTROL | 0 | 0 | 0 | 5 | 0.00 | +| PHASER_REF | 0 | 0 | 0 | 5 | 0.00 | +| OUT_FIFO | 0 | 0 | 0 | 20 | 0.00 | +| IN_FIFO | 0 | 0 | 0 | 20 | 0.00 | +| IDELAYCTRL | 0 | 0 | 0 | 5 | 0.00 | +| IBUFDS | 0 | 0 | 0 | 104 | 0.00 | +| GTPE2_CHANNEL | 0 | 0 | 0 | 2 | 0.00 | +| PHASER_OUT/PHASER_OUT_PHY | 0 | 0 | 0 | 20 | 0.00 | +| PHASER_IN/PHASER_IN_PHY | 0 | 0 | 0 | 20 | 0.00 | +| IDELAYE2/IDELAYE2_FINEDELAY | 0 | 0 | 0 | 250 | 0.00 | +| IBUFDS_GTE2 | 0 | 0 | 0 | 2 | 0.00 | +| ILOGIC | 0 | 0 | 0 | 106 | 0.00 | +| OLOGIC | 0 | 0 | 0 | 106 | 0.00 | ++-----------------------------+------+-------+------------+-----------+-------+ + + +5. Clocking +----------- + ++------------+------+-------+------------+-----------+-------+ +| Site Type | Used | Fixed | Prohibited | Available | Util% | ++------------+------+-------+------------+-----------+-------+ +| BUFGCTRL | 1 | 0 | 0 | 32 | 3.13 | +| BUFIO | 0 | 0 | 0 | 20 | 0.00 | +| MMCME2_ADV | 0 | 0 | 0 | 5 | 0.00 | +| PLLE2_ADV | 0 | 0 | 0 | 5 | 0.00 | +| BUFMRCE | 0 | 0 | 0 | 10 | 0.00 | +| BUFHCE | 0 | 0 | 0 | 72 | 0.00 | +| BUFR | 0 | 0 | 0 | 20 | 0.00 | ++------------+------+-------+------------+-----------+-------+ + + +6. Specific Feature +------------------- + ++-------------+------+-------+------------+-----------+-------+ +| Site Type | Used | Fixed | Prohibited | Available | Util% | ++-------------+------+-------+------------+-----------+-------+ +| BSCANE2 | 0 | 0 | 0 | 4 | 0.00 | +| CAPTUREE2 | 0 | 0 | 0 | 1 | 0.00 | +| DNA_PORT | 0 | 0 | 0 | 1 | 0.00 | +| EFUSE_USR | 0 | 0 | 0 | 1 | 0.00 | +| FRAME_ECCE2 | 0 | 0 | 0 | 1 | 0.00 | +| ICAPE2 | 0 | 0 | 0 | 2 | 0.00 | +| PCIE_2_1 | 0 | 0 | 0 | 1 | 0.00 | +| STARTUPE2 | 0 | 0 | 0 | 1 | 0.00 | +| XADC | 0 | 0 | 0 | 1 | 0.00 | ++-------------+------+-------+------------+-----------+-------+ + + +7. Primitives +------------- + ++----------+------+---------------------+ +| Ref Name | Used | Functional Category | ++----------+------+---------------------+ +| FDRE | 40 | Flop & Latch | +| IBUF | 23 | IO | +| OBUF | 21 | IO | +| LUT2 | 20 | LUT | +| LUT4 | 17 | LUT | +| LUT6 | 5 | LUT | +| LUT3 | 1 | LUT | +| BUFG | 1 | Clock | ++----------+------+---------------------+ + + +8. Black Boxes +-------------- + ++----------+------+ +| Ref Name | Used | ++----------+------+ + + +9. Instantiated Netlists +------------------------ + ++----------+------+ +| Ref Name | Used | ++----------+------+ + + diff --git a/rtl/README.md b/rtl/README.md index 1ddcb32..85105b0 100644 --- a/rtl/README.md +++ b/rtl/README.md @@ -4,7 +4,9 @@ This folder contains the Verilog design files for the project. ## Current status -The current setup includes a simple message parser, a filter to check for an invalid type and a pipeline to connect these and decide the output based on the parsed input and filter result. +The setup includes a simple message parser, a filter to check for an invalid type and a pipeline to connect these and decide the output based on the parsed input and filter result. + +The top-level pipeline is also now synchronous, with registered input/output behaviour and `input_valid` / `output_valid` signalling. ```text ┌────────────────┐ message type ┌────────────────┐ diff --git a/tb/verilog/README.md b/tb/verilog/README.md index 9220a7e..c8dfc8e 100644 --- a/tb/verilog/README.md +++ b/tb/verilog/README.md @@ -4,5 +4,5 @@ This folder contains simple verilog simulation testbenches for the RTL modules. ## Current status -The current testbench inputs fixed 32-bit messages into `top_pipeline.v` and checks the parsed fields in simulation. +The top-level testbench applies fixed 32-bit messages to the synchronous pipeline and checks the registered output values and reject reasons. Outputs are compared against expected values and a count for the number of passes/fails is tracked. From ed4eab8608702b4ac15653b634b34a14e86cdc93 Mon Sep 17 00:00:00 2001 From: Harry Leddy <34631808+harL06@users.noreply.github.com> Date: Thu, 28 May 2026 21:46:45 +0100 Subject: [PATCH 4/8] Update Verilog TB for synchronous pipeline --- rtl/message_filter.v | 2 +- rtl/top_pipeline.v | 17 +++++++--- tb/verilog/top_pipeline_tb.v | 66 ++++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/rtl/message_filter.v b/rtl/message_filter.v index 9027285..e137499 100644 --- a/rtl/message_filter.v +++ b/rtl/message_filter.v @@ -27,7 +27,7 @@ module message_filter else begin message_valid = 1; reject_reason = 4'b0000; // passes through filter - end + end end endmodule diff --git a/rtl/top_pipeline.v b/rtl/top_pipeline.v index ed82ca4..f2912cf 100644 --- a/rtl/top_pipeline.v +++ b/rtl/top_pipeline.v @@ -14,6 +14,13 @@ module top_pipeline( reg [31:0] reg_message; reg reg_message_valid; + + wire [7:0] value_a_wire; + wire [7:0] value_b_wire; + + wire message_accept; + wire [3:0] reject_reason_wire; + always @ (posedge clk) begin // synchronous logic // input sampling logic if (reset) begin // reset HIGH @@ -53,16 +60,18 @@ module top_pipeline( // parse diferent vars from the input message wire [7:0] input_val_a, input_val_b; wire [3:0] message_type; + wire [3:0] channel_id; // will be used in fut ver + wire [7:0] flags; // will be used in fut ver message_parser parser ( .message(reg_message), // input the most recent sampled input .value_a(input_val_a), .value_b(input_val_b), - .message_type(message_type) + .message_type(message_type), + .channel_id(channel_id), + .flags(flags) ); // pass parsed values into message filter - wire message_accept; - wire [3:0] reject_reason_wire; message_filter filter ( .message_type(message_type), .message_valid(message_accept), @@ -71,8 +80,6 @@ module top_pipeline( .value_b(input_val_b) ); - wire [7:0] value_a_wire; // wire values driven by combo logic, will be sampled on clk edge - wire [7:0] value_b_wire; // set outputs only if the message is valid output_formatter out_format ( .input_val_a(input_val_a), diff --git a/tb/verilog/top_pipeline_tb.v b/tb/verilog/top_pipeline_tb.v index 3beb379..e344f78 100644 --- a/tb/verilog/top_pipeline_tb.v +++ b/tb/verilog/top_pipeline_tb.v @@ -10,6 +10,7 @@ module top_pipeline_tb(); reg [7:0] test_value_a; reg [7:0] test_value_b; reg [3:0] test_reject_reason; + wire output_valid; reg clk; initial @@ -17,6 +18,14 @@ module top_pipeline_tb(); always #5 clk = ~clk; //setup simple testbench clock + reg reset, input_valid; + initial begin // set reset HIGH for one clock cycle on start + reset = 1; + #10 + + reset = 0; + end + reg [7:0] fail_count; reg [7:0] pass_count; initial begin @@ -24,65 +33,102 @@ module top_pipeline_tb(); pass_count = 0; end - always @ (posedge clk) begin - if (value_a == test_value_a && - value_b == test_value_b && - reject_reason == test_reject_reason) - // keep track of num failing and passing test cases - - pass_count <= pass_count + 1; - else - fail_count <= fail_count + 1; + always @ (posedge output_valid) begin // pass/fail checks sampled when output valid + if (!reset) begin + if (value_a == test_value_a && + value_b == test_value_b && + reject_reason == test_reject_reason) + // keep track of num failing and passing test cases + + pass_count <= pass_count + 1; + else + fail_count <= fail_count + 1; + end end initial begin + input_valid = 0; #10 + input_valid = 1; + message = 32'b0000_0000_11111111_00001111_00000000; + test_value_a = 8'b00000000; //255 test_value_b = 8'b00000000; //15 test_reject_reason = 4'b0010; - #10 // invalid - A too highj + #10 // invalid - A too high + + input_valid = 0; #10 + input_valid = 1; + message = 32'b0000_0000_11110000_11110000_00000000; + test_value_a = 8'b00000000; //240 test_value_b = 8'b00000000; //240 test_reject_reason = 4'b0010; #10 // invalid - A too high (checks A first) + input_valid = 0; #10 + input_valid = 1; + message = 32'b0000_0000_01010101_11001101_00000000; + test_value_a = 8'b00000000; //85 test_value_b = 8'b00000000; //205 test_reject_reason = 4'b0011; #10 // invalid - B too high + input_valid = 0; #10 + input_valid = 1; + message = 32'b0000_0000_00000101_00000011_00000000; + test_value_a = 8'b00000101; //5 test_value_b = 8'b00000011; //3 test_reject_reason = 4'b0000; #10 // valid - passes filter + input_valid = 0; #10 + input_valid = 1; + message = 32'b0000_0000_11001000_11001000_00000000; + test_value_a = 8'b11001000; //200 test_value_b = 8'b11001000; //200 test_reject_reason = 4'b0000; #10 // valid - passes filter + input_valid = 0; #10 + input_valid = 1; + message = 32'b0001_0000_01010101_10101010_00000000; + test_value_a = 8'b00000000; test_value_b = 8'b00000000; test_reject_reason = 4'b0001; #10 // invalid - wrong type + input_valid = 0; #10 + input_valid = 1; + message = 32'b0001_0000_11110000_00001111_00000000; + test_value_a = 8'b00000000; test_value_b = 8'b00000000; test_reject_reason = 4'b0001; // invalid - wrong type #10 + input_valid = 0; #10 + $display("Passes: %0d, Fails: %0d", pass_count, fail_count); // finish sim and display results $finish; end top_pipeline uut ( + .clk(clk), + .reset(reset), + .input_valid(input_valid), + .output_valid(output_valid), .message(message), .value_a(value_a), .value_b(value_b), From a54ad1f5584a2eebc03e536ccd44d9f01226b393 Mon Sep 17 00:00:00 2001 From: Harry Leddy <34631808+harL06@users.noreply.github.com> Date: Thu, 28 May 2026 22:19:06 +0100 Subject: [PATCH 5/8] Update direct_cases tb to be synchronous --- tb/cocotb/Makefile | 4 +- tb/cocotb/test_direct_cases.py | 85 ++++++++++++++++++++++++++++++---- 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/tb/cocotb/Makefile b/tb/cocotb/Makefile index bec7156..80279a6 100644 --- a/tb/cocotb/Makefile +++ b/tb/cocotb/Makefile @@ -15,9 +15,9 @@ COCOTB_TOPLEVEL = top_pipeline # ================================ # select which test should be used # ================================ -COCOTB_TEST_MODULES = test_random_reference +# COCOTB_TEST_MODULES = test_random_reference # COCOTB_TEST_MODULES = test_reference_cases -# COCOTB_TEST_MODULES = test_direct_cases +COCOTB_TEST_MODULES = test_direct_cases # include cocotb's make rules to take care of the simulator setup include $(shell cocotb-config --makefiles)/Makefile.sim \ No newline at end of file diff --git a/tb/cocotb/test_direct_cases.py b/tb/cocotb/test_direct_cases.py index 356bb90..ba2fe33 100644 --- a/tb/cocotb/test_direct_cases.py +++ b/tb/cocotb/test_direct_cases.py @@ -1,7 +1,8 @@ # Fixed tests from Verilog Testbench, manually checked output import cocotb -from cocotb.triggers import Timer +from cocotb.triggers import Timer, RisingEdge, ClockCycles, ReadOnly +from cocotb.clock import Clock import reference_model @@ -9,10 +10,30 @@ @cocotb.test() async def fixed_direct_cases(dut): + # set up a 10ns cycle clock + clk = Clock(dut.clk, 10, "ns") + clk.start() + + # set reset HIGH for start + dut.reset.value = 1 + dut.input_valid.value = 0 + dut.message.value = 0 + + await ClockCycles(dut.clk, 2) # wait two clock cycles, holding reset HIGH + + dut.reset.value = 0 # disable reset signal + # === Test Case 1 ==== # message = 0b0000_0000_11111111_00001111_00000000 dut.message.value = message - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #255 value_b = int(dut.value_b.value) #15 @@ -25,8 +46,16 @@ async def fixed_direct_cases(dut): assert reject_reason == 2 # === Test Case 2 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge dut.message.value = 0b0000_0000_11110000_11110000_00000000 - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #240 value_b = int(dut.value_b.value) #240 @@ -39,8 +68,16 @@ async def fixed_direct_cases(dut): assert reject_reason == 2 # === Test Case 3 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge dut.message.value = 0b0000_0000_01010101_11001101_00000000 - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #85 value_b = int(dut.value_b.value) #205 @@ -53,8 +90,16 @@ async def fixed_direct_cases(dut): assert reject_reason == 3 # === Test Case 4 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge dut.message.value = 0b0000_0000_00000101_00000011_00000000 - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #5 value_b = int(dut.value_b.value) #3 @@ -67,8 +112,16 @@ async def fixed_direct_cases(dut): assert reject_reason == 0 # === Test Case 5 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge dut.message.value = 0b0000_0000_11001000_11001000_00000000 - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #200 value_b = int(dut.value_b.value) #200 @@ -81,8 +134,16 @@ async def fixed_direct_cases(dut): assert reject_reason == 0 # === Test Case 6 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge dut.message.value = 0b0001_0000_01010101_10101010_00000000 - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #200 value_b = int(dut.value_b.value) #200 @@ -95,8 +156,16 @@ async def fixed_direct_cases(dut): assert reject_reason == 1 # === Test Case 7 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge dut.message.value = 0b0001_0000_11110000_00001111_00000000 - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) value_b = int(dut.value_b.value) From 304cd195a36ac50f181e4bddbcdfa49f1860528b Mon Sep 17 00:00:00 2001 From: Harry Leddy <34631808+harL06@users.noreply.github.com> Date: Fri, 29 May 2026 11:26:12 +0100 Subject: [PATCH 6/8] Added synchronous cocotb tb which compares agaisnt the ref model --- tb/cocotb/Makefile | 4 +- tb/cocotb/test_direct_cases.py | 4 +- tb/cocotb/test_reference_cases.py | 78 +++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/tb/cocotb/Makefile b/tb/cocotb/Makefile index 80279a6..bdf3ada 100644 --- a/tb/cocotb/Makefile +++ b/tb/cocotb/Makefile @@ -16,8 +16,8 @@ COCOTB_TOPLEVEL = top_pipeline # select which test should be used # ================================ # COCOTB_TEST_MODULES = test_random_reference -# COCOTB_TEST_MODULES = test_reference_cases -COCOTB_TEST_MODULES = test_direct_cases +COCOTB_TEST_MODULES = test_reference_cases +# COCOTB_TEST_MODULES = test_direct_cases # include cocotb's make rules to take care of the simulator setup include $(shell cocotb-config --makefiles)/Makefile.sim \ No newline at end of file diff --git a/tb/cocotb/test_direct_cases.py b/tb/cocotb/test_direct_cases.py index ba2fe33..c619b38 100644 --- a/tb/cocotb/test_direct_cases.py +++ b/tb/cocotb/test_direct_cases.py @@ -1,11 +1,9 @@ # Fixed tests from Verilog Testbench, manually checked output import cocotb -from cocotb.triggers import Timer, RisingEdge, ClockCycles, ReadOnly +from cocotb.triggers import RisingEdge, ClockCycles, ReadOnly from cocotb.clock import Clock -import reference_model - # Design under test @cocotb.test() async def fixed_direct_cases(dut): diff --git a/tb/cocotb/test_reference_cases.py b/tb/cocotb/test_reference_cases.py index 73a3519..e212e07 100644 --- a/tb/cocotb/test_reference_cases.py +++ b/tb/cocotb/test_reference_cases.py @@ -1,7 +1,8 @@ # Fixed tests from Verilog Testbench, output checked against Python reference model import cocotb -from cocotb.triggers import Timer +from cocotb.triggers import RisingEdge, ClockCycles, ReadOnly +from cocotb.clock import Clock from reference_model import reference_model @@ -9,11 +10,30 @@ @cocotb.test() async def fixed_reference_cases(dut): + # set up a 10ns cycle clock + clk = Clock(dut.clk, 10, "ns") + clk.start() + + # set reset HIGH for start + dut.reset.value = 1 + dut.input_valid.value = 0 + dut.message.value = 0 + + await ClockCycles(dut.clk, 2) # wait two clock cycles, holding reset HIGH + + dut.reset.value = 0 # disable reset signal + # === Test Case 1 ==== # message = 0b0000_0000_11111111_00001111_00000000 dut.message.value = message - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #255 value_b = int(dut.value_b.value) #15 @@ -29,10 +49,17 @@ async def fixed_reference_cases(dut): assert reject_reason == ref_results["reject_reason"] # === Test Case 2 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge message = 0b0000_0000_11110000_11110000_00000000 dut.message.value = message - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #240 value_b = int(dut.value_b.value) #240 @@ -48,10 +75,17 @@ async def fixed_reference_cases(dut): assert reject_reason == ref_results["reject_reason"] # === Test Case 3 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge message = 0b0000_0000_01010101_11001101_00000000 dut.message.value = message - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #85 value_b = int(dut.value_b.value) #205 @@ -67,10 +101,17 @@ async def fixed_reference_cases(dut): assert reject_reason == ref_results["reject_reason"] # === Test Case 4 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge message = 0b0000_0000_00000101_00000011_00000000 dut.message.value = message - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #5 value_b = int(dut.value_b.value) #3 @@ -86,10 +127,17 @@ async def fixed_reference_cases(dut): assert reject_reason == ref_results["reject_reason"] # === Test Case 5 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge message = 0b0000_0000_11001000_11001000_00000000 dut.message.value = message - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #200 value_b = int(dut.value_b.value) #200 @@ -105,10 +153,17 @@ async def fixed_reference_cases(dut): assert reject_reason == ref_results["reject_reason"] # === Test Case 6 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge message = 0b0001_0000_01010101_10101010_00000000 dut.message.value = message - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) #200 value_b = int(dut.value_b.value) #200 @@ -124,10 +179,17 @@ async def fixed_reference_cases(dut): assert reject_reason == ref_results["reject_reason"] # === Test Case 7 ==== # + await RisingEdge(dut.clk) # set next message at next rising edge message = 0b0001_0000_11110000_00001111_00000000 dut.message.value = message - await Timer(1, unit="ns") # allow message value to settle + dut.input_valid.value = 1 + await RisingEdge(dut.clk) # wait until posedge for input to be sampled + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage value_a = int(dut.value_a.value) value_b = int(dut.value_b.value) From ac16c661e75e47fb7a228a52b0cd61218f16078c Mon Sep 17 00:00:00 2001 From: Harry Leddy <34631808+harL06@users.noreply.github.com> Date: Fri, 29 May 2026 11:32:35 +0100 Subject: [PATCH 7/8] Update the random test cases cocotb for the sync design --- tb/cocotb/Makefile | 4 ++-- tb/cocotb/test_random_reference.py | 31 ++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/tb/cocotb/Makefile b/tb/cocotb/Makefile index bdf3ada..bec7156 100644 --- a/tb/cocotb/Makefile +++ b/tb/cocotb/Makefile @@ -15,8 +15,8 @@ COCOTB_TOPLEVEL = top_pipeline # ================================ # select which test should be used # ================================ -# COCOTB_TEST_MODULES = test_random_reference -COCOTB_TEST_MODULES = test_reference_cases +COCOTB_TEST_MODULES = test_random_reference +# COCOTB_TEST_MODULES = test_reference_cases # COCOTB_TEST_MODULES = test_direct_cases # include cocotb's make rules to take care of the simulator setup diff --git a/tb/cocotb/test_random_reference.py b/tb/cocotb/test_random_reference.py index 45c17c1..e3fd143 100644 --- a/tb/cocotb/test_random_reference.py +++ b/tb/cocotb/test_random_reference.py @@ -1,23 +1,43 @@ # Raondmised tests with outputs compared against Python Reference Model import cocotb -from cocotb.triggers import Timer +from cocotb.triggers import RisingEdge, ClockCycles, ReadOnly +from cocotb.clock import Clock from reference_model import reference_model import message_generator -NUM_TESTS = 1_000 # number of testcases to generate +NUM_TESTS = 10_000 # number of testcases to generate # Design under test @cocotb.test() async def random_reference_cases(dut): + # set up a 10ns cycle clock + clk = Clock(dut.clk, 10, "ns") + clk.start() + # set reset HIGH for start + dut.reset.value = 1 + dut.input_valid.value = 0 + dut.message.value = 0 + + await ClockCycles(dut.clk, 2) # wait two clock cycles, holding reset HIGH + + dut.reset.value = 0 # disable reset signal + + # begin test cases for i in range(NUM_TESTS): # generate random message, only allowing message type of 0 or 1 message = message_generator.generate_paramaterised_random_message(type_limit=1) - dut.message.value = message - await Timer(1, unit="ns") # allow message input value to settle before reading output + + dut.input_valid.value = 1 + await RisingEdge(dut.clk) # wait until posedge for input to be sampled allow message input value to settle before reading output + + dut.input_valid.value = 0 # don't read input after prev was sampled + + await RisingEdge(dut.clk) # wait for next posedge, output updated + await ReadOnly() # wait for values to settle and sim to enter "read only" stage # record output values from sim value_a = int(dut.value_a.value) @@ -37,5 +57,8 @@ async def random_reference_cases(dut): assert reject_reason == ref_results["reject_reason"], \ f"FAIL: simulated reject_reason ({reject_reason}) =/= model reject_reason ({ref_results["reject_reason"]})" + await RisingEdge(dut.clk) # set next message at next rising edge + + cocotb.log.info(f"{NUM_TESTS} tests ran successfully...") cocotb.log.info(f"{i+1}/{NUM_TESTS}: PASS") \ No newline at end of file From 093b85b1f267c8c43129b0a2a77f1c8247636225 Mon Sep 17 00:00:00 2001 From: Harry Leddy <34631808+harL06@users.noreply.github.com> Date: Fri, 29 May 2026 12:04:24 +0100 Subject: [PATCH 8/8] Update readmes flowcharts for sync pipeline design --- README.md | 19 ++++++++--------- rtl/README.md | 50 ++++++++++++++++++++++++++------------------- tb/cocotb/README.md | 3 ++- 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index a2368f6..d36b3c8 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,14 @@ The project implements a simple message processing pipeline that parses fixed-wi ```mermaid flowchart LR - A(input) --> |32-bit input message| B(message parser) - B --> |message type| C(message filter) - B --> |values| C - - B --> |values| D(Output Formatter) - C --> |accepted/rejected| D - D --> |values| E(output) - D --> |reject reason| E + A("32-bit message
input_valid") --> B["input register
(clk, reset)"] + B --> C["parser / filter / formatter"] + C --> D["output register
(clk, reset)"] + D --> E("value_a / value_b
reject_reason
output_valid") ``` +For flowchart with module-level detail, see [`rtl/README.md`](rtl/README.md). + ## Current Features - Synchronous Verilog message-processing pipeline @@ -41,6 +39,8 @@ flowchart LR The project currently includes fixed-case and randomised cocotb tests. The cocotb randomised tests run automatically using GitHub Actions. +For cocotb setup and test-running instructions, see [`tb/cocotb/README.md`](tb/cocotb/README.md). + | File | Purpose | | -------------------------- | ------------------------------------------------------------- | | `test_direct_cases.py` | Fixed input cases with manually specified expected outputs | @@ -63,5 +63,4 @@ Example `cocotb` Output: ## Status -Working simple RTL pipeline with Verilog and cocotb verification. -A Python reference model is used to check fixed and randomised test cases against expected behaviour. +Synchronous RTL pipeline is implemented and verified with fixed and randomised cocotb tests against a Python reference model. Vivado synthesis and post-synthesis timing checks have also been run for the current design. diff --git a/rtl/README.md b/rtl/README.md index 85105b0..aa28417 100644 --- a/rtl/README.md +++ b/rtl/README.md @@ -4,27 +4,35 @@ This folder contains the Verilog design files for the project. ## Current status -The setup includes a simple message parser, a filter to check for an invalid type and a pipeline to connect these and decide the output based on the parsed input and filter result. - -The top-level pipeline is also now synchronous, with registered input/output behaviour and `input_valid` / `output_valid` signalling. - -```text - ┌────────────────┐ message type ┌────────────────┐ - message │ ├──────────────────►│ │ reject reason -───────────────►│ message_parser │ │ message_filter ├───────────► - │ ├──────────────────►│ │ - └────────────────┘ values a&b └────┬────┬──────┘ - │ │ - │ │ - │ │ - │ │ - │ │ - input a&b│ │ valid message? - │ │ ┌───────────────────┐ output a&b - │ └───►│ │ - │ │ output_formatter ├───────────► - └────────►│ │ - └───────────────────┘ +The RTL implements a synchronous message-processing pipeline. + +`top_pipeline.v` provides a clocked wrapper, including reset handling, input sampling, registered outputs, and `input_valid` / `output_valid` signalling. + +The internal processing modules are all combinational: + +- `message_parser.v` splits the 32-bit input message into seperate fields. +- `message_filter.v` checks the parsed fields and produces an accept/reject decision. +- `output_formatter.v` sets the registered output values based on the filter result. + +```mermaid +flowchart LR + A("input") --> |"32-bit message"| B["input register"] + A --> |"input_valid"| B + + CLK["clk & reset"] -.-> B + CLK -.-> F["output registers"] + + B --> C["message_parser"] + C --> |message_type| D["message_filter"] + C --> |value_a & value_b| D + C --> |value_a & value_b| E["output_formatter"] + D --> |accept/reject| E + D --> |reject_reason| F + E --> |formatted values| F + + F --> |"value_a & value_b"| G("output") + F --> |"reject_reason"| G + F --> |"output_valid"| G ``` ## Message format diff --git a/tb/cocotb/README.md b/tb/cocotb/README.md index ff173c1..ff63382 100644 --- a/tb/cocotb/README.md +++ b/tb/cocotb/README.md @@ -31,11 +31,12 @@ sudo apt install iverilog ## Current status Current verification includes fixed cocotb tests, a Python reference model, and randomised RTL vs model testing. +The cocotb tests drive the synchronous pipeline by generating `clk`, `reset`, `input_valid`, and `output_valid`. - [x] Recreate Verilog TB in Python - [x] Write a Python software reference model to compare to the hardware Verilog design - [x] Randomised input testing against reference model -- [ ] Edge case testing +- [X] Edge case testing ## Files