From ea6a23f07c89d95153f7ce10f2b343545c992bca Mon Sep 17 00:00:00 2001 From: M Date: Sun, 25 Jan 2026 15:47:38 +0100 Subject: [PATCH 1/8] Add VGA --- .claude/CLAUDE.md | 61 ---- CLAUDE.md | 2 +- batch_update_tests.py | 78 +++++ docs/architecture.md | 17 +- hdl/cpu/cpu.v | 4 +- hdl/cpu/cpu_core_params.vh | 8 +- .../instruction_memory_axi.v | 9 +- hdl/framebuffer.v | 26 -- hdl/gpu.v | 88 ------ hdl/instruction_engine/instruction_engine.v | 150 --------- hdl/{ => vga_out}/vga_out.v | 27 +- tests/Makefile | 13 +- tests/cpu/constants.py | 8 +- .../cpu_integration_tests_harness.v | 9 +- .../integration_tests/test_add_instruction.py | 22 +- .../test_addi_instruction.py | 19 +- .../integration_tests/test_and_instruction.py | 17 +- .../test_andi_instruction.py | 17 +- .../test_auipc_instruction.py | 16 +- .../integration_tests/test_beq_instruction.py | 23 +- .../integration_tests/test_bge_instruction.py | 24 +- .../test_bgeu_instruction.py | 26 +- .../integration_tests/test_blt_instruction.py | 22 +- .../test_bltu_instruction.py | 25 +- .../integration_tests/test_bne_instruction.py | 23 +- .../test_instruction_fetch.py | 40 ++- .../integration_tests/test_jal_instruction.py | 11 +- .../test_jalr_instruction.py | 16 +- .../integration_tests/test_lb_instruction.py | 19 +- .../integration_tests/test_lbu_instruction.py | 19 +- .../integration_tests/test_lh_instruction.py | 17 +- .../integration_tests/test_lhu_instruction.py | 16 +- .../integration_tests/test_lui_instruction.py | 13 +- .../integration_tests/test_lw_instruction.py | 15 +- .../integration_tests/test_or_instruction.py | 18 +- .../integration_tests/test_ori_instruction.py | 17 +- tests/cpu/integration_tests/test_program.py | 19 +- .../integration_tests/test_sb_instruction.py | 18 +- .../integration_tests/test_sh_instruction.py | 11 +- .../integration_tests/test_sll_instruction.py | 12 +- .../test_slli_instruction.py | 12 +- .../integration_tests/test_slt_instruction.py | 12 +- .../test_slti_instruction.py | 12 +- .../test_sltiu_instruction.py | 12 +- .../test_sltu_instruction.py | 12 +- .../integration_tests/test_sra_instruction.py | 12 +- .../test_srai_instruction.py | 12 +- .../integration_tests/test_srl_instruction.py | 12 +- .../test_srli_instruction.py | 14 +- .../integration_tests/test_sub_instruction.py | 14 +- .../integration_tests/test_sw_instruction.py | 15 +- .../integration_tests/test_xor_instruction.py | 11 +- .../test_xori_instruction.py | 11 +- .../unit_tests/test_instruction_memory_axi.py | 10 +- tests/cpu/utils.py | 43 ++- tests/vga/__init__.py | 0 tests/vga/constants.py | 52 ++++ tests/vga/unit_tests/__init__.py | 0 tests/vga/unit_tests/vga_unit_tests_harness.v | 47 +++ tests/vga/utils.py | 93 ++++++ tools/debugger/commands.go | 36 ++- tools/debugger/opcodes.go | 23 +- tools/debugger/serial.go | 58 +++- tools/debugger/ui.go | 287 ++++++++++++++++-- 64 files changed, 1181 insertions(+), 624 deletions(-) delete mode 100644 .claude/CLAUDE.md create mode 100644 batch_update_tests.py delete mode 100644 hdl/framebuffer.v delete mode 100644 hdl/gpu.v delete mode 100644 hdl/instruction_engine/instruction_engine.v rename hdl/{ => vga_out}/vga_out.v (76%) create mode 100644 tests/vga/__init__.py create mode 100644 tests/vga/constants.py create mode 100644 tests/vga/unit_tests/__init__.py create mode 100644 tests/vga/unit_tests/vga_unit_tests_harness.v create mode 100644 tests/vga/utils.py diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md deleted file mode 100644 index 5965f39..0000000 --- a/.claude/CLAUDE.md +++ /dev/null @@ -1,61 +0,0 @@ -# GPU FPGA Project - -**Last updated**: 2026-01-05 - -Minimal computer on Arty S7-50 FPGA: RISC-V RV32I soft core + VGA video + UART debug. - -## Current Status - -- **CPU**: RV32I (no M/F/D extensions), unit + integration tests passing -- **Memory**: DDR3 operational @ 81.25 MHz (MIG initialized 2026-01-04) -- **Video**: VGA module exists, framebuffer not yet DDR3-backed -- **Debug**: UART debug peripheral working (`tools/debugger`) -- **Blocker**: None (DDR3 now functional) - -## Quick Start - -```bash -# Run tests -cd tests && source test_env/bin/activate && make - -# Debug via UART -go run tools/debugger/main.go - -# Build (future - not yet set up) -# cd tools/compiler && make -``` - -## Key Directories - -- `hdl/` - Verilog sources (cpu/, debug_peripheral/, vga_out.v, framebuffer.v, gpu.v) -- `tests/` - Verilator + cocotb tests (unit_tests/, integration_tests/) -- `tools/` - debugger/ (Go UART CLI), compiler/ (placeholder) -- `config/` - arty-s7-50.xdc (pin constraints, clocks, ILA debug) -- `docs/` - Human-facing setup guides - -## Documentation System - -This project uses **path-scoped rules** in `.claude/rules/` that auto-load when you work with matching files: - -- **Always loaded**: `.claude/rules/process.md` (documentation workflow) -- **When editing CPU**: `.claude/rules/architecture/cpu.md` -- **When editing memory**: `.claude/rules/architecture/memory.md` -- **When editing tests**: `.claude/rules/testing/tests.md` -- **When editing debug**: `.claude/rules/debug/debug.md` -- **When editing constraints**: `.claude/rules/architecture/mig-vivado.md` - -You don't need to manually read docs - the relevant rules load automatically based on which files you're working with. - -## Critical Constraints - -- **DDR3**: Requires 200 MHz ref_clk, Bank 34 only (voltage isolation) -- **UART**: 115200 baud @ 81.25 MHz ≈ 706 clocks/bit -- **Memory map**: ROM < 0x1000, RAM ≥ 0x1000 -- **Pipeline**: 3-stage, no hazard detection (insert NOPs manually) - -## Next Steps - -1. Boot CPU from DDR3 (load program, execute) -2. Connect framebuffer to DDR3 -3. Add game controller peripheral -4. Network peripheral (TBD) diff --git a/CLAUDE.md b/CLAUDE.md index 5965f39..2383b80 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,7 @@ You don't need to manually read docs - the relevant rules load automatically bas - **DDR3**: Requires 200 MHz ref_clk, Bank 34 only (voltage isolation) - **UART**: 115200 baud @ 81.25 MHz ≈ 706 clocks/bit -- **Memory map**: ROM < 0x1000, RAM ≥ 0x1000 +- **Memory map**: CPU base 0x80000000, ROM 0x80000000-0x80000FFF, RAM 0x80001000+, FB at end - **Pipeline**: 3-stage, no hazard detection (insert NOPs manually) ## Next Steps diff --git a/batch_update_tests.py b/batch_update_tests.py new file mode 100644 index 0000000..266eed4 --- /dev/null +++ b/batch_update_tests.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Batch update test files with halt/unhalt pattern.""" + +import re +import sys + +files_to_update = [ + 'test_ori', 'test_sub', 'test_xor', 'test_xori', + 'test_sll', 'test_slli', 'test_slt', 'test_slti', 'test_sltiu', 'test_sltu', + 'test_sra', 'test_srai', 'test_srl', 'test_srli', + 'test_lb', 'test_lbu', 'test_lh', 'test_lhu', 'test_lw', + 'test_sb', 'test_sh', 'test_sw', + 'test_lui', 'test_jal', 'test_jalr', +] + +base_path = '/home/emma/gpu/tests/cpu/integration_tests/' + +def update_file_with_halt_unhalt(filepath): + """Add halt/unhalt pattern to test file.""" + with open(filepath, 'r') as f: + content = f.read() + + # Pattern 1: For files with loops (most R-type and I-type ALU instructions) + # Match: await ClockCycles + write_word_to_mem + r_PC + registers + await ClockCycles(PIPELINE_CYCLES) + pattern1 = re.compile( + r'(\s+)(dut\.i_Reset\.value = 0\n\s+await ClockCycles\(dut\.i_Clock, 1\))\n\n' + r'(\s+)((?:write_word_to_mem|write_byte_to_mem|write_half_to_mem)[^\n]+\n)' + r'(\s+)(dut\.cpu\.r_PC\.value = [^\n]+\n)' + r'((?:\s+dut\.cpu\.reg_file\.Registers\[[^\]]+\]\.value = [^\n]+\n)+)' + r'(\s+)(await ClockCycles\(dut\.i_Clock, PIPELINE_CYCLES\))', + re.MULTILINE + ) + + def replacement1(match): + indent = match.group(1) + reset_line = match.group(2) + write_indent = match.group(3) + write_line = match.group(4) + pc_indent = match.group(5) + pc_line = match.group(6) + reg_lines = match.group(7) + await_indent = match.group(8) + await_line = match.group(9) + + return ( + f'{indent}{reset_line}\n\n' + f'{indent}# HALT CPU before setup\n' + f'{indent}await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT)\n' + f'{indent}await wait_for_pipeline_flush(dut)\n\n' + f'{indent}# Set up test while CPU is halted\n' + f'{write_indent}{write_line}' + f'{pc_indent}{pc_line}' + f'{reg_lines}' + f'{await_indent}await ClockCycles(dut.i_Clock, 1)\n\n' + f'{indent}# UNHALT CPU to start execution\n' + f'{indent}await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT)\n\n' + f'{await_indent}{await_line.replace("PIPELINE_CYCLES)", "PIPELINE_CYCLES + 3)")}' + ) + + # Try pattern 1 + new_content, count = pattern1.subn(replacement1, content) + + if count > 0: + with open(filepath, 'w') as f: + f.write(new_content) + print(f"Updated {filepath} ({count} replacements)") + return True + else: + print(f"No matches found in {filepath} - needs manual update") + return False + +if __name__ == '__main__': + for fname in files_to_update: + fpath = base_path + fname + '_instruction.py' + try: + update_file_with_halt_unhalt(fpath) + except Exception as e: + print(f"Error updating {fname}: {e}") diff --git a/docs/architecture.md b/docs/architecture.md index 19aa5b4..fae91cc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -17,20 +17,25 @@ For detailed CPU internals, see [../ai/cpu-architecture.md](../ai/cpu-architectu ## Memory Map ``` -0x00000000 - 0x00000FFF: Boot ROM (4KB) -0x10000000 - 0x1FFFFFFF: DDR3 RAM (via MIG, 256MB planned) -0x20000000 - 0x2FFFFFFF: Framebuffer -0x30000000 - 0x3FFFFFFF: Peripherals -0x40000000 - 0x4FFFFFFF: Debug peripheral +0x80000000 - 0x80000FFF: Boot ROM (4KB BRAM, internal to CPU) +0x80001000 - 0x87F1DFFF: General RAM (~127MB DDR3 via MIG) +0x87F1E000 - 0x87F8EFFF: Framebuffer 0 (462,848 bytes, 640x480x12bpp) +0x87F8F000 - 0x87FFFFFF: Framebuffer 1 (462,848 bytes, 640x480x12bpp) ``` +Key addresses: +- CPU_BASE_ADDR: 0x80000000 (PC starts here on reset) +- ROM_BOUNDARY_ADDR: 0x80000FFF (last ROM address) +- RAM_START_ADDR: 0x80001000 (first DDR3 address) +- Framebuffers are 4K-aligned for DMA compatibility + See [../ai/memory-map.md](../ai/memory-map.md) for detailed memory layout. ## Video System - VGA output: 640x480 @ 60Hz - Dual framebuffer for tear-free rendering -- Pixel format: TBD (likely 8-bit indexed color) +- Pixel format: 12-bit RGB (4 bits per channel) The video system uses double buffering to prevent tearing. While one framebuffer is being displayed, the CPU can write to the other. A register controls which buffer is active. diff --git a/hdl/cpu/cpu.v b/hdl/cpu/cpu.v index f988658..c7e28ac 100644 --- a/hdl/cpu/cpu.v +++ b/hdl/cpu/cpu.v @@ -360,7 +360,9 @@ module cpu ( wire w_Retire = w_Retire_Reg || w_Store_Commit; always @(posedge i_Clock) begin - if (!w_Reset) begin + if (w_Reset) begin + r_PC <= CPU_BASE_ADDR; + end else begin if(w_Debug_Write_PC_Enable && w_Pipeline_Flushed) begin r_PC <= w_Debug_Write_PC_Data; end else if (!w_Stall_S1 && w_Instruction_Valid && w_Enable_Instruction_Fetch) begin diff --git a/hdl/cpu/cpu_core_params.vh b/hdl/cpu/cpu_core_params.vh index c35ba0f..2fba2eb 100644 --- a/hdl/cpu/cpu_core_params.vh +++ b/hdl/cpu/cpu_core_params.vh @@ -24,7 +24,13 @@ localparam REG_WRITE_NONE = 5; localparam CLOCK_FREQUENCY = 81_247_969; -localparam ROM_BOUNDARY_ADDR = 32'hFFF; +// Memory map constants +localparam CPU_BASE_ADDR = 32'h80000000; +localparam ROM_SIZE = 32'h00001000; +localparam ROM_BOUNDARY_ADDR = CPU_BASE_ADDR + ROM_SIZE - 1; +localparam FRAMEBUFFER_0_ADDR = 32'h87F1E000; +localparam FRAMEBUFFER_1_ADDR = 32'h87F8F000; +localparam FRAMEBUFFER_SIZE = 32'h71000; // UART parameters localparam UART_BAUD_RATE = 115200; diff --git a/hdl/cpu/instruction_memory/instruction_memory_axi.v b/hdl/cpu/instruction_memory/instruction_memory_axi.v index d53613e..3e78c90 100644 --- a/hdl/cpu/instruction_memory/instruction_memory_axi.v +++ b/hdl/cpu/instruction_memory/instruction_memory_axi.v @@ -4,7 +4,7 @@ module instruction_memory_axi ( input i_Reset, input i_Clock, - input i_Enable_Fetch, // Allows new fetch commands to be issued + input i_Enable_Fetch, // Allows new fetch commands to be issued input [XLEN-1:0] i_Instruction_Addr, output reg [XLEN-1:0] o_Instruction, output o_Instruction_Valid, @@ -28,7 +28,7 @@ module instruction_memory_axi ( output s_axil_bready ); - reg [31:0] rom[0:(ROM_BOUNDARY_ADDR>>2)]; // ROM Instruction Memory + reg [31:0] rom[0:(ROM_SIZE>>2)-1]; // ROM Instruction Memory (1024 words = 4KB) initial begin $readmemh("rom.mem", rom); @@ -78,9 +78,12 @@ module instruction_memory_axi ( assign o_Instruction_Valid = (r_State == READ_SUCCESS); // assign o_Fetch_Busy = (r_State != IDLE); + // Calculate offset from CPU base address for ROM indexing + wire [31:0] w_Rom_Offset = i_Instruction_Addr - CPU_BASE_ADDR; + always @(*) begin if (i_Instruction_Addr <= ROM_BOUNDARY_ADDR && i_Enable_Fetch) begin - o_Instruction = rom[i_Instruction_Addr[11:2]]; + o_Instruction = rom[w_Rom_Offset[11:2]]; end else if (r_State == READ_SUCCESS) begin o_Instruction = s_axil_rdata; end else begin diff --git a/hdl/framebuffer.v b/hdl/framebuffer.v deleted file mode 100644 index 9de120b..0000000 --- a/hdl/framebuffer.v +++ /dev/null @@ -1,26 +0,0 @@ -`timescale 1ns / 1ps - -module framebuffer #( - parameter BITS_PER_PIXEL = 4, - parameter FRAMEBUFFER_DEPTH = 640 * 480 -) ( - input i_Clock, - input [31:0] i_Read_Addr, - input i_Write_Enable, - input [31:0] i_Write_Addr, - input [BITS_PER_PIXEL-1:0] i_Write_Data, - output reg [BITS_PER_PIXEL-1:0] o_Read_Data -); - - localparam FRAMEBUFFER_CUTOFF = 640 * 300; - - (* ram_style = "block" *) reg [BITS_PER_PIXEL-1:0] Frame_Buffer[0:FRAMEBUFFER_CUTOFF-1]; - - always @(posedge i_Clock) begin - if (i_Write_Enable && i_Write_Addr < FRAMEBUFFER_CUTOFF) - Frame_Buffer[i_Write_Addr] <= i_Write_Data; - - o_Read_Data <= i_Read_Addr < FRAMEBUFFER_CUTOFF ? Frame_Buffer[i_Read_Addr] : 0; - end - -endmodule diff --git a/hdl/gpu.v b/hdl/gpu.v deleted file mode 100644 index 38c2126..0000000 --- a/hdl/gpu.v +++ /dev/null @@ -1,88 +0,0 @@ -`timescale 1ns / 1ps - -module gpu ( - input i_Clock, - input i_Uart_Tx_In, - output o_Horizontal_Sync, - output o_Vertical_Sync, - output [11:0] o_RGB, - output o_Write_Enable -); - - localparam CLOCK_FREQUENCY = 100_000_000; - localparam UART_BAUD_RATE = 115200; - localparam UART_CLOCKS_PER_BIT = (CLOCK_FREQUENCY / UART_BAUD_RATE); - - localparam RESOLUTION_W = 640; - localparam RESOLUTION_H = 480; - localparam FRAMEBUFFER_DEPTH = RESOLUTION_W * RESOLUTION_H; - localparam BITS_PER_PIXEL = 12; - - wire w_Rx_DV; - wire [7:0] w_Rx_Byte; - uart_receiver #( - .CLKS_PER_BIT(UART_CLOCKS_PER_BIT) - ) UART_RECEIVER ( - .i_Reset(), - .i_Clock(i_Clock), - .i_Rx_Serial(i_Uart_Tx_In), - .o_Rx_DV(w_Rx_DV), - .o_Rx_Byte(w_Rx_Byte) - ); - - wire [31:0] w_Read_Addr; - wire [BITS_PER_PIXEL-1:0] w_Read_Data; - wire [BITS_PER_PIXEL-1:0] w_Write_Data; - wire [31:0] w_Write_Addr; - wire w_Write_Enable; - framebuffer #( - .BITS_PER_PIXEL(BITS_PER_PIXEL), - .FRAMEBUFFER_DEPTH(FRAMEBUFFER_DEPTH) - ) FRAMEBUFFER ( - .i_Clock(i_Clock), - .i_Read_Addr(w_Read_Addr), - .i_Write_Enable(w_Write_Enable), - .i_Write_Addr(w_Write_Addr), - .i_Write_Data(w_Write_Data), - .o_Read_Data(w_Read_Data) - ); - - // instruction_engine #( - // .BITS_PER_PIXEL(BITS_PER_PIXEL), - // .FRAMEBUFFER_DEPTH(FRAMEBUFFER_DEPTH) - // ) INSTRUCTION_ENGINE ( - // .i_Clock(i_Clock), - // .i_Rx_DV(w_Rx_DV), - // .i_Rx_Byte(w_Rx_Byte), - // .o_Write_Enable(w_Write_Enable), - // .o_Write_Addr(w_Write_Addr), - // .o_Write_Data(w_Write_Data) - // ); - - vga_out #( - .BITS_PER_PIXEL(BITS_PER_PIXEL), - .FRAMEBUFFER_DEPTH(FRAMEBUFFER_DEPTH) - ) VGA_OUT ( - .i_Clock(i_Clock), - .i_Fb_Read_Data(w_Read_Data), - .o_Fb_Read_Addr(w_Read_Addr), - .o_Horizontal_Sync(o_Horizontal_Sync), - .o_Vertical_Sync(o_Vertical_Sync), - .o_RGB(o_RGB) - ); - - - // cpu CPU( - // .i_Clock(i_Clock), - // .i_Write_Enable(w_Write_Enable), - // .i_Load_Store_Type(3'b101), // LS_TYPE_STORE_WORD - // .i_Instruction_Addr(32'b0), - // .i_Data_Addr(32'b0), - // .i_Write_Data(32'b0), - // .o_Read_Data(), - // .o_Instruction() - // ); - - assign o_Write_Enable = w_Write_Enable; - -endmodule diff --git a/hdl/instruction_engine/instruction_engine.v b/hdl/instruction_engine/instruction_engine.v deleted file mode 100644 index 035466f..0000000 --- a/hdl/instruction_engine/instruction_engine.v +++ /dev/null @@ -1,150 +0,0 @@ -`timescale 1ns / 1ps - -module instruction_engine #( - parameter BITS_PER_PIXEL = 12, - parameter FRAMEBUFFER_DEPTH = 640 * 480 -) ( - input i_Clock, - input i_Rx_DV, - input [7:0] i_Rx_Byte, - output reg o_Write_Enable, - output reg [31:0] o_Write_Addr, - output reg [BITS_PER_PIXEL-1:0] o_Write_Data -); - - localparam s_IDLE = 2'd0; - localparam s_DECODE_AND_EXECUTE = 2'd1; - localparam s_EXECUTE = 2'd2; - - localparam op_NOP = 8'b000; - localparam op_RED = 8'b001; - localparam op_GREEN = 8'b010; - localparam op_BLUE = 8'b011; - localparam op_FRAME = 8'b100; - localparam op_STORE = 8'b101; - localparam op_DRAW = 8'b110; - localparam op_RESERVED = 8'b111; - - localparam s_RED = 0; - localparam s_GREEN = 1; - localparam s_BLUE = 2; - - reg [1:0] r_State = s_IDLE; - reg [7:0] r_Op_Code = 0; - reg [31:0] r_Pixel_Index = 0; - reg [1:0] r_Which_Color = s_RED; - - - reg [3:0] r_Red; - reg [3:0] r_Green; - reg [3:0] r_Blue; - - reg r_Next_State = 0; - - always @* begin - if (r_State != s_IDLE) begin - case (r_Op_Code) - op_NOP: begin - r_Next_State = 1; - o_Write_Enable = 0; - o_Write_Addr = 0; - o_Write_Data = 0; - end - // op_RED: begin - // if (r_Pixel_Index == FRAMEBUFFER_DEPTH - 1) r_Next_State = 1; - // else begin - // r_Next_State = 0; - // end - // o_Write_Enable = 1; - // o_Write_Addr = r_Pixel_Index; - // o_Write_Data = 12'b100; - // end - // op_GREEN: begin - // if (r_Pixel_Index == FRAMEBUFFER_DEPTH - 1) r_Next_State = 1; - // else begin - // r_Next_State = 0; - // end - // o_Write_Enable = 1; - // o_Write_Addr = r_Pixel_Index; - // o_Write_Data = 12'b010; - // end - // op_BLUE: begin - // if (r_Pixel_Index == FRAMEBUFFER_DEPTH - 1) r_Next_State = 1; - // else begin - // r_Next_State = 0; - // end - // o_Write_Enable = 1; - // o_Write_Addr = r_Pixel_Index; - // o_Write_Data = 3'b001; - // end - op_FRAME: begin - if (r_Pixel_Index == FRAMEBUFFER_DEPTH - 1) r_Next_State = 1; - else begin - r_Next_State = 0; - end - o_Write_Addr = r_Pixel_Index; - o_Write_Data = {r_Blue, r_Green, r_Red}; - o_Write_Enable = r_Which_Color == s_BLUE; - - end - // op_STORE: - // begin - // r_End_Of_Decode = 1; - // end - // op_DRAW: - // begin - // r_End_Of_Decode = 1; - // end - default: begin - r_Next_State = 1; - o_Write_Enable = 0; - o_Write_Addr = 0; - o_Write_Data = 0; - end - endcase - end else begin - o_Write_Addr = 0; - o_Write_Data = 0; - o_Write_Enable = 0; - r_Next_State = 0; - end - end - - - always @(posedge i_Clock) begin - if (i_Rx_DV) begin - case (r_State) - s_IDLE: begin - r_Op_Code <= i_Rx_Byte; - r_State <= s_DECODE_AND_EXECUTE; - r_Which_Color <= s_RED; - r_Pixel_Index <= 0; - end - s_DECODE_AND_EXECUTE: begin - if (r_Next_State) begin - r_Pixel_Index <= 0; - r_State <= s_IDLE; - end else begin - if (r_Which_Color == s_RED) begin - r_Red <= i_Rx_Byte; - r_Which_Color <= r_Which_Color + 1; - end else if (r_Which_Color == s_GREEN) begin - r_Green <= i_Rx_Byte; - r_Which_Color <= r_Which_Color + 1; - end else begin - r_Blue <= i_Rx_Byte; - r_Pixel_Index <= r_Pixel_Index + 1; - r_Which_Color <= s_RED; - end - r_State <= s_DECODE_AND_EXECUTE; - end - end - default: begin - r_State <= s_IDLE; - r_Pixel_Index <= 0; - end - endcase - end - end - -endmodule diff --git a/hdl/vga_out.v b/hdl/vga_out/vga_out.v similarity index 76% rename from hdl/vga_out.v rename to hdl/vga_out/vga_out.v index 13f7e49..3c32a0d 100644 --- a/hdl/vga_out.v +++ b/hdl/vga_out/vga_out.v @@ -1,13 +1,20 @@ `timescale 1ns / 1ps module vga_out #( - parameter BITS_PER_PIXEL = 4, - parameter FRAMEBUFFER_DEPTH = 640 * 480 + parameter BITS_PER_COLOR_CHANNEL = 4 ) ( input i_Clock, - input [BITS_PER_PIXEL-1:0] i_Fb_Read_Data, - output [31:0] o_Fb_Read_Addr, - output [BITS_PER_PIXEL-1:0] o_RGB, + + // AXI-Stream Interface + input [15:0] s_axis_tdata, + input s_axis_tvalid, + output s_axis_tready, + + output o_mm2s_fsync, + + output [BITS_PER_COLOR_CHANNEL-1:0] o_Red, + output [BITS_PER_COLOR_CHANNEL-1:0] o_Green, + output [BITS_PER_COLOR_CHANNEL-1:0] o_Blue, output o_Horizontal_Sync, output o_Vertical_Sync ); @@ -72,8 +79,14 @@ module vga_out #( end end - assign o_Fb_Read_Addr = (r_V_Counter * VISIBLE_H[15:0]) + {16'd0, r_H_Counter}; - assign o_RGB = (w_Visible) ? i_Fb_Read_Data : 0; + + assign s_axis_tready = w_Visible; + assign o_mm2s_fsync = (r_H_Counter == 0) && (r_V_Counter == 0); + + assign o_Red = (s_axis_tready && s_axis_tvalid) ? s_axis_tdata[15:12] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Green = (s_axis_tready && s_axis_tvalid) ? s_axis_tdata[10:7] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Blue = (s_axis_tready && s_axis_tvalid) ? s_axis_tdata[4:1] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Horizontal_Sync = ~(r_H_State == STATE_SYNC); // Invert for active low assign o_Vertical_Sync = ~(r_V_State == STATE_SYNC); // Invert for active low diff --git a/tests/Makefile b/tests/Makefile index 6fa07e8..231a4f5 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -71,8 +71,11 @@ CPU_INTEGRATION_TESTS_MODULE = "cpu.integration_tests.test_instruction_fetch, \ cpu.integration_tests.test_debug_read_pc, \ cpu.integration_tests.test_debug_read_register, \ cpu.integration_tests.test_debug_write_register, \ - cpu.integration_tests.test_debug_write_pc" + cpu.integration_tests.test_debug_write_pc" +VGA_UNIT_TESTS_TOPLEVEL = vga_unit_tests_harness +VGA_UNIT_TESTS_MODULE = "vga.unit_tests.test_vga_timing, \ + vga.unit_tests.test_vga_data" TEST_TYPE ?= unit @@ -83,6 +86,8 @@ all: $(MAKE) TEST_TYPE=unit $(MAKE) clean TEST_TYPE=integration $(MAKE) TEST_TYPE=integration + $(MAKE) clean TEST_TYPE=vga + $(MAKE) TEST_TYPE=vga else ifeq ($(TEST_TYPE),unit) @@ -97,6 +102,12 @@ ifeq ($(TEST_TYPE),integration) VERILOG_SOURCES += ./cpu/integration_tests/cpu_integration_tests_harness.v endif +ifeq ($(TEST_TYPE),vga) + TOPLEVEL = $(VGA_UNIT_TESTS_TOPLEVEL) + MODULE = $(VGA_UNIT_TESTS_MODULE) + VERILOG_SOURCES += ./vga/unit_tests/vga_unit_tests_harness.v +endif + export SIM TOPLEVEL_LANG VERILOG_SOURCES TOPLEVEL MODULE EXTRA_ARGS PYTHONPATH include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/tests/cpu/constants.py b/tests/cpu/constants.py index b6ee233..998cbce 100644 --- a/tests/cpu/constants.py +++ b/tests/cpu/constants.py @@ -90,7 +90,13 @@ # Number of cycles the pipeline takes to complete an instruction PIPELINE_CYCLES = 13 -ROM_BOUNDARY_ADDR = 0x1000 # 4kB +# Memory map constants +CPU_BASE_ADDR = 0x80000000 +ROM_BOUNDARY_ADDR = 0x80000FFF +RAM_START_ADDR = 0x80001000 +FRAMEBUFFER_0_ADDR = 0x87F1E000 +FRAMEBUFFER_1_ADDR = 0x87F8F000 +FRAMEBUFFER_SIZE = 0x71000 CLOCK_FREQUENCY = 81_247_969 # 81.247969 MHz diff --git a/tests/cpu/integration_tests/cpu_integration_tests_harness.v b/tests/cpu/integration_tests/cpu_integration_tests_harness.v index 5d499af..35fed1e 100644 --- a/tests/cpu/integration_tests/cpu_integration_tests_harness.v +++ b/tests/cpu/integration_tests/cpu_integration_tests_harness.v @@ -59,19 +59,22 @@ module cpu_integration_tests_harness (); ); // verilator lint_on PINMISSING + wire [31:0] w_axil_data_memory_adjusted_araddr = s_data_memory_axil_araddr - 32'h80000000; + wire [31:0] w_axil_data_memory_adjusted_awaddr = s_data_memory_axil_awaddr - 32'h80000000; + wire [31:0] w_axil_instruction_memory_adjusted_araddr = s_instruction_memory_axil_araddr - 32'h00000000; // verilator lint_off PINMISSING axil_ram data_ram ( .rst(i_Reset), .clk(i_Clock), - .s_axil_araddr(s_data_memory_axil_araddr[15:0]), + .s_axil_araddr(w_axil_data_memory_adjusted_araddr[15:0]), .s_axil_arvalid(s_data_memory_axil_arvalid), .s_axil_arready(s_data_memory_axil_arready), .s_axil_rdata(s_data_memory_axil_rdata), .s_axil_rvalid(s_data_memory_axil_rvalid), .s_axil_rready(s_data_memory_axil_rready), .s_axil_awvalid(s_data_memory_axil_awvalid), - .s_axil_awaddr(s_data_memory_axil_awaddr[15:0]), + .s_axil_awaddr(w_axil_data_memory_adjusted_awaddr[15:0]), .s_axil_awready(s_data_memory_axil_awready), .s_axil_wvalid(s_data_memory_axil_wvalid), .s_axil_wdata(s_data_memory_axil_wdata), @@ -87,7 +90,7 @@ module cpu_integration_tests_harness (); axil_ram instruction_ram ( .rst(i_Reset), .clk(i_Clock), - .s_axil_araddr(s_instruction_memory_axil_araddr[15:0]), + .s_axil_araddr(w_axil_instruction_memory_adjusted_araddr[15:0]), .s_axil_arvalid(s_instruction_memory_axil_arvalid), .s_axil_arready(s_instruction_memory_axil_arready), .s_axil_rdata(s_instruction_memory_axil_rdata), diff --git a/tests/cpu/integration_tests/test_add_instruction.py b/tests/cpu/integration_tests/test_add_instruction.py index 423a17c..c87c1bd 100644 --- a/tests/cpu/integration_tests/test_add_instruction.py +++ b/tests/cpu/integration_tests/test_add_instruction.py @@ -5,11 +5,15 @@ from cpu.utils import ( gen_r_type_instruction, write_word_to_mem, + uart_send_byte, + wait_for_pipeline_flush, ) from cpu.constants import ( FUNC3_ALU_ADD_SUB, PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, + DEBUG_OP_HALT, + DEBUG_OP_UNHALT, ) wait_ns = 1 @@ -29,7 +33,7 @@ async def test_add_instruction(dut): (0xFFFFFFFF, 0x1, 0x0), # Wrap around case ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -45,13 +49,21 @@ async def test_add_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = start_address - write_word_to_mem(dut.instruction_ram.mem, start_address, add_instruction) + # HALT CPU before setup + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT) + await wait_for_pipeline_flush(dut) + # Set up test while CPU is halted + write_word_to_mem(dut.instruction_ram.mem, start_address, add_instruction) + dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await ClockCycles(dut.i_Clock, 1) + + # UNHALT CPU to start execution + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.signed_integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_addi_instruction.py b/tests/cpu/integration_tests/test_addi_instruction.py index 96303c2..fec76fc 100644 --- a/tests/cpu/integration_tests/test_addi_instruction.py +++ b/tests/cpu/integration_tests/test_addi_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_ADD_SUB, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_ADD_SUB, PIPELINE_CYCLES, RAM_START_ADDR, DEBUG_OP_HALT, DEBUG_OP_UNHALT wait_ns = 1 @@ -11,13 +11,16 @@ async def test_addi_instruction(dut): """Test addi instruction""" - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs1_value = 0x20 rd = 3 imm_value = 0x10 expected_result = rs1_value + imm_value + instruction = gen_i_type_instruction(OP_I_TYPE_ALU, rd, FUNC3_ALU_ADD_SUB, rs1, imm_value) + write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -26,12 +29,12 @@ async def test_addi_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - instruction = gen_i_type_instruction(OP_I_TYPE_ALU, rd, FUNC3_ALU_ADD_SUB, rs1, imm_value) - - write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) - dut.cpu.r_PC.value = start_address + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) assert dut.cpu.reg_file.Registers[rd].value.integer == expected_result, f"ADD instruction failed: Rd value is {dut.cpu.reg_file.Registers[rd].value.integer:#010x}, expected {expected_result:#010x}" diff --git a/tests/cpu/integration_tests/test_and_instruction.py b/tests/cpu/integration_tests/test_and_instruction.py index e6b2fa4..f195639 100644 --- a/tests/cpu/integration_tests/test_and_instruction.py +++ b/tests/cpu/integration_tests/test_and_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_r_type_instruction, write_word_to_mem -from cpu.constants import PIPELINE_CYCLES, FUNC3_ALU_AND, ROM_BOUNDARY_ADDR +from cpu.utils import gen_r_type_instruction, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush +from cpu.constants import PIPELINE_CYCLES, FUNC3_ALU_AND, RAM_START_ADDR, DEBUG_OP_HALT, DEBUG_OP_UNHALT wait_ns = 1 @@ -18,7 +18,7 @@ async def test_and_instruction(dut): (0, 0, 0), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -34,12 +34,21 @@ async def test_and_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + # HALT CPU before setup + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT) + await wait_for_pipeline_flush(dut) + + # Set up test while CPU is halted write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await ClockCycles(dut.i_Clock, 1) + + # UNHALT CPU to start execution + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, f"AND failed: rs1={rs1_value:#010x} rs2={rs2_value:#010x} -> rd={actual:#010x} expected={expected_result:#010x}" diff --git a/tests/cpu/integration_tests/test_andi_instruction.py b/tests/cpu/integration_tests/test_andi_instruction.py index 489fa2b..85975e3 100644 --- a/tests/cpu/integration_tests/test_andi_instruction.py +++ b/tests/cpu/integration_tests/test_andi_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_AND, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_AND, PIPELINE_CYCLES, RAM_START_ADDR, DEBUG_OP_HALT, DEBUG_OP_UNHALT wait_ns = 1 @@ -19,7 +19,7 @@ async def test_andi_instruction(dut): (0xFFFFFFFF, 0x0F0, 0x0F0), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rd = 3 @@ -32,12 +32,21 @@ async def test_andi_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + # HALT CPU before setup + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT) + await wait_for_pipeline_flush(dut) + + # Set up test while CPU is halted instruction = gen_i_type_instruction(OP_I_TYPE_ALU, rd, FUNC3_ALU_AND, rs1, imm_value) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF + await ClockCycles(dut.i_Clock, 1) + + # UNHALT CPU to start execution + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_auipc_instruction.py b/tests/cpu/integration_tests/test_auipc_instruction.py index fb47c4c..5530cdc 100644 --- a/tests/cpu/integration_tests/test_auipc_instruction.py +++ b/tests/cpu/integration_tests/test_auipc_instruction.py @@ -7,9 +7,11 @@ PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, + DEBUG_OP_HALT, + DEBUG_OP_UNHALT, ) -from cpu.utils import write_word_to_mem +from cpu.utils import send_unhalt_command, send_write_pc_command, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush wait_ns = 1 @@ -18,7 +20,7 @@ async def test_auipc_instruction(dut): """Test AUIPC instruction""" dest_register = 22 - start_address = ROM_BOUNDARY_ADDR + 512 + start_address = RAM_START_ADDR + 512 magic_value = 0x12345 auipc_instruction = OP_U_TYPE_AUIPC @@ -26,7 +28,6 @@ async def test_auipc_instruction(dut): auipc_instruction |= magic_value << 12 # immediate value write_word_to_mem(dut.instruction_ram.mem, start_address, auipc_instruction) - dut.cpu.r_PC.value = start_address clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -36,7 +37,12 @@ async def test_auipc_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + await send_unhalt_command(dut) + + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) result = dut.cpu.reg_file.Registers[dest_register].value.integer expected = (magic_value << 12) + start_address diff --git a/tests/cpu/integration_tests/test_beq_instruction.py b/tests/cpu/integration_tests/test_beq_instruction.py index d23fc9b..f8be49f 100644 --- a/tests/cpu/integration_tests/test_beq_instruction.py +++ b/tests/cpu/integration_tests/test_beq_instruction.py @@ -4,11 +4,14 @@ from cpu.utils import ( gen_b_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, + wait_for_pipeline_flush, ) from cpu.constants import ( FUNC3_BRANCH_BEQ, - ROM_BOUNDARY_ADDR + RAM_START_ADDR, ) wait_ns = 1 @@ -18,7 +21,7 @@ async def test_beq_instruction_when_equal(dut): """Test BEQ instruction""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0x200 rs2 = 3 @@ -29,7 +32,6 @@ async def test_beq_instruction_when_equal(dut): beq_instruction = gen_b_type_instruction(FUNC3_BRANCH_BEQ, rs1, rs2, offset) expected_pc = start_address + offset - dut.cpu.r_PC.value = start_address write_word_to_mem(dut.instruction_ram.mem, start_address, beq_instruction) clock = Clock(dut.i_Clock, wait_ns, "ns") @@ -40,8 +42,11 @@ async def test_beq_instruction_when_equal(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + await send_unhalt_command(dut) max_cycles = 100 for _ in range(max_cycles): @@ -57,7 +62,7 @@ async def test_beq_instruction_when_equal(dut): async def test_beq_instruction_when_not_equal(dut): """Test BEQ instruction""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0x200 rs2 = 3 @@ -67,20 +72,24 @@ async def test_beq_instruction_when_not_equal(dut): beq_instruction = gen_b_type_instruction(FUNC3_BRANCH_BEQ, rs1, rs2, offset) expected_pc = start_address + 4 + write_word_to_mem(dut.instruction_ram.mem, start_address, beq_instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) - dut.cpu.r_PC.value = start_address - write_word_to_mem(dut.instruction_ram.mem, start_address, beq_instruction) - dut.i_Reset.value = 1 await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + # UNHALT CPU to start execution + await send_unhalt_command(dut) + max_cycles = 100 for _ in range(max_cycles): await RisingEdge(dut.i_Clock) diff --git a/tests/cpu/integration_tests/test_bge_instruction.py b/tests/cpu/integration_tests/test_bge_instruction.py index 5ad82f8..7203b07 100644 --- a/tests/cpu/integration_tests/test_bge_instruction.py +++ b/tests/cpu/integration_tests/test_bge_instruction.py @@ -4,11 +4,14 @@ from cpu.utils import ( gen_b_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, + wait_for_pipeline_flush, ) from cpu.constants import ( FUNC3_BRANCH_BGE, - ROM_BOUNDARY_ADDR + RAM_START_ADDR, ) wait_ns = 1 @@ -16,7 +19,7 @@ @cocotb.test() async def test_bge_instruction_when_ge(dut): """Test BGE instruction: rs1 >= rs2""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0x200 rs2 = 3 @@ -25,18 +28,21 @@ async def test_bge_instruction_when_ge(dut): bge_instruction = gen_b_type_instruction(FUNC3_BRANCH_BGE, rs1, rs2, offset) expected_pc = start_address + offset + write_word_to_mem(dut.instruction_ram.mem, start_address, bge_instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) - + dut.i_Reset.value = 1 await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = start_address - write_word_to_mem(dut.instruction_ram.mem, start_address, bge_instruction) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + await send_unhalt_command(dut) max_cycles = 100 for _ in range(max_cycles): @@ -51,7 +57,7 @@ async def test_bge_instruction_when_ge(dut): @cocotb.test() async def test_bge_instruction_when_lt(dut): """Test BGE instruction: rs1 < rs2""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0x100 rs2 = 3 @@ -59,7 +65,7 @@ async def test_bge_instruction_when_lt(dut): offset = 1024 bge_instruction = gen_b_type_instruction(FUNC3_BRANCH_BGE, rs1, rs2, offset) expected_pc = start_address + 4 - dut.cpu.r_PC.value = start_address + write_word_to_mem(dut.instruction_ram.mem, start_address, bge_instruction) clock = Clock(dut.i_Clock, wait_ns, "ns") @@ -70,9 +76,13 @@ async def test_bge_instruction_when_lt(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + await send_unhalt_command(dut) + max_cycles = 100 for _ in range(max_cycles): await RisingEdge(dut.i_Clock) diff --git a/tests/cpu/integration_tests/test_bgeu_instruction.py b/tests/cpu/integration_tests/test_bgeu_instruction.py index a49b47a..c89fb81 100644 --- a/tests/cpu/integration_tests/test_bgeu_instruction.py +++ b/tests/cpu/integration_tests/test_bgeu_instruction.py @@ -4,11 +4,14 @@ from cpu.utils import ( gen_b_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, + wait_for_pipeline_flush, ) from cpu.constants import ( FUNC3_BRANCH_BGEU, - ROM_BOUNDARY_ADDR + RAM_START_ADDR, ) wait_ns = 1 @@ -16,7 +19,7 @@ @cocotb.test() async def test_bgeu_instruction_when_geu(dut): """Test BGEU instruction: rs1 >= rs2 (unsigned)""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0xFFFFFFFF rs2 = 3 @@ -24,7 +27,7 @@ async def test_bgeu_instruction_when_geu(dut): offset = 1024 bgeu_instruction = gen_b_type_instruction(FUNC3_BRANCH_BGEU, rs1, rs2, offset) expected_pc = start_address + offset - dut.cpu.r_PC.value = start_address + write_word_to_mem(dut.instruction_ram.mem, start_address, bgeu_instruction) clock = Clock(dut.i_Clock, wait_ns, "ns") @@ -35,9 +38,15 @@ async def test_bgeu_instruction_when_geu(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value - + await ClockCycles(dut.i_Clock, 1) + + await send_unhalt_command(dut) + max_cycles = 100 for _ in range(max_cycles): await RisingEdge(dut.i_Clock) @@ -51,7 +60,7 @@ async def test_bgeu_instruction_when_geu(dut): @cocotb.test() async def test_bgeu_instruction_when_ltu(dut): """Test BGEU instruction: rs1 < rs2 (unsigned)""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0x100 rs2 = 3 @@ -59,7 +68,7 @@ async def test_bgeu_instruction_when_ltu(dut): offset = 1024 bgeu_instruction = gen_b_type_instruction(FUNC3_BRANCH_BGEU, rs1, rs2, offset) expected_pc = start_address + 4 - dut.cpu.r_PC.value = start_address + write_word_to_mem(dut.instruction_ram.mem, start_address, bgeu_instruction) clock = Clock(dut.i_Clock, wait_ns, "ns") @@ -70,9 +79,12 @@ async def test_bgeu_instruction_when_ltu(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value - + await send_unhalt_command(dut) + max_cycles = 100 for _ in range(max_cycles): await RisingEdge(dut.i_Clock) diff --git a/tests/cpu/integration_tests/test_blt_instruction.py b/tests/cpu/integration_tests/test_blt_instruction.py index 0091a62..cb7c3ea 100644 --- a/tests/cpu/integration_tests/test_blt_instruction.py +++ b/tests/cpu/integration_tests/test_blt_instruction.py @@ -4,11 +4,14 @@ from cpu.utils import ( gen_b_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, + wait_for_pipeline_flush, ) from cpu.constants import ( FUNC3_BRANCH_BLT, - ROM_BOUNDARY_ADDR + RAM_START_ADDR, ) wait_ns = 1 @@ -16,7 +19,7 @@ @cocotb.test() async def test_blt_instruction_when_lt(dut): """Test BLT instruction: rs1 < rs2""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0x100 rs2 = 3 @@ -25,19 +28,21 @@ async def test_blt_instruction_when_lt(dut): blt_instruction = gen_b_type_instruction(FUNC3_BRANCH_BLT, rs1, rs2, offset) expected_pc = start_address + offset + write_word_to_mem(dut.instruction_ram.mem, start_address, blt_instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) - dut.cpu.r_PC.value = start_address - write_word_to_mem(dut.instruction_ram.mem, start_address, blt_instruction) - dut.i_Reset.value = 1 await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + await send_unhalt_command(dut) max_cycles = 100 for _ in range(max_cycles): @@ -52,7 +57,7 @@ async def test_blt_instruction_when_lt(dut): @cocotb.test() async def test_blt_instruction_when_ge(dut): """Test BLT instruction: rs1 >= rs2""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0x200 rs2 = 3 @@ -60,7 +65,7 @@ async def test_blt_instruction_when_ge(dut): offset = 1024 blt_instruction = gen_b_type_instruction(FUNC3_BRANCH_BLT, rs1, rs2, offset) expected_pc = start_address + 4 - dut.cpu.r_PC.value = start_address + write_word_to_mem(dut.instruction_ram.mem, start_address, blt_instruction) clock = Clock(dut.i_Clock, wait_ns, "ns") @@ -71,8 +76,11 @@ async def test_blt_instruction_when_ge(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + await send_unhalt_command(dut) max_cycles = 100 for _ in range(max_cycles): diff --git a/tests/cpu/integration_tests/test_bltu_instruction.py b/tests/cpu/integration_tests/test_bltu_instruction.py index 621c7be..d40e5d1 100644 --- a/tests/cpu/integration_tests/test_bltu_instruction.py +++ b/tests/cpu/integration_tests/test_bltu_instruction.py @@ -4,11 +4,17 @@ from cpu.utils import ( gen_b_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, + uart_send_byte, + wait_for_pipeline_flush, ) from cpu.constants import ( FUNC3_BRANCH_BLTU, - ROM_BOUNDARY_ADDR + RAM_START_ADDR, + DEBUG_OP_HALT, + DEBUG_OP_UNHALT, ) wait_ns = 1 @@ -16,7 +22,7 @@ @cocotb.test() async def test_bltu_instruction_when_ltu(dut): """Test BLTU instruction: rs1 < rs2 (unsigned)""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0x100 rs2 = 3 @@ -25,6 +31,8 @@ async def test_bltu_instruction_when_ltu(dut): bltu_instruction = gen_b_type_instruction(FUNC3_BRANCH_BLTU, rs1, rs2, offset) expected_pc = start_address + offset + write_word_to_mem(dut.instruction_ram.mem, start_address, bltu_instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -33,11 +41,12 @@ async def test_bltu_instruction_when_ltu(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = start_address - write_word_to_mem(dut.instruction_ram.mem, start_address, bltu_instruction) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value - + await send_unhalt_command(dut) + max_cycles = 100 for _ in range(max_cycles): await RisingEdge(dut.i_Clock) @@ -51,7 +60,7 @@ async def test_bltu_instruction_when_ltu(dut): @cocotb.test() async def test_bltu_instruction_when_geu(dut): """Test BLTU instruction: rs1 >= rs2 (unsigned)""" - start_address = ROM_BOUNDARY_ADDR + 16 + start_address = RAM_START_ADDR + 16 rs1 = 2 rs1_value = 0xFFFFFFFF rs2 = 3 @@ -68,10 +77,12 @@ async def test_bltu_instruction_when_geu(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = start_address + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, bltu_instruction) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + await send_unhalt_command(dut) max_cycles = 100 for _ in range(max_cycles): diff --git a/tests/cpu/integration_tests/test_bne_instruction.py b/tests/cpu/integration_tests/test_bne_instruction.py index cf69ce0..bfcc36e 100644 --- a/tests/cpu/integration_tests/test_bne_instruction.py +++ b/tests/cpu/integration_tests/test_bne_instruction.py @@ -5,6 +5,8 @@ from cpu.utils import ( gen_b_type_instruction, gen_i_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush, @@ -35,6 +37,9 @@ async def test_bne_instruction_when_not_equal(dut): poison_instruction = gen_i_type_instruction(OP_I_TYPE_ALU, rd_poison, FUNC3_ALU_ADD_SUB, 0, 0x42) expected_pc = start_address + offset + write_word_to_mem(dut.instruction_ram.mem, start_address, bne_instruction) + write_word_to_mem(dut.instruction_ram.mem, start_address + 4, poison_instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -43,26 +48,12 @@ async def test_bne_instruction_when_not_equal(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - # HALT CPU before setup - await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT) + await send_write_pc_command(dut, start_address) await wait_for_pipeline_flush(dut) - - # Write instructions to memory before setting PC - write_word_to_mem(dut.instruction_ram.mem, start_address, bne_instruction) - write_word_to_mem(dut.instruction_ram.mem, start_address + 4, poison_instruction) - - # Set PC and registers while CPU is halted - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value dut.cpu.reg_file.Registers[rd_poison].value = 0 - - # Wait for values to propagate - await ClockCycles(dut.i_Clock, 1) - - # UNHALT CPU to start execution - await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) - await ClockCycles(dut.i_Clock, 3) + await send_unhalt_command(dut) # Track pipeline flush signals flush_pipeline_seen = False diff --git a/tests/cpu/integration_tests/test_instruction_fetch.py b/tests/cpu/integration_tests/test_instruction_fetch.py index 388305e..b7e5144 100644 --- a/tests/cpu/integration_tests/test_instruction_fetch.py +++ b/tests/cpu/integration_tests/test_instruction_fetch.py @@ -2,8 +2,8 @@ from cocotb.triggers import RisingEdge, ClockCycles from cocotb.clock import Clock -from cpu.utils import write_instructions_rom, write_word_to_mem, write_instructions -from cpu.constants import ROM_BOUNDARY_ADDR +from cpu.utils import wait_for_pipeline_flush, write_instructions_rom, write_word_to_mem, write_instructions, uart_send_byte +from cpu.constants import CPU_BASE_ADDR, DEBUG_OP_HALT, DEBUG_OP_UNHALT, DEBUG_OP_WRITE_PC, RAM_START_ADDR, ROM_BOUNDARY_ADDR wait_ns = 1 @@ -13,9 +13,9 @@ async def test_single_instruction_fetch_dram(dut): dut._log.info("Starting instruction fetch test") test_instruction = 0x12345678 - write_word_to_mem(dut.instruction_ram.mem, ROM_BOUNDARY_ADDR, test_instruction) - write_word_to_mem(dut.instruction_ram.mem, ROM_BOUNDARY_ADDR + 4, 0x9ABCDEF0) - write_word_to_mem(dut.instruction_ram.mem, ROM_BOUNDARY_ADDR + 8, 0x0FEDCBA9) + write_word_to_mem(dut.instruction_ram.mem, RAM_START_ADDR, test_instruction) + write_word_to_mem(dut.instruction_ram.mem, RAM_START_ADDR + 4, 0x9ABCDEF0) + write_word_to_mem(dut.instruction_ram.mem, RAM_START_ADDR + 8, 0x0FEDCBA9) clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -24,7 +24,13 @@ async def test_single_instruction_fetch_dram(dut): await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 - dut.cpu.r_PC.value = ROM_BOUNDARY_ADDR + # HALT CPU before setup + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT) + await wait_for_pipeline_flush(dut) + + dut.cpu.r_PC.value = RAM_START_ADDR + + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) await RisingEdge(dut.cpu.w_Instruction_Valid) @@ -37,7 +43,7 @@ async def test_single_instruction_fetch_rom(dut): dut._log.info("Starting instruction fetch test") test_instruction = 0x12345678 - write_instructions_rom(dut.cpu.instruction_memory.rom, 0, [test_instruction, 0x9ABCDEF0, 0x0FEDCBA9]) + write_instructions_rom(dut.cpu.instruction_memory.rom, CPU_BASE_ADDR, [test_instruction, 0x9ABCDEF0, 0x0FEDCBA9]) clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -63,8 +69,10 @@ async def test_multiple_instruction_fetch_dram(dut): 0xB16B00B5, ] - write_instructions(dut.instruction_ram.mem, ROM_BOUNDARY_ADDR, instructions) - + write_instructions(dut.instruction_ram.mem, RAM_START_ADDR, instructions) + dut._log.info(f"Wrote instructions to DRAM at address {RAM_START_ADDR:#010x}") + dut._log.info(f"Instruction 0: {dut.instruction_ram.mem[0x1000].value.integer:#04x} {dut.instruction_ram.mem[0x1001].value.integer:#04x} {dut.instruction_ram.mem[0x1002].value.integer:#04x} {dut.instruction_ram.mem[0x1003].value.integer:#04x}") + dut._log.info(f"Instruction 1: {dut.instruction_ram.mem[0x1004].value.integer:#04x} {dut.instruction_ram.mem[0x1005].value.integer:#04x} {dut.instruction_ram.mem[0x1006].value.integer:#04x} {dut.instruction_ram.mem[0x1007].value.integer:#04x}") clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -73,10 +81,20 @@ async def test_multiple_instruction_fetch_dram(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = ROM_BOUNDARY_ADDR + # HALT CPU before setup + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_WRITE_PC) + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, (RAM_START_ADDR >> 0) & 0xFF) + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, (RAM_START_ADDR >> 8) & 0xFF) + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, (RAM_START_ADDR >> 16) & 0xFF) + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, (RAM_START_ADDR >> 24) & 0xFF) + + await ClockCycles(dut.i_Clock, 10) + + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) for i in range(1): await RisingEdge(dut.cpu.w_Instruction_Valid) + dut._log.info(f"Program Counter: {dut.cpu.r_PC.value.integer:#010x}") dut._log.info(f"Fetched instruction {i}: {dut.cpu.w_Instruction.value.integer:#010x}") dut._log.info(f"Instruction Valid: {dut.cpu.w_Instruction_Valid.value}") @@ -95,7 +113,7 @@ async def test_multiple_instruction_fetch_rom(dut): 0xB16B00B5, ] - write_instructions_rom(dut.cpu.instruction_memory.rom, 0, instructions) + write_instructions_rom(dut.cpu.instruction_memory.rom, CPU_BASE_ADDR, instructions) clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) diff --git a/tests/cpu/integration_tests/test_jal_instruction.py b/tests/cpu/integration_tests/test_jal_instruction.py index d7e6d5e..63db32d 100644 --- a/tests/cpu/integration_tests/test_jal_instruction.py +++ b/tests/cpu/integration_tests/test_jal_instruction.py @@ -4,9 +4,9 @@ from cpu.constants import ( OP_J_TYPE, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) -from cpu.utils import write_word_to_mem +from cpu.utils import send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush wait_ns = 1 @@ -15,7 +15,7 @@ async def test_jal_instruction(dut): """Test JAL instruction""" dest_register = 13 - start_address = ROM_BOUNDARY_ADDR + 256 + start_address = RAM_START_ADDR + 256 magic_value = 0b00000000000000000000001010101010000000000 j_type_imm = 0b10101010100 @@ -28,7 +28,6 @@ async def test_jal_instruction(dut): jal_instruction |= magic_value << 12 write_word_to_mem(dut.instruction_ram.mem, start_address, jal_instruction) - dut.cpu.r_PC.value = start_address clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -38,6 +37,10 @@ async def test_jal_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + await send_unhalt_command(dut) + max_cycles = 200 pc_reached = False reg_reached = False diff --git a/tests/cpu/integration_tests/test_jalr_instruction.py b/tests/cpu/integration_tests/test_jalr_instruction.py index 38da987..4c57f81 100644 --- a/tests/cpu/integration_tests/test_jalr_instruction.py +++ b/tests/cpu/integration_tests/test_jalr_instruction.py @@ -4,12 +4,15 @@ from cpu.utils import ( gen_i_type_instruction, + send_unhalt_command, + send_write_pc_command, + wait_for_pipeline_flush, + write_word_to_mem, ) from cpu.constants import ( OP_I_TYPE_JALR, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) -from cpu.utils import write_word_to_mem wait_ns = 1 @@ -18,7 +21,7 @@ async def test_jalr_instruction(dut): """Test JALR instruction""" - start_address = ROM_BOUNDARY_ADDR + 256 + start_address = RAM_START_ADDR + 256 dest_register = 1 rs1 = 2 rs1_value = 0x200 @@ -28,6 +31,8 @@ async def test_jalr_instruction(dut): expected_pc = rs1_value + i_type_imm expected_register_value = start_address + 4 + write_word_to_mem(dut.instruction_ram.mem, start_address, jalr_instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -36,9 +41,10 @@ async def test_jalr_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - write_word_to_mem(dut.instruction_ram.mem, start_address, jalr_instruction) - dut.cpu.r_PC.value = start_address + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value + await send_unhalt_command(dut) max_cycles = 200 pc_reached = False diff --git a/tests/cpu/integration_tests/test_lb_instruction.py b/tests/cpu/integration_tests/test_lb_instruction.py index 09ec390..200c13b 100644 --- a/tests/cpu/integration_tests/test_lb_instruction.py +++ b/tests/cpu/integration_tests/test_lb_instruction.py @@ -4,8 +4,11 @@ from cpu.utils import ( gen_i_type_instruction, - write_word_to_mem, + send_unhalt_command, + send_write_pc_command, write_byte_to_mem, + write_word_to_mem, + wait_for_pipeline_flush, ) from cpu.constants import ( OP_I_TYPE_LOAD, @@ -14,7 +17,7 @@ PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) wait_ns = 1 @@ -24,7 +27,7 @@ async def test_lb_instruction_when_equal(dut): """Test lb instruction""" - start_address = ROM_BOUNDARY_ADDR + 0 + start_address = RAM_START_ADDR + 0 rd = 1 rs1 = 2 rs1_value = 0 @@ -34,9 +37,7 @@ async def test_lb_instruction_when_equal(dut): lb_instruction = gen_i_type_instruction(OP_I_TYPE_LOAD, rd, FUNC3_LS_B, rs1, offset) - dut.cpu.r_PC.value = start_address write_word_to_mem(dut.instruction_ram.mem, start_address, lb_instruction) - dut.cpu.reg_file.Registers[rs1].value = rs1_value write_byte_to_mem(dut.data_ram.mem, mem_address, mem_value & 0xFF) clock = Clock(dut.i_Clock, wait_ns, "ns") @@ -47,7 +48,13 @@ async def test_lb_instruction_when_equal(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + dut.cpu.reg_file.Registers[rs1].value = rs1_value + await send_unhalt_command(dut) + + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) assert dut.cpu.reg_file.Registers[rd].value.integer == mem_value, f"LB instruction failed: Rd value is {dut.cpu.reg_file.Registers[rd].value.integer:#010x}, expected {mem_value:#010x}" diff --git a/tests/cpu/integration_tests/test_lbu_instruction.py b/tests/cpu/integration_tests/test_lbu_instruction.py index ce9c4c6..bed7c47 100644 --- a/tests/cpu/integration_tests/test_lbu_instruction.py +++ b/tests/cpu/integration_tests/test_lbu_instruction.py @@ -4,14 +4,17 @@ from cpu.utils import ( gen_i_type_instruction, + send_unhalt_command, write_word_to_mem, write_byte_to_mem, + send_write_pc_command, + wait_for_pipeline_flush, ) from cpu.constants import ( OP_I_TYPE_LOAD, FUNC3_LS_BU, PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) wait_ns = 1 @@ -22,19 +25,15 @@ async def test_lbu_instruction(dut): rd = 5 rs1 = 2 - start_address = ROM_BOUNDARY_ADDR + 0 + start_address = RAM_START_ADDR + 0 rs1_value = 0 offset = 0 mem_value = 0xAB mem_address = rs1_value + offset - write_byte_to_mem(dut.data_ram.mem, mem_address, mem_value & 0xFF) - lbu_instruction = gen_i_type_instruction(OP_I_TYPE_LOAD, rd, FUNC3_LS_BU, rs1, offset) - - dut.cpu.r_PC.value = start_address + write_byte_to_mem(dut.data_ram.mem, mem_address, mem_value & 0xFF) write_word_to_mem(dut.instruction_ram.mem, start_address, lbu_instruction) - dut.cpu.reg_file.Registers[rs1].value = rs1_value clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -42,6 +41,12 @@ async def test_lbu_instruction(dut): dut.i_Reset.value = 1 await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 + + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + dut.cpu.reg_file.Registers[rs1].value = rs1_value + await send_unhalt_command(dut) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) result = dut.cpu.reg_file.Registers[rd].value.integer diff --git a/tests/cpu/integration_tests/test_lh_instruction.py b/tests/cpu/integration_tests/test_lh_instruction.py index afdb1b3..2afe54c 100644 --- a/tests/cpu/integration_tests/test_lh_instruction.py +++ b/tests/cpu/integration_tests/test_lh_instruction.py @@ -4,6 +4,9 @@ from cpu.utils import ( gen_i_type_instruction, + send_unhalt_command, + send_write_pc_command, + wait_for_pipeline_flush, write_word_to_mem, write_half_to_mem, ) @@ -11,7 +14,7 @@ OP_I_TYPE_LOAD, FUNC3_LS_H, PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) wait_ns = 1 @@ -23,13 +26,15 @@ async def test_lh_instruction_when_equal(dut): rd = 5 rs1 = 2 - start_address = ROM_BOUNDARY_ADDR + 0 + start_address = RAM_START_ADDR + 0 mem_value = 0xBEEF - offset = 0 lh_instruction = gen_i_type_instruction(OP_I_TYPE_LOAD, rd, FUNC3_LS_H, rs1, offset) + write_half_to_mem(dut.data_ram.mem, start_address, mem_value & 0xFFFF) + write_word_to_mem(dut.instruction_ram.mem, RAM_START_ADDR + 0, lh_instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -37,10 +42,10 @@ async def test_lh_instruction_when_equal(dut): await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 - write_half_to_mem(dut.data_ram.mem, start_address, mem_value & 0xFFFF) - dut.cpu.r_PC.value = ROM_BOUNDARY_ADDR + 0 + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = start_address - write_word_to_mem(dut.instruction_ram.mem, ROM_BOUNDARY_ADDR + 0, lh_instruction) + await send_unhalt_command(dut) await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) diff --git a/tests/cpu/integration_tests/test_lhu_instruction.py b/tests/cpu/integration_tests/test_lhu_instruction.py index 42ae062..607b78f 100644 --- a/tests/cpu/integration_tests/test_lhu_instruction.py +++ b/tests/cpu/integration_tests/test_lhu_instruction.py @@ -4,8 +4,11 @@ from cpu.utils import ( gen_i_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, write_half_to_mem, + wait_for_pipeline_flush, ) from cpu.constants import ( OP_I_TYPE_LOAD, @@ -14,7 +17,7 @@ PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) wait_ns = 1 @@ -22,7 +25,7 @@ @cocotb.test() async def test_lhu_instruction(dut): """Test lhu instruction (unsigned halfword load)""" - start_address = ROM_BOUNDARY_ADDR + 96 + start_address = RAM_START_ADDR + 96 rd = 9 rs1 = 10 rs1_value = 400 @@ -35,15 +38,18 @@ async def test_lhu_instruction(dut): clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) + write_word_to_mem(dut.instruction_ram.mem, start_address, lhu_instruction) + write_half_to_mem(dut.data_ram.mem, mem_address, mem_value & 0xFFFF) + dut.i_Reset.value = 1 await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = start_address - write_word_to_mem(dut.instruction_ram.mem, start_address, lhu_instruction) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value - write_half_to_mem(dut.data_ram.mem, mem_address, mem_value & 0xFFFF) + await send_unhalt_command(dut) await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) diff --git a/tests/cpu/integration_tests/test_lui_instruction.py b/tests/cpu/integration_tests/test_lui_instruction.py index dac5b1a..1cabeba 100644 --- a/tests/cpu/integration_tests/test_lui_instruction.py +++ b/tests/cpu/integration_tests/test_lui_instruction.py @@ -5,9 +5,11 @@ from cpu.constants import ( OP_U_TYPE_LUI, PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, + DEBUG_OP_HALT, + DEBUG_OP_UNHALT, ) -from cpu.utils import write_word_to_mem +from cpu.utils import send_unhalt_command, send_write_pc_command, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush wait_ns = 1 @@ -15,13 +17,12 @@ async def test_lui_instruction(dut): """Test LUI instruction""" - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 lui_instruction = OP_U_TYPE_LUI lui_instruction |= 1 << 7 lui_instruction |= 0x12345 << 12 write_word_to_mem(dut.instruction_ram.mem, start_address, lui_instruction) - dut.cpu.r_PC.value = start_address clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -31,6 +32,10 @@ async def test_lui_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + await send_unhalt_command(dut) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) result = dut.cpu.reg_file.Registers[1].value.integer diff --git a/tests/cpu/integration_tests/test_lw_instruction.py b/tests/cpu/integration_tests/test_lw_instruction.py index 29108dc..94a1cbe 100644 --- a/tests/cpu/integration_tests/test_lw_instruction.py +++ b/tests/cpu/integration_tests/test_lw_instruction.py @@ -4,6 +4,9 @@ from cpu.utils import ( gen_i_type_instruction, + send_write_pc_command, + send_unhalt_command, + wait_for_pipeline_flush, write_word_to_mem, ) from cpu.constants import ( @@ -13,7 +16,7 @@ PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) wait_ns = 1 @@ -21,7 +24,7 @@ @cocotb.test() async def test_lw_instruction(dut): """Test lw instruction""" - start_address = ROM_BOUNDARY_ADDR + 64 + start_address = RAM_START_ADDR + 64 rd = 5 rs1 = 6 rs1_value = 0 @@ -31,10 +34,7 @@ async def test_lw_instruction(dut): lw_instruction = gen_i_type_instruction(OP_I_TYPE_LOAD, rd, FUNC3_LS_W, rs1, offset) - dut.cpu.r_PC.value = start_address write_word_to_mem(dut.instruction_ram.mem, start_address, lw_instruction) - dut.cpu.reg_file.Registers[rs1].value = rs1_value - write_word_to_mem(dut.data_ram.mem, mem_address, mem_value) clock = Clock(dut.i_Clock, wait_ns, "ns") @@ -45,6 +45,11 @@ async def test_lw_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + dut.cpu.reg_file.Registers[rs1].value = rs1_value + await send_unhalt_command(dut) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) assert dut.cpu.reg_file.Registers[rd].value.integer == mem_value, f"LW instruction failed: Rd value is {dut.cpu.reg_file.Registers[rd].value.integer:#010x}, expected {mem_value:#010x}" diff --git a/tests/cpu/integration_tests/test_or_instruction.py b/tests/cpu/integration_tests/test_or_instruction.py index 852cf47..485a677 100644 --- a/tests/cpu/integration_tests/test_or_instruction.py +++ b/tests/cpu/integration_tests/test_or_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_r_type_instruction, write_word_to_mem -from cpu.constants import FUNC3_ALU_OR, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_r_type_instruction, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush +from cpu.constants import FUNC3_ALU_OR, PIPELINE_CYCLES, RAM_START_ADDR, DEBUG_OP_HALT, DEBUG_OP_UNHALT wait_ns = 1 @@ -18,7 +18,7 @@ async def test_or_instruction(dut): (0, 0, 0), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -29,18 +29,26 @@ async def test_or_instruction(dut): for rs1_value, rs2_value, expected_result in tests: instruction = gen_r_type_instruction(rd, FUNC3_ALU_OR, rs1, rs2, 0) - # Reset per test case dut.i_Reset.value = 1 await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + # HALT CPU before setup + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT) + await wait_for_pipeline_flush(dut) + + # Set up test while CPU is halted write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await ClockCycles(dut.i_Clock, 1) + + # UNHALT CPU to start execution + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, f"OR failed: rs1={rs1_value:#010x} rs2={rs2_value:#010x} -> rd={actual:#010x} expected={expected_result:#010x}" diff --git a/tests/cpu/integration_tests/test_ori_instruction.py b/tests/cpu/integration_tests/test_ori_instruction.py index acb38d5..6ac2130 100644 --- a/tests/cpu/integration_tests/test_ori_instruction.py +++ b/tests/cpu/integration_tests/test_ori_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_OR, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_OR, PIPELINE_CYCLES, RAM_START_ADDR, DEBUG_OP_HALT, DEBUG_OP_UNHALT wait_ns = 1 @@ -20,7 +20,7 @@ async def test_ori_instruction(dut): (0xF0F0F0F0, 0x00F, 0xF0F0F0FF), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rd = 3 @@ -35,12 +35,21 @@ async def test_ori_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + # HALT CPU before setup + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT) + await wait_for_pipeline_flush(dut) + + # Set up test while CPU is halted instruction = ori_instruction_template(imm_value) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF + await ClockCycles(dut.i_Clock, 1) + + # UNHALT CPU to start execution + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_program.py b/tests/cpu/integration_tests/test_program.py index 3c431c6..00a6576 100644 --- a/tests/cpu/integration_tests/test_program.py +++ b/tests/cpu/integration_tests/test_program.py @@ -7,13 +7,16 @@ FUNC3_BRANCH_BLT, FUNC3_ALU_ADD_SUB, - - ROM_BOUNDARY_ADDR, + + RAM_START_ADDR, ) from cpu.utils import ( gen_i_type_instruction, gen_b_type_instruction, gen_r_type_instruction, + send_unhalt_command, + send_write_pc_command, + wait_for_pipeline_flush, write_instructions, ) @@ -36,8 +39,7 @@ async def test_sum_loop_ram(dut): gen_i_type_instruction(OP_I_TYPE_ALU, 0, FUNC3_ALU_ADD_SUB, 0, 0), # addi $0, $0, 0 (nop) ] - start_address = ROM_BOUNDARY_ADDR + 0x0 - dut.cpu.r_PC.value = start_address + start_address = RAM_START_ADDR + 0x0 write_instructions(dut.instruction_ram.mem, start_address, instructions) haltInstructions = 100 @@ -50,6 +52,10 @@ async def test_sum_loop_ram(dut): await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + await send_unhalt_command(dut) + while dut.cpu.r_PC.value.integer < endAddress and haltInstructions > 0: await ClockCycles(dut.i_Clock, 4) haltInstructions -= 1 @@ -80,7 +86,6 @@ async def test_sum_loop_rom_file(dut): ] start_address = 512 - dut.cpu.r_PC.value = start_address haltInstructions = 100 endAddress = start_address + len(instructions) * 4 @@ -92,6 +97,10 @@ async def test_sum_loop_rom_file(dut): await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) + await send_unhalt_command(dut) + while dut.cpu.r_PC.value.integer < endAddress and haltInstructions > 0: await ClockCycles(dut.i_Clock, 4) haltInstructions -= 1 diff --git a/tests/cpu/integration_tests/test_sb_instruction.py b/tests/cpu/integration_tests/test_sb_instruction.py index 3f28dc9..c70c3c3 100644 --- a/tests/cpu/integration_tests/test_sb_instruction.py +++ b/tests/cpu/integration_tests/test_sb_instruction.py @@ -4,14 +4,20 @@ from cpu.utils import ( gen_s_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, + uart_send_byte, + wait_for_pipeline_flush, ) from cpu.constants import ( FUNC3_LS_B, PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, + DEBUG_OP_HALT, + DEBUG_OP_UNHALT, ) wait_ns = 1 @@ -20,7 +26,7 @@ @cocotb.test() async def test_sb_instruction(dut): """Test sb instruction""" - start_address = ROM_BOUNDARY_ADDR + 0x40 + start_address = RAM_START_ADDR + 0x40 rs1 = 0x4 rs2 = 0x5 rs1_value = 0 @@ -31,6 +37,8 @@ async def test_sb_instruction(dut): sh_instruction = gen_s_type_instruction(FUNC3_LS_B, rs1, rs2, imm_value) + write_word_to_mem(dut.instruction_ram.mem, start_address, sh_instruction) + clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) @@ -39,10 +47,12 @@ async def test_sb_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = start_address - write_word_to_mem(dut.instruction_ram.mem, start_address, sh_instruction) + + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + await send_unhalt_command(dut) await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) diff --git a/tests/cpu/integration_tests/test_sh_instruction.py b/tests/cpu/integration_tests/test_sh_instruction.py index 65148a6..1cf026f 100644 --- a/tests/cpu/integration_tests/test_sh_instruction.py +++ b/tests/cpu/integration_tests/test_sh_instruction.py @@ -4,6 +4,9 @@ from cpu.utils import ( gen_s_type_instruction, + send_unhalt_command, + send_write_pc_command, + wait_for_pipeline_flush, write_word_to_mem, ) from cpu.constants import ( @@ -11,7 +14,7 @@ PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) wait_ns = 1 @@ -19,7 +22,7 @@ @cocotb.test() async def test_sh_instruction(dut): """Test sh instruction""" - start_address = ROM_BOUNDARY_ADDR + 0x40 + start_address = RAM_START_ADDR + 0x40 rs1 = 0x4 rs2 = 0x5 rs1_value = 0 @@ -39,10 +42,12 @@ async def test_sh_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = start_address + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, sh_instruction) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value + await send_unhalt_command(dut) await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) diff --git a/tests/cpu/integration_tests/test_sll_instruction.py b/tests/cpu/integration_tests/test_sll_instruction.py index 21ca954..7cdc394 100644 --- a/tests/cpu/integration_tests/test_sll_instruction.py +++ b/tests/cpu/integration_tests/test_sll_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_r_type_instruction, write_word_to_mem -from cpu.constants import FUNC3_ALU_SLL, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_r_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import FUNC3_ALU_SLL, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -19,7 +19,7 @@ async def test_sll_instruction(dut): (0xFFFFFFFF, 1, 0xFFFFFFFE), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -35,12 +35,14 @@ async def test_sll_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, f"SLL failed: rs1={rs1_value:#010x} rs2={rs2_value:#010x} -> rd={actual:#010x} expected={expected_result:#010x}" diff --git a/tests/cpu/integration_tests/test_slli_instruction.py b/tests/cpu/integration_tests/test_slli_instruction.py index 7c4ebf0..517ff63 100644 --- a/tests/cpu/integration_tests/test_slli_instruction.py +++ b/tests/cpu/integration_tests/test_slli_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SLL, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SLL, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -20,7 +20,7 @@ async def test_slli_instruction(dut): (0xFFFFFFFF, 1, 0XFFFFFFFe), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rd = 3 @@ -35,11 +35,13 @@ async def test_slli_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, f"SLLI failed: rs1={rs1_value:#010x} imm={imm_value} -> rd={actual:#010x} expected={expected_result:#010x}" diff --git a/tests/cpu/integration_tests/test_slt_instruction.py b/tests/cpu/integration_tests/test_slt_instruction.py index 8a01558..14cf55c 100644 --- a/tests/cpu/integration_tests/test_slt_instruction.py +++ b/tests/cpu/integration_tests/test_slt_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_r_type_instruction, write_word_to_mem -from cpu.constants import FUNC3_ALU_SLT, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_r_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import FUNC3_ALU_SLT, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -20,7 +20,7 @@ async def test_slt_instruction(dut): (-1, -2, 0), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -37,12 +37,14 @@ async def test_slt_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, slt_instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.signed_integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_slti_instruction.py b/tests/cpu/integration_tests/test_slti_instruction.py index 1dec566..363a7d2 100644 --- a/tests/cpu/integration_tests/test_slti_instruction.py +++ b/tests/cpu/integration_tests/test_slti_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SLT, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SLT, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -11,7 +11,7 @@ async def test_slti_instruction(dut): """Test slti instruction""" - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rd = 3 @@ -36,11 +36,13 @@ async def test_slti_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, f"SLTI failed: rs1={rs1_value:#010x} imm={imm_value:#06x} -> rd={actual} expected={expected_result}" \ No newline at end of file diff --git a/tests/cpu/integration_tests/test_sltiu_instruction.py b/tests/cpu/integration_tests/test_sltiu_instruction.py index 9dc6ea3..2f917e0 100644 --- a/tests/cpu/integration_tests/test_sltiu_instruction.py +++ b/tests/cpu/integration_tests/test_sltiu_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SLTU, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush, send_unhalt_command +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SLTU, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -11,7 +11,7 @@ async def test_sltiu_instruction(dut): """Test sltiu instruction""" - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rd = 3 @@ -32,12 +32,14 @@ async def test_sltiu_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) instruction = gen_i_type_instruction(OP_I_TYPE_ALU, rd, FUNC3_ALU_SLTU, rs1, imm_value) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_sltu_instruction.py b/tests/cpu/integration_tests/test_sltu_instruction.py index e4b5940..a279f17 100644 --- a/tests/cpu/integration_tests/test_sltu_instruction.py +++ b/tests/cpu/integration_tests/test_sltu_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_r_type_instruction, write_word_to_mem -from cpu.constants import FUNC3_ALU_SLTU, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_r_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import FUNC3_ALU_SLTU, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -20,7 +20,7 @@ async def test_sltu_instruction(dut): (0x80000000, 0x7FFFFFFF, 0), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -36,12 +36,14 @@ async def test_sltu_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, sltu_instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_sra_instruction.py b/tests/cpu/integration_tests/test_sra_instruction.py index b7880f0..20d404d 100644 --- a/tests/cpu/integration_tests/test_sra_instruction.py +++ b/tests/cpu/integration_tests/test_sra_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_r_type_instruction, write_word_to_mem -from cpu.constants import FUNC3_ALU_SRL_SRA, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_r_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import FUNC3_ALU_SRL_SRA, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -17,7 +17,7 @@ async def test_sra_instruction(dut): (-1, 1, -1), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -33,12 +33,14 @@ async def test_sra_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, sra_instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.signed_integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_srai_instruction.py b/tests/cpu/integration_tests/test_srai_instruction.py index d209854..e3af8e0 100644 --- a/tests/cpu/integration_tests/test_srai_instruction.py +++ b/tests/cpu/integration_tests/test_srai_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SRL_SRA, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SRL_SRA, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -20,7 +20,7 @@ async def test_srai_instruction(dut): (0xFFFFFFFF, 1, 0XFFFFFFFF), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rd = 3 @@ -33,12 +33,14 @@ async def test_srai_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) instruction = gen_i_type_instruction(OP_I_TYPE_ALU, rd, FUNC3_ALU_SRL_SRA, rs1, imm_value | 0b0100000 << 5) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_srl_instruction.py b/tests/cpu/integration_tests/test_srl_instruction.py index 295532f..0964cf8 100644 --- a/tests/cpu/integration_tests/test_srl_instruction.py +++ b/tests/cpu/integration_tests/test_srl_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_r_type_instruction, write_word_to_mem -from cpu.constants import FUNC3_ALU_SRL_SRA, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_r_type_instruction, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush, send_unhalt_command +from cpu.constants import FUNC3_ALU_SRL_SRA, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -19,7 +19,7 @@ async def test_srl_instruction(dut): (0x80000000, 31, 0x1), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -35,12 +35,14 @@ async def test_srl_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, srl_instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_srli_instruction.py b/tests/cpu/integration_tests/test_srli_instruction.py index 18ab3a1..e94a6ab 100644 --- a/tests/cpu/integration_tests/test_srli_instruction.py +++ b/tests/cpu/integration_tests/test_srli_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SRL_SRA, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_SRL_SRA, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -20,7 +20,7 @@ async def test_srli_instruction(dut): (0xFFFFFFFF, 1, 0X7FFFFFFF), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rd = 3 @@ -35,11 +35,13 @@ async def test_srli_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) - dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF - - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await send_unhalt_command(dut) + + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, f"SRLI failed: rs1={rs1_value:#010x} imm={imm_value} -> rd={actual:#010x} expected={expected_result:#010x}" diff --git a/tests/cpu/integration_tests/test_sub_instruction.py b/tests/cpu/integration_tests/test_sub_instruction.py index 8ebdbb4..c4faa09 100644 --- a/tests/cpu/integration_tests/test_sub_instruction.py +++ b/tests/cpu/integration_tests/test_sub_instruction.py @@ -4,14 +4,17 @@ from cpu.utils import ( gen_r_type_instruction, + send_unhalt_command, + send_write_pc_command, write_word_to_mem, + wait_for_pipeline_flush, ) from cpu.constants import ( PIPELINE_CYCLES, FUNC3_ALU_ADD_SUB, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) wait_ns = 1 @@ -32,7 +35,7 @@ async def test_sub_instruction(dut): (0x0, 0x1, -0x1), # Wrap around case ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -48,13 +51,14 @@ async def test_sub_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, sub_instruction) - dut.cpu.r_PC.value = start_address - dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.signed_integer assert actual == expected_result, ( diff --git a/tests/cpu/integration_tests/test_sw_instruction.py b/tests/cpu/integration_tests/test_sw_instruction.py index 076e18d..63ca0a0 100644 --- a/tests/cpu/integration_tests/test_sw_instruction.py +++ b/tests/cpu/integration_tests/test_sw_instruction.py @@ -4,14 +4,17 @@ from cpu.utils import ( gen_s_type_instruction, + send_write_pc_command, write_word_to_mem, + wait_for_pipeline_flush, + send_unhalt_command, ) from cpu.constants import ( FUNC3_LS_W, PIPELINE_CYCLES, - ROM_BOUNDARY_ADDR, + RAM_START_ADDR, ) wait_ns = 1 @@ -19,7 +22,7 @@ @cocotb.test() async def test_sw_instruction(dut): """Test sw instruction""" - start_address = ROM_BOUNDARY_ADDR + 0 + start_address = RAM_START_ADDR + 0 rs1 = 0x6 rs2 = 0x7 rs1_value = 0 @@ -32,16 +35,18 @@ async def test_sw_instruction(dut): clock = Clock(dut.i_Clock, wait_ns, "ns") cocotb.start_soon(clock.start()) + write_word_to_mem(dut.instruction_ram.mem, start_address, sw_instruction) + dut.i_Reset.value = 1 await ClockCycles(dut.i_Clock, 1) dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) - dut.cpu.r_PC.value = start_address - write_word_to_mem(dut.instruction_ram.mem, start_address, sw_instruction) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) dut.cpu.reg_file.Registers[rs1].value = rs1_value dut.cpu.reg_file.Registers[rs2].value = rs2_value - + await send_unhalt_command(dut) await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) diff --git a/tests/cpu/integration_tests/test_xor_instruction.py b/tests/cpu/integration_tests/test_xor_instruction.py index 0238adc..8e28048 100644 --- a/tests/cpu/integration_tests/test_xor_instruction.py +++ b/tests/cpu/integration_tests/test_xor_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_r_type_instruction, write_word_to_mem -from cpu.constants import FUNC3_ALU_XOR, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_r_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, wait_for_pipeline_flush +from cpu.constants import FUNC3_ALU_XOR, PIPELINE_CYCLES, RAM_START_ADDR wait_ns = 1 @@ -18,7 +18,7 @@ async def test_xor_instruction(dut): (0, 0, 0), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rs2 = 2 rd = 3 @@ -34,12 +34,15 @@ async def test_xor_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF dut.cpu.reg_file.Registers[rs2].value = rs2_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, f"XOR failed: rs1={rs1_value:#010x} rs2={rs2_value:#010x} -> rd={actual:#010x} expected={expected_result:#010x}" diff --git a/tests/cpu/integration_tests/test_xori_instruction.py b/tests/cpu/integration_tests/test_xori_instruction.py index 124bd16..0f14a36 100644 --- a/tests/cpu/integration_tests/test_xori_instruction.py +++ b/tests/cpu/integration_tests/test_xori_instruction.py @@ -2,8 +2,8 @@ from cocotb.triggers import ClockCycles from cocotb.clock import Clock -from cpu.utils import gen_i_type_instruction, write_word_to_mem -from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_XOR, PIPELINE_CYCLES, ROM_BOUNDARY_ADDR +from cpu.utils import gen_i_type_instruction, send_unhalt_command, send_write_pc_command, write_word_to_mem, uart_send_byte, wait_for_pipeline_flush +from cpu.constants import OP_I_TYPE_ALU, FUNC3_ALU_XOR, PIPELINE_CYCLES, RAM_START_ADDR, DEBUG_OP_HALT, DEBUG_OP_UNHALT wait_ns = 1 @@ -20,7 +20,7 @@ async def test_xori_instruction(dut): (0xF0F0F0F0, 0x00F, 0xF0F0F0FF), ] - start_address = ROM_BOUNDARY_ADDR + 0x0 + start_address = RAM_START_ADDR + 0x0 rs1 = 1 rd = 3 @@ -33,12 +33,15 @@ async def test_xori_instruction(dut): dut.i_Reset.value = 0 await ClockCycles(dut.i_Clock, 1) + await send_write_pc_command(dut, start_address) + await wait_for_pipeline_flush(dut) instruction = gen_i_type_instruction(OP_I_TYPE_ALU, rd, FUNC3_ALU_XOR, rs1, imm_value) write_word_to_mem(dut.instruction_ram.mem, start_address, instruction) dut.cpu.r_PC.value = start_address dut.cpu.reg_file.Registers[rs1].value = rs1_value & 0xFFFFFFFF + await send_unhalt_command(dut) - await ClockCycles(dut.i_Clock, PIPELINE_CYCLES) + await ClockCycles(dut.i_Clock, PIPELINE_CYCLES + 3) actual = dut.cpu.reg_file.Registers[rd].value.integer assert actual == expected_result, ( diff --git a/tests/cpu/unit_tests/test_instruction_memory_axi.py b/tests/cpu/unit_tests/test_instruction_memory_axi.py index afea0bd..b9d40a0 100644 --- a/tests/cpu/unit_tests/test_instruction_memory_axi.py +++ b/tests/cpu/unit_tests/test_instruction_memory_axi.py @@ -2,7 +2,7 @@ from cocotb.triggers import Timer from cpu.utils import write_instructions, write_instructions_rom -from cpu.constants import ROM_BOUNDARY_ADDR +from cpu.constants import RAM_START_ADDR, CPU_BASE_ADDR wait_ns = 1 @@ -12,10 +12,10 @@ async def test_read_instruction(dut): instructions = [0x12345678, 0x9ABCDEF0, 0x0FEDCBA9, 0x87654321] - write_instructions(dut.instruction_ram.mem, ROM_BOUNDARY_ADDR, instructions) + write_instructions(dut.instruction_ram.mem, RAM_START_ADDR, instructions) for instruction_index in range(4): - dut.instruction_memory_axi.i_Instruction_Addr.value = ROM_BOUNDARY_ADDR + instruction_index*4 + dut.instruction_memory_axi.i_Instruction_Addr.value = RAM_START_ADDR + instruction_index*4 for clock_cnt in range(10): dut.i_Clock.value = 1 @@ -43,10 +43,10 @@ async def test_read_instruction_rom(dut): instructions = [0x12345678, 0x9ABCDEF0, 0x0FEDCBA9, 0x87654321] - write_instructions_rom(dut.instruction_memory_axi.rom, 0x0, instructions) + write_instructions_rom(dut.instruction_memory_axi.rom, CPU_BASE_ADDR, instructions) for instruction_index in range(4): - dut.instruction_memory_axi.i_Instruction_Addr.value = instruction_index*4 + dut.instruction_memory_axi.i_Instruction_Addr.value = CPU_BASE_ADDR + instruction_index*4 for clock_cnt in range(10): dut.i_Clock.value = 1 diff --git a/tests/cpu/utils.py b/tests/cpu/utils.py index 7df819c..61b9dc9 100644 --- a/tests/cpu/utils.py +++ b/tests/cpu/utils.py @@ -4,9 +4,12 @@ OP_S_TYPE, OP_R_TYPE, + CPU_BASE_ADDR, ROM_BOUNDARY_ADDR, - + UART_CLOCKS_PER_BIT, + DEBUG_OP_WRITE_PC, + DEBUG_OP_UNHALT, ) from cocotb.triggers import ClockCycles, FallingEdge @@ -62,31 +65,42 @@ def gen_r_type_instruction(rd, funct3, rs1, rs2, funct7): return instruction def write_word_to_mem(mem_array, addr, value): - """Write a 32-bit value into byte-addressable cocotb memory (little-endian).""" + """Write a 32-bit value into byte-addressable cocotb memory (little-endian). + + Address is masked to 16 bits to match test harness AXI truncation. + """ + addr = (addr - 0x80000000) & 0xFFFF mem_array[addr + 0].value = (value >> 0) & 0xFF mem_array[addr + 1].value = (value >> 8) & 0xFF mem_array[addr + 2].value = (value >> 16) & 0xFF mem_array[addr + 3].value = (value >> 24) & 0xFF def write_half_to_mem(mem_array, addr, value): + """Write a 16-bit value into byte-addressable cocotb memory (little-endian).""" + addr = (addr - 0x80000000) & 0xFFFF mem_array[addr + 0].value = (value >> 0) & 0xFF mem_array[addr + 1].value = (value >> 8) & 0xFF def write_byte_to_mem(mem_array, addr, value): + """Write an 8-bit value into byte-addressable cocotb memory.""" + addr = (addr - 0x80000000) & 0xFFFF mem_array[addr].value = value & 0xFF def write_instructions(mem_array, base_addr, instructions): """Write a list of 32-bit instructions at word stride (4 bytes).""" - if base_addr < ROM_BOUNDARY_ADDR: - raise ValueError(f"Base address {base_addr:#06x} is inside of ROM boundary.") + if base_addr <= ROM_BOUNDARY_ADDR: + raise ValueError(f"Base address {base_addr:#010x} is inside ROM boundary (must be > {ROM_BOUNDARY_ADDR:#010x}).") for i, ins in enumerate(instructions): write_word_to_mem(mem_array, base_addr + 4*i, ins) def write_instructions_rom(mem_array, base_addr, instructions): + """Write instructions to ROM (word-addressable, not byte-addressable).""" if base_addr > ROM_BOUNDARY_ADDR: - raise ValueError(f"Base address {base_addr:#06x} is outside of ROM boundary.") + raise ValueError(f"Base address {base_addr:#010x} is outside ROM boundary (must be <= {ROM_BOUNDARY_ADDR:#010x}).") + # ROM uses word addressing, offset from CPU_BASE_ADDR + rom_offset = (base_addr - CPU_BASE_ADDR) // 4 for i, ins in enumerate(instructions): - mem_array[base_addr//4 + i].value = ins + mem_array[rom_offset + i].value = ins async def uart_send_byte(clock, i_rx_serial, o_rx_dv, data_byte): @@ -165,3 +179,20 @@ async def wait_for_pipeline_flush(dut, timeout_cycles=1000): await ClockCycles(dut.i_Clock, 1) raise AssertionError(f"Pipeline did not flush after {timeout_cycles} cycles") + +async def send_write_pc_command(dut, pc_value): + """Send WRITE_PC command via debug peripheral: opcode + 4 PC bytes (little-endian). + + Note: The WRITE_PC command automatically halts the CPU. + """ + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_WRITE_PC) + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, (pc_value >> 0) & 0xFF) + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, (pc_value >> 8) & 0xFF) + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, (pc_value >> 16) & 0xFF) + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, (pc_value >> 24) & 0xFF) + + +async def send_unhalt_command(dut): + """Send UNHALT command via debug peripheral to resume CPU execution.""" + await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT) + diff --git a/tests/vga/__init__.py b/tests/vga/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/vga/constants.py b/tests/vga/constants.py new file mode 100644 index 0000000..bed173c --- /dev/null +++ b/tests/vga/constants.py @@ -0,0 +1,52 @@ +# VGA 640x480 @ 60Hz timing constants +# These match the values in hdl/vga_out/vga_out.v + +# Horizontal timing (in pixels) +VISIBLE_H = 640 +FRONT_PORCH_H = 16 +SYNC_PULSE_H = 96 +BACK_PORCH_H = 48 +TOTAL_H = 800 # VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H + BACK_PORCH_H + +# Vertical timing (in lines) +VISIBLE_V = 480 +FRONT_PORCH_V = 10 +SYNC_PULSE_V = 2 +BACK_PORCH_V = 33 +TOTAL_V = 525 # VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V + BACK_PORCH_V + +# Clock divider - module advances pixel counter every 4 clocks +CLOCKS_PER_PIXEL = 4 + +# Derived timing constants (in clock cycles) +CLOCKS_PER_LINE = TOTAL_H * CLOCKS_PER_PIXEL # 3200 clocks +CLOCKS_PER_FRAME = TOTAL_V * CLOCKS_PER_LINE # 1,680,000 clocks + +# Horizontal region boundaries (pixel positions) +H_VISIBLE_END = VISIBLE_H # 640 +H_FRONT_PORCH_END = H_VISIBLE_END + FRONT_PORCH_H # 656 +H_SYNC_END = H_FRONT_PORCH_END + SYNC_PULSE_H # 752 +# H_BACK_PORCH_END = TOTAL_H = 800 + +# Vertical region boundaries (line positions) +V_VISIBLE_END = VISIBLE_V # 480 +V_FRONT_PORCH_END = V_VISIBLE_END + FRONT_PORCH_V # 490 +V_SYNC_END = V_FRONT_PORCH_END + SYNC_PULSE_V # 492 +# V_BACK_PORCH_END = TOTAL_V = 525 + + +def make_pixel(r, g, b): + """Create a 16-bit pixel value from 4-bit RGB components. + + Format: [15:12]=R, [10:7]=G, [4:1]=B (bits 11, 6, 5, 0 unused) + """ + return ((r & 0xF) << 12) | ((g & 0xF) << 7) | ((b & 0xF) << 1) + + +# Test color constants +PIXEL_RED = make_pixel(0xF, 0x0, 0x0) +PIXEL_GREEN = make_pixel(0x0, 0xF, 0x0) +PIXEL_BLUE = make_pixel(0x0, 0x0, 0xF) +PIXEL_WHITE = make_pixel(0xF, 0xF, 0xF) +PIXEL_BLACK = make_pixel(0x0, 0x0, 0x0) +PIXEL_TEST = make_pixel(0xA, 0x5, 0x3) # Arbitrary test pattern diff --git a/tests/vga/unit_tests/__init__.py b/tests/vga/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/vga/unit_tests/vga_unit_tests_harness.v b/tests/vga/unit_tests/vga_unit_tests_harness.v new file mode 100644 index 0000000..088f05e --- /dev/null +++ b/tests/vga/unit_tests/vga_unit_tests_harness.v @@ -0,0 +1,47 @@ +`timescale 1ns / 1ps + +module vga_unit_tests_harness (); + + wire i_Clock; + + reg [15:0] s_axis_tdata; + reg s_axis_tvalid; + wire s_axis_tready; + + /* Mock VDMA input interface */ + initial begin + s_axis_tdata = 16'h0000; + s_axis_tvalid = 1'b0; + #100; + s_axis_tvalid = 1'b1; + repeat (640 * 480) begin + @(posedge i_Clock); + s_axis_tdata = s_axis_tdata + 16'h0001; + end + s_axis_tvalid = 1'b0; + end + + + wire o_mm2s_fsync; + wire [3:0] o_Red; + wire [3:0] o_Green; + wire [3:0] o_Blue; + wire o_Horizontal_Sync; + wire o_Vertical_Sync; + + vga_out #( + .BITS_PER_COLOR_CHANNEL(4) + ) vga_out ( + .i_Clock(i_Clock), + .s_axis_tdata(s_axis_tdata), + .s_axis_tvalid(s_axis_tvalid), + .s_axis_tready(s_axis_tready), + .o_mm2s_fsync(o_mm2s_fsync), + .o_Red(o_Red), + .o_Green(o_Green), + .o_Blue(o_Blue), + .o_Horizontal_Sync(o_Horizontal_Sync), + .o_Vertical_Sync(o_Vertical_Sync) + ); + +endmodule diff --git a/tests/vga/utils.py b/tests/vga/utils.py new file mode 100644 index 0000000..fcd5b36 --- /dev/null +++ b/tests/vga/utils.py @@ -0,0 +1,93 @@ +from cocotb.triggers import ClockCycles, RisingEdge + +from vga.constants import CLOCKS_PER_PIXEL, CLOCKS_PER_LINE, TOTAL_H, TOTAL_V + + +async def wait_pixels(dut, num_pixels): + """Wait for the specified number of pixel periods.""" + await ClockCycles(dut.i_Clock, num_pixels * CLOCKS_PER_PIXEL) + + +async def wait_lines(dut, num_lines): + """Wait for the specified number of complete lines.""" + await ClockCycles(dut.i_Clock, num_lines * CLOCKS_PER_LINE) + + +async def wait_for_frame_start(dut): + """Wait until we're at the start of a frame (position near 0,0). + + For tests that need to start from a known position. + """ + # Wait a couple cycles to let signals settle at simulation start + await ClockCycles(dut.i_Clock, 2) + + # Check if we're already at or very near frame start (first 10 pixels of line 0) + h, v = get_current_position(dut) + if v == 0 and h < 10: + # Already at frame start, just return + return True + + # Need to wait for next frame - calculate how many pixels + pixels_remaining_in_line = TOTAL_H - h + lines_remaining = TOTAL_V - v - 1 # -1 because current line is partial + + # Total pixels to next frame start + pixels_to_frame_start = pixels_remaining_in_line + (lines_remaining * TOTAL_H) + + # Jump ahead to frame start + if pixels_to_frame_start > 5: + await wait_pixels(dut, pixels_to_frame_start - 2) + + # Poll for exact frame start (when counters roll over to 0,0) + for _ in range(50): + await RisingEdge(dut.i_Clock) + h, v = get_current_position(dut) + if v == 0 and h < 5: + return True + + raise TimeoutError("wait_for_frame_start: failed to find frame start") + + +async def wait_for_hsync_pulse(dut): + """Wait for horizontal sync to go low (active).""" + # If already in hsync, wait for it to end first + while dut.o_Horizontal_Sync.value == 0: + await RisingEdge(dut.i_Clock) + + # Now wait for next hsync + for _ in range(TOTAL_H * CLOCKS_PER_PIXEL + 100): + await RisingEdge(dut.i_Clock) + if dut.o_Horizontal_Sync.value == 0: + return True + + raise TimeoutError("wait_for_hsync_pulse timed out") + + +async def wait_for_vsync_pulse(dut): + """Wait for vertical sync to go low (active).""" + # If already in vsync, wait for it to end first + while dut.o_Vertical_Sync.value == 0: + await RisingEdge(dut.i_Clock) + + # Now wait for next vsync - could be up to a full frame + for _ in range(TOTAL_H * TOTAL_V * CLOCKS_PER_PIXEL + 100): + await RisingEdge(dut.i_Clock) + if dut.o_Vertical_Sync.value == 0: + return True + + raise TimeoutError("wait_for_vsync_pulse timed out") + + +def get_current_position(dut): + """Get current H and V counter positions from internal registers.""" + h_counter = dut.vga_out.r_H_Counter.value.integer + v_counter = dut.vga_out.r_V_Counter.value.integer + return h_counter, v_counter + + +def extract_rgb(dut): + """Extract R, G, B values from VGA outputs.""" + r = dut.o_Red.value.integer + g = dut.o_Green.value.integer + b = dut.o_Blue.value.integer + return r, g, b diff --git a/tools/debugger/commands.go b/tools/debugger/commands.go index 4410712..710a14a 100644 --- a/tools/debugger/commands.go +++ b/tools/debugger/commands.go @@ -32,12 +32,12 @@ var commands = map[Command]CommandInfo{ CmdUnhalt: {"Unhalt CPU", "Resume CPU execution", true}, CmdReset: {"Reset CPU", "Reset the CPU", true}, CmdUnreset: {"Unreset CPU", "Take CPU out of reset", true}, - CmdReadRegister: {"Read Register", "Read a specific register value", false}, + CmdReadRegister: {"Read Register", "Read a specific register value (CPU stays halted)", true}, CmdFullDump: {"Full Dump", "Read all registers and memory", false}, CmdPing: {"Ping CPU", "Check if CPU is responsive", true}, CmdReadPC: {"Read PC", "Read program counter value", true}, - CmdSetRegister: {"Set Register", "Write value to a register", false}, - CmdJumpToAddress: {"Jump to Address", "Set PC to specific address", false}, + CmdSetRegister: {"Set Register", "Write value to a register (CPU stays halted)", true}, + CmdJumpToAddress: {"Jump to Address", "Set PC to specific address (CPU stays halted)", true}, CmdLoadProgram: {"Load Program", "Load program from file", false}, CmdStatsDump: {"Read Stats", "Read CPU statistics", false}, CmdReadMemory: {"Read Memory", "Read memory at address", false}, @@ -59,11 +59,41 @@ func (c Command) GetOpCode() (OpCode, bool) { return op_PING, true case CmdReadPC: return op_READ_PC, true + case CmdReadRegister: + return op_READ_REGISTER, true + case CmdSetRegister: + return op_WRITE_REGISTER, true + case CmdJumpToAddress: + return op_WRITE_PC, true default: return 0, false } } +// NeedsInput returns true if the command requires user input +func (c Command) NeedsInput() bool { + switch c { + case CmdReadRegister, CmdSetRegister, CmdJumpToAddress: + return true + default: + return false + } +} + +// GetInputPrompt returns the prompt for user input +func (c Command) GetInputPrompt() string { + switch c { + case CmdReadRegister: + return "Register number (0-31): " + case CmdSetRegister: + return "Register number (0-31): " + case CmdJumpToAddress: + return "Address (hex, e.g. 1000): " + default: + return "" + } +} + // GetName returns the display name for a command func (c Command) GetName() string { if info, ok := commands[c]; ok { diff --git a/tools/debugger/opcodes.go b/tools/debugger/opcodes.go index b7f3398..7a9e8f9 100644 --- a/tools/debugger/opcodes.go +++ b/tools/debugger/opcodes.go @@ -4,13 +4,16 @@ package main type OpCode byte const ( - op_NOP OpCode = 0x0 - op_RESET OpCode = 0x1 - op_UNRESET OpCode = 0x2 - op_HALT OpCode = 0x3 - op_UNHALT OpCode = 0x4 - op_PING OpCode = 0x5 - op_READ_PC OpCode = 0x6 + op_NOP OpCode = 0x0 + op_RESET OpCode = 0x1 + op_UNRESET OpCode = 0x2 + op_HALT OpCode = 0x3 + op_UNHALT OpCode = 0x4 + op_PING OpCode = 0x5 + op_READ_PC OpCode = 0x6 + op_WRITE_PC OpCode = 0x7 + op_READ_REGISTER OpCode = 0x8 + op_WRITE_REGISTER OpCode = 0x9 ) // String returns the human-readable name of the opcode @@ -30,6 +33,12 @@ func (o OpCode) String() string { return "PING" case op_READ_PC: return "READ_PC" + case op_WRITE_PC: + return "WRITE_PC" + case op_READ_REGISTER: + return "READ_REGISTER" + case op_WRITE_REGISTER: + return "WRITE_REGISTER" default: return "UNKNOWN" } diff --git a/tools/debugger/serial.go b/tools/debugger/serial.go index 06c69e9..d072d41 100644 --- a/tools/debugger/serial.go +++ b/tools/debugger/serial.go @@ -226,6 +226,48 @@ func (sm *SerialManager) SendCommand(opcode OpCode) error { return err } +// SendCommandWithData sends a command with additional data bytes +func (sm *SerialManager) SendCommandWithData(opcode OpCode, data []byte) error { + dataStr := formatHex(data) + sm.logFile.Printf("TX: %s (0x%02X) + data: %s\n", opcode.String(), opcode, dataStr) + + if sm.isMock { + // Simulate mock response based on command type + time.AfterFunc(50*time.Millisecond, func() { + var mockData []byte + switch opcode { + case op_READ_REGISTER: + // Return mock register value (4 bytes, little-endian) + mockData = []byte{0x12, 0x34, 0x56, 0x78} + case op_WRITE_REGISTER, op_WRITE_PC: + // No response expected for write commands + mockData = []byte{0xAA} // Just an ACK + default: + mockData = []byte{byte(opcode), 0xAA} + } + sm.handleResponse(mockData) + }) + return nil + } + + if sm.port == nil { + return fmt.Errorf("serial port not connected") + } + + // Send opcode first + _, err := sm.port.Write([]byte{byte(opcode)}) + if err != nil { + return err + } + + // Small delay between opcode and data + time.Sleep(10 * time.Millisecond) + + // Send data bytes + _, err = sm.port.Write(data) + return err +} + // Close closes the serial port func (sm *SerialManager) Close() error { sm.StopListening() @@ -256,18 +298,18 @@ func parseResponse(data []byte) string { return "Empty response" } - // Check for READ_PC response (4 bytes, little-endian PC value) + // Check for PING response (single 0xAA byte) + if len(data) == 1 && data[0] == 0xAA { + return "PING response (0xAA)" + } + + // Check for 4-byte value response (could be PC or register value) if len(data) == 4 { - pc := uint32(data[0]) | + value := uint32(data[0]) | (uint32(data[1]) << 8) | (uint32(data[2]) << 16) | (uint32(data[3]) << 24) - return fmt.Sprintf("PC = 0x%08X", pc) - } - - // Check for PING response (single 0xAA byte) - if len(data) == 1 && data[0] == 0xAA { - return "PING response (0xAA)" + return fmt.Sprintf("0x%08X (%d)", value, value) } // Try to identify opcode echo diff --git a/tools/debugger/ui.go b/tools/debugger/ui.go index 71019a0..22f6f94 100644 --- a/tools/debugger/ui.go +++ b/tools/debugger/ui.go @@ -15,13 +15,14 @@ type AppState int const ( StateSelectPort AppState = iota StateMainMenu + StateInput // Text input mode for commands that need parameters + StateInputValue // Second input for set register (value) ) // CPUState tracks the current state of the CPU type CPUState struct { haltSet bool resetSet bool - lastPing time.Time connected bool pc uint32 pcValid bool @@ -45,6 +46,11 @@ type model struct { scrollOffset int // Offset for scrolling through responses autoScroll bool // Auto-scroll to latest response cpuState CPUState + // Input mode fields + inputBuffer string // Current input text + inputPrompt string // Prompt to show user + pendingCommand Command // Command waiting for input + inputRegNum uint8 // Register number (for set register, stored after first input) } type portsListMsg struct { @@ -304,9 +310,57 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } else if m.state == StateMainMenu { cmd := m.commands[m.cursor] if commands[cmd].implemented { - m.executing = true - m.status = fmt.Sprintf("Executing: %s...", commands[cmd].name) - return m, m.executeCommand(cmd) + // Check if command needs input + if cmd.NeedsInput() { + m.state = StateInput + m.pendingCommand = cmd + m.inputBuffer = "" + m.inputPrompt = cmd.GetInputPrompt() + m.status = "Enter value and press Enter" + } else { + m.executing = true + m.status = fmt.Sprintf("Executing: %s...", commands[cmd].name) + return m, m.executeCommand(cmd) + } + } + } else if m.state == StateInput { + // Process input for the pending command + return m, m.processInput() + } else if m.state == StateInputValue { + // Process second input (value for set register) + return m, m.processValueInput() + } + + case "esc": + // Cancel input mode + if m.state == StateInput || m.state == StateInputValue { + m.state = StateMainMenu + m.inputBuffer = "" + m.status = "Input cancelled" + } + + case "backspace": + if (m.state == StateInput || m.state == StateInputValue) && len(m.inputBuffer) > 0 { + m.inputBuffer = m.inputBuffer[:len(m.inputBuffer)-1] + } + + default: + // Handle text input + if m.state == StateInput || m.state == StateInputValue { + // Only allow valid characters (hex digits for address, digits for register) + char := msg.String() + if len(char) == 1 { + if m.pendingCommand == CmdJumpToAddress || m.state == StateInputValue { + // Allow hex digits for address/value + if (char >= "0" && char <= "9") || (char >= "a" && char <= "f") || (char >= "A" && char <= "F") { + m.inputBuffer += char + } + } else { + // Allow only digits for register number + if char >= "0" && char <= "9" { + m.inputBuffer += char + } + } } } } @@ -341,11 +395,15 @@ func (m *model) updateCPUState(cmd Command, pcValue uint32) { m.cpuState.resetSet = true case CmdUnreset: m.cpuState.resetSet = false - case CmdPing: - m.cpuState.lastPing = time.Now() case CmdReadPC: m.cpuState.pc = pcValue m.cpuState.pcValid = true + case CmdReadRegister, CmdSetRegister: + m.cpuState.haltSet = true // CPU stays halted after register access + case CmdJumpToAddress: + m.cpuState.pc = pcValue + m.cpuState.pcValid = true + m.cpuState.haltSet = true // CPU stays halted after jump } } @@ -409,6 +467,178 @@ func (m model) executeCommand(cmd Command) tea.Cmd { } } +func (m *model) processInput() tea.Cmd { + input := m.inputBuffer + + switch m.pendingCommand { + case CmdReadRegister: + // Parse register number + var regNum int + _, err := fmt.Sscanf(input, "%d", ®Num) + if err != nil || regNum < 0 || regNum > 31 { + m.state = StateMainMenu + m.status = "Invalid register number (0-31)" + return nil + } + + m.state = StateMainMenu + m.executing = true + m.status = fmt.Sprintf("Reading register x%d...", regNum) + + return m.executeReadRegister(uint8(regNum)) + + case CmdSetRegister: + // Parse register number, then prompt for value + var regNum int + _, err := fmt.Sscanf(input, "%d", ®Num) + if err != nil || regNum < 0 || regNum > 31 { + m.state = StateMainMenu + m.status = "Invalid register number (0-31)" + return nil + } + + m.inputRegNum = uint8(regNum) + m.state = StateInputValue + m.inputBuffer = "" + m.inputPrompt = fmt.Sprintf("Value for x%d (hex): ", regNum) + return nil + + case CmdJumpToAddress: + // Parse address (hex) + var addr uint32 + _, err := fmt.Sscanf(input, "%x", &addr) + if err != nil { + m.state = StateMainMenu + m.status = "Invalid address (use hex, e.g. 1000)" + return nil + } + + m.state = StateMainMenu + m.executing = true + m.status = fmt.Sprintf("Jumping to 0x%08X...", addr) + + return m.executeJumpToAddress(addr) + } + + m.state = StateMainMenu + return nil +} + +func (m *model) processValueInput() tea.Cmd { + input := m.inputBuffer + + // Parse value (hex) + var value uint32 + _, err := fmt.Sscanf(input, "%x", &value) + if err != nil { + m.state = StateMainMenu + m.status = "Invalid value (use hex)" + return nil + } + + m.state = StateMainMenu + m.executing = true + m.status = fmt.Sprintf("Setting x%d = 0x%08X...", m.inputRegNum, value) + + return m.executeSetRegister(m.inputRegNum, value) +} + +func (m model) executeReadRegister(regNum uint8) tea.Cmd { + return func() tea.Msg { + // Send READ_REGISTER opcode with register number + data := []byte{regNum} + err := m.serialMgr.SendCommandWithData(op_READ_REGISTER, data) + if err != nil { + return commandCompleteMsg{ + success: false, + message: fmt.Sprintf("Failed to send command: %v", err), + } + } + + // Wait for 4-byte response + time.Sleep(300 * time.Millisecond) + + responses := m.serialMgr.GetResponses() + if len(responses) > 0 { + lastResp := responses[len(responses)-1] + if len(lastResp.Data) >= 4 { + value := uint32(lastResp.Data[0]) | + (uint32(lastResp.Data[1]) << 8) | + (uint32(lastResp.Data[2]) << 16) | + (uint32(lastResp.Data[3]) << 24) + + return commandCompleteMsg{ + success: true, + message: fmt.Sprintf("✓ x%d = 0x%08X", regNum, value), + cmd: CmdReadRegister, + } + } + } + + return commandCompleteMsg{ + success: false, + message: "Failed to read register value", + cmd: CmdReadRegister, + } + } +} + +func (m model) executeSetRegister(regNum uint8, value uint32) tea.Cmd { + return func() tea.Msg { + // Send WRITE_REGISTER opcode with register number and value (little-endian) + data := []byte{ + regNum, + byte(value), + byte(value >> 8), + byte(value >> 16), + byte(value >> 24), + } + err := m.serialMgr.SendCommandWithData(op_WRITE_REGISTER, data) + if err != nil { + return commandCompleteMsg{ + success: false, + message: fmt.Sprintf("Failed to send command: %v", err), + } + } + + time.Sleep(150 * time.Millisecond) + + return commandCompleteMsg{ + success: true, + message: fmt.Sprintf("✓ x%d = 0x%08X", regNum, value), + cmd: CmdSetRegister, + } + } +} + +func (m model) executeJumpToAddress(addr uint32) tea.Cmd { + return func() tea.Msg { + // Send WRITE_PC opcode with address (little-endian) + data := []byte{ + byte(addr), + byte(addr >> 8), + byte(addr >> 16), + byte(addr >> 24), + } + err := m.serialMgr.SendCommandWithData(op_WRITE_PC, data) + if err != nil { + return commandCompleteMsg{ + success: false, + message: fmt.Sprintf("Failed to send command: %v", err), + } + } + + time.Sleep(150 * time.Millisecond) + + return commandCompleteMsg{ + success: true, + message: fmt.Sprintf("✓ PC = 0x%08X", addr), + cmd: CmdJumpToAddress, + pcValue: addr, + } + } +} + func (m model) View() string { s := titleStyle.Render("🔧 CPU Debugger") s += "\n\n" @@ -500,6 +730,30 @@ func (m model) renderCommandPanel(width int, height int) string { s.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("86")).Render("Commands")) s.WriteString("\n\n") + // Check if in input mode + if m.state == StateInput || m.state == StateInputValue { + // Show input prompt + cmdInfo := commands[m.pendingCommand] + s.WriteString(lipgloss.NewStyle(). + Foreground(lipgloss.Color("170")). + Bold(true). + Render(cmdInfo.name) + "\n\n") + + s.WriteString(lipgloss.NewStyle(). + Foreground(lipgloss.Color("255")). + Render(m.inputPrompt)) + + // Show input with cursor + inputStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color("cyan")). + Bold(true) + s.WriteString(inputStyle.Render(m.inputBuffer + "█") + "\n\n") + + s.WriteString(helpStyle.Render("⏎ confirm • esc cancel")) + + return panelStyle.Render(s.String()) + } + // Commands list for i, cmd := range m.commands { info := commands[cmd] @@ -608,30 +862,11 @@ func (m model) renderCPUState(width int, height int) string { } else { s.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render("📍 PC: unknown")) } - s.WriteString("\n\n") - - // Last ping - if !m.cpuState.lastPing.IsZero() { - elapsed := time.Since(m.cpuState.lastPing) - pingStr := fmt.Sprintf("🏓 Last ping: %s ago", formatDuration(elapsed)) - s.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("245")).Render(pingStr)) - } else { - s.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render("🏓 No ping yet")) - } + s.WriteString("\n") return panelStyle.Render(s.String()) } -func formatDuration(d time.Duration) string { - if d < time.Second { - return fmt.Sprintf("%dms", d.Milliseconds()) - } - if d < time.Minute { - return fmt.Sprintf("%.1fs", d.Seconds()) - } - return fmt.Sprintf("%.1fm", d.Minutes()) -} - func (m model) renderSerialMonitor(width int, height int) string { var content strings.Builder From 7acf72f5ba897dc28de38fa2891903a36ba69139 Mon Sep 17 00:00:00 2001 From: Ema Dervisevic Date: Mon, 26 Jan 2026 16:57:52 +0100 Subject: [PATCH 2/8] fml --- hdl/vga_out/vga_out.v | 93 ++- tests/Makefile | 4 +- tests/test_output/vdma_input_data.txt | 641 ++++++++++++++++++ tests/test_output/vga_output_data.txt | 6 + tests/vga/constants.py | 45 +- .../vga/unit_tests/test_vga_comprehensive.py | 185 +++++ tests/vga/unit_tests/test_vga_pattern.py | 272 ++++++++ tests/vga/unit_tests/vga_unit_tests_harness.v | 12 +- tests/vga/utils.py | 93 --- 9 files changed, 1188 insertions(+), 163 deletions(-) create mode 100644 tests/test_output/vdma_input_data.txt create mode 100644 tests/test_output/vga_output_data.txt create mode 100644 tests/vga/unit_tests/test_vga_comprehensive.py create mode 100644 tests/vga/unit_tests/test_vga_pattern.py diff --git a/hdl/vga_out/vga_out.v b/hdl/vga_out/vga_out.v index 3c32a0d..d63145a 100644 --- a/hdl/vga_out/vga_out.v +++ b/hdl/vga_out/vga_out.v @@ -3,6 +3,7 @@ module vga_out #( parameter BITS_PER_COLOR_CHANNEL = 4 ) ( + input i_Reset, input i_Clock, // AXI-Stream Interface @@ -19,8 +20,6 @@ module vga_out #( output o_Vertical_Sync ); - reg [1:0] r_Clock_Counter = 0; - // Horizontal timing for 640x480 @ 60Hz (approx 25MHz pixel clock) localparam VISIBLE_H = 640; localparam FRONT_PORCH_H = 16; @@ -35,10 +34,21 @@ module vga_out #( localparam BACK_PORCH_V = 33; localparam TOTAL_V = VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V + BACK_PORCH_V; // Should be 525 + // Clock divider for VGA timing (100MHz -> 25MHz) + reg [1:0] r_Clock_Counter = 0; + // Vertical and horizontal counter registers reg [15:0] r_H_Counter = 0; reg [15:0] r_V_Counter = 0; + // Dual row buffers for pixel data + reg [15:0] r_Row_Buffer_0 [0:VISIBLE_H-1]; + reg [15:0] r_Row_Buffer_1 [0:VISIBLE_H-1]; + + // Buffer control signals + reg r_Active_Buffer = 0; // 0 = reading from buffer_0, 1 = reading from buffer_1 + reg [9:0] r_Fill_Addr = 0; // Address for filling buffer (0 to VISIBLE_H-1) + reg r_Blanking_Prefetch_Done = 0; localparam [1:0] STATE_VISIBLE = 0; localparam [1:0] STATE_FRONT_PORCH = 1; @@ -63,29 +73,74 @@ module vga_out #( else r_V_State = STATE_BACK_PORCH; end - always @(posedge i_Clock) begin - r_Clock_Counter <= r_Clock_Counter + 1; - if(r_Clock_Counter == 2'b00) // Every 4 clock cycles - begin - if(r_H_Counter == TOTAL_H - 1) // If we've reached the end of the horizontal line - begin - r_H_Counter <= 0; - if (r_V_Counter == TOTAL_V - 1) // If we've reached the end of the vertical frame - r_V_Counter <= 0; - else r_V_Counter <= r_V_Counter + 1; - end else begin - r_H_Counter <= r_H_Counter + 1; + always @(posedge i_Clock or posedge i_Reset) begin + if (i_Reset) begin + r_Clock_Counter <= 0; + r_H_Counter <= 0; + r_V_Counter <= VISIBLE_V; // Start vertical counter in blanking period + end else begin + r_Clock_Counter <= r_Clock_Counter + 1; + + if (r_Clock_Counter == 3) begin // VGA pixel clock tick every 4th cycle + if(r_H_Counter == TOTAL_H - 1) begin // If we've reached the end of the horizontal line + r_H_Counter <= 0; + if (r_V_Counter == TOTAL_V - 1) // If we've reached the end of the vertical frame + r_V_Counter <= 0; + else begin + r_V_Counter <= r_V_Counter + 1; + end + end else begin + r_H_Counter <= r_H_Counter + 1; + end end end end + assign s_axis_tready = !i_Reset && r_V_State == STATE_VISIBLE ? r_Fill_Addr < VISIBLE_H - 1 : !r_Blanking_Prefetch_Done; + + wire w_Frame_Sync_Position = (r_H_Counter == 0) && (r_V_Counter == VISIBLE_V); + assign o_mm2s_fsync = w_Frame_Sync_Position; + + // Buffer filling and switching logic + always @(posedge i_Clock or posedge i_Reset) begin + if (i_Reset) begin + r_Fill_Addr <= 0; + r_Active_Buffer <= 0; + r_Blanking_Prefetch_Done <= 0; + end else begin + if (s_axis_tready && s_axis_tvalid) begin + if (r_Active_Buffer) begin + r_Row_Buffer_0[r_Fill_Addr] <= s_axis_tdata; + end else begin + r_Row_Buffer_1[r_Fill_Addr] <= s_axis_tdata; + end + + if (r_Fill_Addr < VISIBLE_H - 1) begin + r_Fill_Addr <= r_Fill_Addr + 1; + end else if (r_Fill_Addr == VISIBLE_H - 1) begin + r_Fill_Addr <= VISIBLE_H; + end + end + + // Switch buffers and start loading new row soon as we've drawn the visible area of the line + if (r_H_Counter == VISIBLE_H && r_V_State == STATE_VISIBLE) begin + r_Active_Buffer <= ~r_Active_Buffer; + r_Fill_Addr <= 0; + end else if(r_Fill_Addr == VISIBLE_H - 1 && r_V_State != STATE_VISIBLE && !r_Blanking_Prefetch_Done) begin + r_Active_Buffer <= ~r_Active_Buffer; + r_Fill_Addr <= 0; + r_Blanking_Prefetch_Done <= 1; + end + end + end - assign s_axis_tready = w_Visible; - assign o_mm2s_fsync = (r_H_Counter == 0) && (r_V_Counter == 0); + // VGA reads from opposite buffer that VDMA is filling (proper double buffering) + wire [15:0] w_Current_Pixel = w_Visible ? + (r_Active_Buffer ? r_Row_Buffer_1[r_H_Counter[9:0]] : r_Row_Buffer_0[r_H_Counter[9:0]]) : 16'h0000; - assign o_Red = (s_axis_tready && s_axis_tvalid) ? s_axis_tdata[15:12] : {BITS_PER_COLOR_CHANNEL{1'b0}}; - assign o_Green = (s_axis_tready && s_axis_tvalid) ? s_axis_tdata[10:7] : {BITS_PER_COLOR_CHANNEL{1'b0}}; - assign o_Blue = (s_axis_tready && s_axis_tvalid) ? s_axis_tdata[4:1] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Red = w_Visible ? w_Current_Pixel[15:12] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Green = w_Visible ? w_Current_Pixel[10:7] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Blue = w_Visible ? w_Current_Pixel[4:1] : {BITS_PER_COLOR_CHANNEL{1'b0}}; assign o_Horizontal_Sync = ~(r_H_State == STATE_SYNC); // Invert for active low assign o_Vertical_Sync = ~(r_V_State == STATE_SYNC); // Invert for active low diff --git a/tests/Makefile b/tests/Makefile index 231a4f5..9a3b595 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -74,8 +74,8 @@ CPU_INTEGRATION_TESTS_MODULE = "cpu.integration_tests.test_instruction_fetch, \ cpu.integration_tests.test_debug_write_pc" VGA_UNIT_TESTS_TOPLEVEL = vga_unit_tests_harness -VGA_UNIT_TESTS_MODULE = "vga.unit_tests.test_vga_timing, \ - vga.unit_tests.test_vga_data" +VGA_UNIT_TESTS_MODULE = "vga.unit_tests.test_vga_comprehensive, \ + vga.unit_tests.test_vga_pattern" TEST_TYPE ?= unit diff --git a/tests/test_output/vdma_input_data.txt b/tests/test_output/vdma_input_data.txt new file mode 100644 index 0000000..683be95 --- /dev/null +++ b/tests/test_output/vdma_input_data.txt @@ -0,0 +1,641 @@ +# Format: cycle,row,col,pixel_data_hex,red,green,blue,blanking_prefetch_done,fill_addr,active_buffer,tready +1,0,0,0000,0,0,0,0,0,0,1 +2,0,1,1002,1,0,1,0,1,0,1 +3,0,2,2004,2,0,2,0,2,0,1 +4,0,3,3006,3,0,3,0,3,0,1 +5,0,4,4008,4,0,4,0,4,0,1 +6,0,5,500A,5,0,5,0,5,0,1 +7,0,6,600C,6,0,6,0,6,0,1 +8,0,7,700E,7,0,7,0,7,0,1 +9,0,8,8010,8,0,8,0,8,0,1 +10,0,9,9012,9,0,9,0,9,0,1 +11,0,10,A014,10,0,10,0,10,0,1 +12,0,11,B016,11,0,11,0,11,0,1 +13,0,12,C018,12,0,12,0,12,0,1 +14,0,13,D01A,13,0,13,0,13,0,1 +15,0,14,E01C,14,0,14,0,14,0,1 +16,0,15,F01E,15,0,15,0,15,0,1 +17,0,16,0000,0,0,0,0,16,0,1 +18,0,17,1002,1,0,1,0,17,0,1 +19,0,18,2004,2,0,2,0,18,0,1 +20,0,19,3006,3,0,3,0,19,0,1 +21,0,20,4008,4,0,4,0,20,0,1 +22,0,21,500A,5,0,5,0,21,0,1 +23,0,22,600C,6,0,6,0,22,0,1 +24,0,23,700E,7,0,7,0,23,0,1 +25,0,24,8010,8,0,8,0,24,0,1 +26,0,25,9012,9,0,9,0,25,0,1 +27,0,26,A014,10,0,10,0,26,0,1 +28,0,27,B016,11,0,11,0,27,0,1 +29,0,28,C018,12,0,12,0,28,0,1 +30,0,29,D01A,13,0,13,0,29,0,1 +31,0,30,E01C,14,0,14,0,30,0,1 +32,0,31,F01E,15,0,15,0,31,0,1 +33,0,32,0000,0,0,0,0,32,0,1 +34,0,33,1002,1,0,1,0,33,0,1 +35,0,34,2004,2,0,2,0,34,0,1 +36,0,35,3006,3,0,3,0,35,0,1 +37,0,36,4008,4,0,4,0,36,0,1 +38,0,37,500A,5,0,5,0,37,0,1 +39,0,38,600C,6,0,6,0,38,0,1 +40,0,39,700E,7,0,7,0,39,0,1 +41,0,40,8010,8,0,8,0,40,0,1 +42,0,41,9012,9,0,9,0,41,0,1 +43,0,42,A014,10,0,10,0,42,0,1 +44,0,43,B016,11,0,11,0,43,0,1 +45,0,44,C018,12,0,12,0,44,0,1 +46,0,45,D01A,13,0,13,0,45,0,1 +47,0,46,E01C,14,0,14,0,46,0,1 +48,0,47,F01E,15,0,15,0,47,0,1 +49,0,48,0000,0,0,0,0,48,0,1 +50,0,49,1002,1,0,1,0,49,0,1 +51,0,50,2004,2,0,2,0,50,0,1 +52,0,51,3006,3,0,3,0,51,0,1 +53,0,52,4008,4,0,4,0,52,0,1 +54,0,53,500A,5,0,5,0,53,0,1 +55,0,54,600C,6,0,6,0,54,0,1 +56,0,55,700E,7,0,7,0,55,0,1 +57,0,56,8010,8,0,8,0,56,0,1 +58,0,57,9012,9,0,9,0,57,0,1 +59,0,58,A014,10,0,10,0,58,0,1 +60,0,59,B016,11,0,11,0,59,0,1 +61,0,60,C018,12,0,12,0,60,0,1 +62,0,61,D01A,13,0,13,0,61,0,1 +63,0,62,E01C,14,0,14,0,62,0,1 +64,0,63,F01E,15,0,15,0,63,0,1 +65,0,64,0000,0,0,0,0,64,0,1 +66,0,65,1002,1,0,1,0,65,0,1 +67,0,66,2004,2,0,2,0,66,0,1 +68,0,67,3006,3,0,3,0,67,0,1 +69,0,68,4008,4,0,4,0,68,0,1 +70,0,69,500A,5,0,5,0,69,0,1 +71,0,70,600C,6,0,6,0,70,0,1 +72,0,71,700E,7,0,7,0,71,0,1 +73,0,72,8010,8,0,8,0,72,0,1 +74,0,73,9012,9,0,9,0,73,0,1 +75,0,74,A014,10,0,10,0,74,0,1 +76,0,75,B016,11,0,11,0,75,0,1 +77,0,76,C018,12,0,12,0,76,0,1 +78,0,77,D01A,13,0,13,0,77,0,1 +79,0,78,E01C,14,0,14,0,78,0,1 +80,0,79,F01E,15,0,15,0,79,0,1 +81,0,80,0000,0,0,0,0,80,0,1 +82,0,81,1002,1,0,1,0,81,0,1 +83,0,82,2004,2,0,2,0,82,0,1 +84,0,83,3006,3,0,3,0,83,0,1 +85,0,84,4008,4,0,4,0,84,0,1 +86,0,85,500A,5,0,5,0,85,0,1 +87,0,86,600C,6,0,6,0,86,0,1 +88,0,87,700E,7,0,7,0,87,0,1 +89,0,88,8010,8,0,8,0,88,0,1 +90,0,89,9012,9,0,9,0,89,0,1 +91,0,90,A014,10,0,10,0,90,0,1 +92,0,91,B016,11,0,11,0,91,0,1 +93,0,92,C018,12,0,12,0,92,0,1 +94,0,93,D01A,13,0,13,0,93,0,1 +95,0,94,E01C,14,0,14,0,94,0,1 +96,0,95,F01E,15,0,15,0,95,0,1 +97,0,96,0000,0,0,0,0,96,0,1 +98,0,97,1002,1,0,1,0,97,0,1 +99,0,98,2004,2,0,2,0,98,0,1 +100,0,99,3006,3,0,3,0,99,0,1 +101,0,100,4008,4,0,4,0,100,0,1 +102,0,101,500A,5,0,5,0,101,0,1 +103,0,102,600C,6,0,6,0,102,0,1 +104,0,103,700E,7,0,7,0,103,0,1 +105,0,104,8010,8,0,8,0,104,0,1 +106,0,105,9012,9,0,9,0,105,0,1 +107,0,106,A014,10,0,10,0,106,0,1 +108,0,107,B016,11,0,11,0,107,0,1 +109,0,108,C018,12,0,12,0,108,0,1 +110,0,109,D01A,13,0,13,0,109,0,1 +111,0,110,E01C,14,0,14,0,110,0,1 +112,0,111,F01E,15,0,15,0,111,0,1 +113,0,112,0000,0,0,0,0,112,0,1 +114,0,113,1002,1,0,1,0,113,0,1 +115,0,114,2004,2,0,2,0,114,0,1 +116,0,115,3006,3,0,3,0,115,0,1 +117,0,116,4008,4,0,4,0,116,0,1 +118,0,117,500A,5,0,5,0,117,0,1 +119,0,118,600C,6,0,6,0,118,0,1 +120,0,119,700E,7,0,7,0,119,0,1 +121,0,120,8010,8,0,8,0,120,0,1 +122,0,121,9012,9,0,9,0,121,0,1 +123,0,122,A014,10,0,10,0,122,0,1 +124,0,123,B016,11,0,11,0,123,0,1 +125,0,124,C018,12,0,12,0,124,0,1 +126,0,125,D01A,13,0,13,0,125,0,1 +127,0,126,E01C,14,0,14,0,126,0,1 +128,0,127,F01E,15,0,15,0,127,0,1 +129,0,128,0000,0,0,0,0,128,0,1 +130,0,129,1002,1,0,1,0,129,0,1 +131,0,130,2004,2,0,2,0,130,0,1 +132,0,131,3006,3,0,3,0,131,0,1 +133,0,132,4008,4,0,4,0,132,0,1 +134,0,133,500A,5,0,5,0,133,0,1 +135,0,134,600C,6,0,6,0,134,0,1 +136,0,135,700E,7,0,7,0,135,0,1 +137,0,136,8010,8,0,8,0,136,0,1 +138,0,137,9012,9,0,9,0,137,0,1 +139,0,138,A014,10,0,10,0,138,0,1 +140,0,139,B016,11,0,11,0,139,0,1 +141,0,140,C018,12,0,12,0,140,0,1 +142,0,141,D01A,13,0,13,0,141,0,1 +143,0,142,E01C,14,0,14,0,142,0,1 +144,0,143,F01E,15,0,15,0,143,0,1 +145,0,144,0000,0,0,0,0,144,0,1 +146,0,145,1002,1,0,1,0,145,0,1 +147,0,146,2004,2,0,2,0,146,0,1 +148,0,147,3006,3,0,3,0,147,0,1 +149,0,148,4008,4,0,4,0,148,0,1 +150,0,149,500A,5,0,5,0,149,0,1 +151,0,150,600C,6,0,6,0,150,0,1 +152,0,151,700E,7,0,7,0,151,0,1 +153,0,152,8010,8,0,8,0,152,0,1 +154,0,153,9012,9,0,9,0,153,0,1 +155,0,154,A014,10,0,10,0,154,0,1 +156,0,155,B016,11,0,11,0,155,0,1 +157,0,156,C018,12,0,12,0,156,0,1 +158,0,157,D01A,13,0,13,0,157,0,1 +159,0,158,E01C,14,0,14,0,158,0,1 +160,0,159,F01E,15,0,15,0,159,0,1 +161,0,160,0000,0,0,0,0,160,0,1 +162,0,161,1002,1,0,1,0,161,0,1 +163,0,162,2004,2,0,2,0,162,0,1 +164,0,163,3006,3,0,3,0,163,0,1 +165,0,164,4008,4,0,4,0,164,0,1 +166,0,165,500A,5,0,5,0,165,0,1 +167,0,166,600C,6,0,6,0,166,0,1 +168,0,167,700E,7,0,7,0,167,0,1 +169,0,168,8010,8,0,8,0,168,0,1 +170,0,169,9012,9,0,9,0,169,0,1 +171,0,170,A014,10,0,10,0,170,0,1 +172,0,171,B016,11,0,11,0,171,0,1 +173,0,172,C018,12,0,12,0,172,0,1 +174,0,173,D01A,13,0,13,0,173,0,1 +175,0,174,E01C,14,0,14,0,174,0,1 +176,0,175,F01E,15,0,15,0,175,0,1 +177,0,176,0000,0,0,0,0,176,0,1 +178,0,177,1002,1,0,1,0,177,0,1 +179,0,178,2004,2,0,2,0,178,0,1 +180,0,179,3006,3,0,3,0,179,0,1 +181,0,180,4008,4,0,4,0,180,0,1 +182,0,181,500A,5,0,5,0,181,0,1 +183,0,182,600C,6,0,6,0,182,0,1 +184,0,183,700E,7,0,7,0,183,0,1 +185,0,184,8010,8,0,8,0,184,0,1 +186,0,185,9012,9,0,9,0,185,0,1 +187,0,186,A014,10,0,10,0,186,0,1 +188,0,187,B016,11,0,11,0,187,0,1 +189,0,188,C018,12,0,12,0,188,0,1 +190,0,189,D01A,13,0,13,0,189,0,1 +191,0,190,E01C,14,0,14,0,190,0,1 +192,0,191,F01E,15,0,15,0,191,0,1 +193,0,192,0000,0,0,0,0,192,0,1 +194,0,193,1002,1,0,1,0,193,0,1 +195,0,194,2004,2,0,2,0,194,0,1 +196,0,195,3006,3,0,3,0,195,0,1 +197,0,196,4008,4,0,4,0,196,0,1 +198,0,197,500A,5,0,5,0,197,0,1 +199,0,198,600C,6,0,6,0,198,0,1 +200,0,199,700E,7,0,7,0,199,0,1 +201,0,200,8010,8,0,8,0,200,0,1 +202,0,201,9012,9,0,9,0,201,0,1 +203,0,202,A014,10,0,10,0,202,0,1 +204,0,203,B016,11,0,11,0,203,0,1 +205,0,204,C018,12,0,12,0,204,0,1 +206,0,205,D01A,13,0,13,0,205,0,1 +207,0,206,E01C,14,0,14,0,206,0,1 +208,0,207,F01E,15,0,15,0,207,0,1 +209,0,208,0000,0,0,0,0,208,0,1 +210,0,209,1002,1,0,1,0,209,0,1 +211,0,210,2004,2,0,2,0,210,0,1 +212,0,211,3006,3,0,3,0,211,0,1 +213,0,212,4008,4,0,4,0,212,0,1 +214,0,213,500A,5,0,5,0,213,0,1 +215,0,214,600C,6,0,6,0,214,0,1 +216,0,215,700E,7,0,7,0,215,0,1 +217,0,216,8010,8,0,8,0,216,0,1 +218,0,217,9012,9,0,9,0,217,0,1 +219,0,218,A014,10,0,10,0,218,0,1 +220,0,219,B016,11,0,11,0,219,0,1 +221,0,220,C018,12,0,12,0,220,0,1 +222,0,221,D01A,13,0,13,0,221,0,1 +223,0,222,E01C,14,0,14,0,222,0,1 +224,0,223,F01E,15,0,15,0,223,0,1 +225,0,224,0000,0,0,0,0,224,0,1 +226,0,225,1002,1,0,1,0,225,0,1 +227,0,226,2004,2,0,2,0,226,0,1 +228,0,227,3006,3,0,3,0,227,0,1 +229,0,228,4008,4,0,4,0,228,0,1 +230,0,229,500A,5,0,5,0,229,0,1 +231,0,230,600C,6,0,6,0,230,0,1 +232,0,231,700E,7,0,7,0,231,0,1 +233,0,232,8010,8,0,8,0,232,0,1 +234,0,233,9012,9,0,9,0,233,0,1 +235,0,234,A014,10,0,10,0,234,0,1 +236,0,235,B016,11,0,11,0,235,0,1 +237,0,236,C018,12,0,12,0,236,0,1 +238,0,237,D01A,13,0,13,0,237,0,1 +239,0,238,E01C,14,0,14,0,238,0,1 +240,0,239,F01E,15,0,15,0,239,0,1 +241,0,240,0000,0,0,0,0,240,0,1 +242,0,241,1002,1,0,1,0,241,0,1 +243,0,242,2004,2,0,2,0,242,0,1 +244,0,243,3006,3,0,3,0,243,0,1 +245,0,244,4008,4,0,4,0,244,0,1 +246,0,245,500A,5,0,5,0,245,0,1 +247,0,246,600C,6,0,6,0,246,0,1 +248,0,247,700E,7,0,7,0,247,0,1 +249,0,248,8010,8,0,8,0,248,0,1 +250,0,249,9012,9,0,9,0,249,0,1 +251,0,250,A014,10,0,10,0,250,0,1 +252,0,251,B016,11,0,11,0,251,0,1 +253,0,252,C018,12,0,12,0,252,0,1 +254,0,253,D01A,13,0,13,0,253,0,1 +255,0,254,E01C,14,0,14,0,254,0,1 +256,0,255,F01E,15,0,15,0,255,0,1 +257,0,256,0000,0,0,0,0,256,0,1 +258,0,257,1002,1,0,1,0,257,0,1 +259,0,258,2004,2,0,2,0,258,0,1 +260,0,259,3006,3,0,3,0,259,0,1 +261,0,260,4008,4,0,4,0,260,0,1 +262,0,261,500A,5,0,5,0,261,0,1 +263,0,262,600C,6,0,6,0,262,0,1 +264,0,263,700E,7,0,7,0,263,0,1 +265,0,264,8010,8,0,8,0,264,0,1 +266,0,265,9012,9,0,9,0,265,0,1 +267,0,266,A014,10,0,10,0,266,0,1 +268,0,267,B016,11,0,11,0,267,0,1 +269,0,268,C018,12,0,12,0,268,0,1 +270,0,269,D01A,13,0,13,0,269,0,1 +271,0,270,E01C,14,0,14,0,270,0,1 +272,0,271,F01E,15,0,15,0,271,0,1 +273,0,272,0000,0,0,0,0,272,0,1 +274,0,273,1002,1,0,1,0,273,0,1 +275,0,274,2004,2,0,2,0,274,0,1 +276,0,275,3006,3,0,3,0,275,0,1 +277,0,276,4008,4,0,4,0,276,0,1 +278,0,277,500A,5,0,5,0,277,0,1 +279,0,278,600C,6,0,6,0,278,0,1 +280,0,279,700E,7,0,7,0,279,0,1 +281,0,280,8010,8,0,8,0,280,0,1 +282,0,281,9012,9,0,9,0,281,0,1 +283,0,282,A014,10,0,10,0,282,0,1 +284,0,283,B016,11,0,11,0,283,0,1 +285,0,284,C018,12,0,12,0,284,0,1 +286,0,285,D01A,13,0,13,0,285,0,1 +287,0,286,E01C,14,0,14,0,286,0,1 +288,0,287,F01E,15,0,15,0,287,0,1 +289,0,288,0000,0,0,0,0,288,0,1 +290,0,289,1002,1,0,1,0,289,0,1 +291,0,290,2004,2,0,2,0,290,0,1 +292,0,291,3006,3,0,3,0,291,0,1 +293,0,292,4008,4,0,4,0,292,0,1 +294,0,293,500A,5,0,5,0,293,0,1 +295,0,294,600C,6,0,6,0,294,0,1 +296,0,295,700E,7,0,7,0,295,0,1 +297,0,296,8010,8,0,8,0,296,0,1 +298,0,297,9012,9,0,9,0,297,0,1 +299,0,298,A014,10,0,10,0,298,0,1 +300,0,299,B016,11,0,11,0,299,0,1 +301,0,300,C018,12,0,12,0,300,0,1 +302,0,301,D01A,13,0,13,0,301,0,1 +303,0,302,E01C,14,0,14,0,302,0,1 +304,0,303,F01E,15,0,15,0,303,0,1 +305,0,304,0000,0,0,0,0,304,0,1 +306,0,305,1002,1,0,1,0,305,0,1 +307,0,306,2004,2,0,2,0,306,0,1 +308,0,307,3006,3,0,3,0,307,0,1 +309,0,308,4008,4,0,4,0,308,0,1 +310,0,309,500A,5,0,5,0,309,0,1 +311,0,310,600C,6,0,6,0,310,0,1 +312,0,311,700E,7,0,7,0,311,0,1 +313,0,312,8010,8,0,8,0,312,0,1 +314,0,313,9012,9,0,9,0,313,0,1 +315,0,314,A014,10,0,10,0,314,0,1 +316,0,315,B016,11,0,11,0,315,0,1 +317,0,316,C018,12,0,12,0,316,0,1 +318,0,317,D01A,13,0,13,0,317,0,1 +319,0,318,E01C,14,0,14,0,318,0,1 +320,0,319,F01E,15,0,15,0,319,0,1 +321,0,320,0000,0,0,0,0,320,0,1 +322,0,321,1002,1,0,1,0,321,0,1 +323,0,322,2004,2,0,2,0,322,0,1 +324,0,323,3006,3,0,3,0,323,0,1 +325,0,324,4008,4,0,4,0,324,0,1 +326,0,325,500A,5,0,5,0,325,0,1 +327,0,326,600C,6,0,6,0,326,0,1 +328,0,327,700E,7,0,7,0,327,0,1 +329,0,328,8010,8,0,8,0,328,0,1 +330,0,329,9012,9,0,9,0,329,0,1 +331,0,330,A014,10,0,10,0,330,0,1 +332,0,331,B016,11,0,11,0,331,0,1 +333,0,332,C018,12,0,12,0,332,0,1 +334,0,333,D01A,13,0,13,0,333,0,1 +335,0,334,E01C,14,0,14,0,334,0,1 +336,0,335,F01E,15,0,15,0,335,0,1 +337,0,336,0000,0,0,0,0,336,0,1 +338,0,337,1002,1,0,1,0,337,0,1 +339,0,338,2004,2,0,2,0,338,0,1 +340,0,339,3006,3,0,3,0,339,0,1 +341,0,340,4008,4,0,4,0,340,0,1 +342,0,341,500A,5,0,5,0,341,0,1 +343,0,342,600C,6,0,6,0,342,0,1 +344,0,343,700E,7,0,7,0,343,0,1 +345,0,344,8010,8,0,8,0,344,0,1 +346,0,345,9012,9,0,9,0,345,0,1 +347,0,346,A014,10,0,10,0,346,0,1 +348,0,347,B016,11,0,11,0,347,0,1 +349,0,348,C018,12,0,12,0,348,0,1 +350,0,349,D01A,13,0,13,0,349,0,1 +351,0,350,E01C,14,0,14,0,350,0,1 +352,0,351,F01E,15,0,15,0,351,0,1 +353,0,352,0000,0,0,0,0,352,0,1 +354,0,353,1002,1,0,1,0,353,0,1 +355,0,354,2004,2,0,2,0,354,0,1 +356,0,355,3006,3,0,3,0,355,0,1 +357,0,356,4008,4,0,4,0,356,0,1 +358,0,357,500A,5,0,5,0,357,0,1 +359,0,358,600C,6,0,6,0,358,0,1 +360,0,359,700E,7,0,7,0,359,0,1 +361,0,360,8010,8,0,8,0,360,0,1 +362,0,361,9012,9,0,9,0,361,0,1 +363,0,362,A014,10,0,10,0,362,0,1 +364,0,363,B016,11,0,11,0,363,0,1 +365,0,364,C018,12,0,12,0,364,0,1 +366,0,365,D01A,13,0,13,0,365,0,1 +367,0,366,E01C,14,0,14,0,366,0,1 +368,0,367,F01E,15,0,15,0,367,0,1 +369,0,368,0000,0,0,0,0,368,0,1 +370,0,369,1002,1,0,1,0,369,0,1 +371,0,370,2004,2,0,2,0,370,0,1 +372,0,371,3006,3,0,3,0,371,0,1 +373,0,372,4008,4,0,4,0,372,0,1 +374,0,373,500A,5,0,5,0,373,0,1 +375,0,374,600C,6,0,6,0,374,0,1 +376,0,375,700E,7,0,7,0,375,0,1 +377,0,376,8010,8,0,8,0,376,0,1 +378,0,377,9012,9,0,9,0,377,0,1 +379,0,378,A014,10,0,10,0,378,0,1 +380,0,379,B016,11,0,11,0,379,0,1 +381,0,380,C018,12,0,12,0,380,0,1 +382,0,381,D01A,13,0,13,0,381,0,1 +383,0,382,E01C,14,0,14,0,382,0,1 +384,0,383,F01E,15,0,15,0,383,0,1 +385,0,384,0000,0,0,0,0,384,0,1 +386,0,385,1002,1,0,1,0,385,0,1 +387,0,386,2004,2,0,2,0,386,0,1 +388,0,387,3006,3,0,3,0,387,0,1 +389,0,388,4008,4,0,4,0,388,0,1 +390,0,389,500A,5,0,5,0,389,0,1 +391,0,390,600C,6,0,6,0,390,0,1 +392,0,391,700E,7,0,7,0,391,0,1 +393,0,392,8010,8,0,8,0,392,0,1 +394,0,393,9012,9,0,9,0,393,0,1 +395,0,394,A014,10,0,10,0,394,0,1 +396,0,395,B016,11,0,11,0,395,0,1 +397,0,396,C018,12,0,12,0,396,0,1 +398,0,397,D01A,13,0,13,0,397,0,1 +399,0,398,E01C,14,0,14,0,398,0,1 +400,0,399,F01E,15,0,15,0,399,0,1 +401,0,400,0000,0,0,0,0,400,0,1 +402,0,401,1002,1,0,1,0,401,0,1 +403,0,402,2004,2,0,2,0,402,0,1 +404,0,403,3006,3,0,3,0,403,0,1 +405,0,404,4008,4,0,4,0,404,0,1 +406,0,405,500A,5,0,5,0,405,0,1 +407,0,406,600C,6,0,6,0,406,0,1 +408,0,407,700E,7,0,7,0,407,0,1 +409,0,408,8010,8,0,8,0,408,0,1 +410,0,409,9012,9,0,9,0,409,0,1 +411,0,410,A014,10,0,10,0,410,0,1 +412,0,411,B016,11,0,11,0,411,0,1 +413,0,412,C018,12,0,12,0,412,0,1 +414,0,413,D01A,13,0,13,0,413,0,1 +415,0,414,E01C,14,0,14,0,414,0,1 +416,0,415,F01E,15,0,15,0,415,0,1 +417,0,416,0000,0,0,0,0,416,0,1 +418,0,417,1002,1,0,1,0,417,0,1 +419,0,418,2004,2,0,2,0,418,0,1 +420,0,419,3006,3,0,3,0,419,0,1 +421,0,420,4008,4,0,4,0,420,0,1 +422,0,421,500A,5,0,5,0,421,0,1 +423,0,422,600C,6,0,6,0,422,0,1 +424,0,423,700E,7,0,7,0,423,0,1 +425,0,424,8010,8,0,8,0,424,0,1 +426,0,425,9012,9,0,9,0,425,0,1 +427,0,426,A014,10,0,10,0,426,0,1 +428,0,427,B016,11,0,11,0,427,0,1 +429,0,428,C018,12,0,12,0,428,0,1 +430,0,429,D01A,13,0,13,0,429,0,1 +431,0,430,E01C,14,0,14,0,430,0,1 +432,0,431,F01E,15,0,15,0,431,0,1 +433,0,432,0000,0,0,0,0,432,0,1 +434,0,433,1002,1,0,1,0,433,0,1 +435,0,434,2004,2,0,2,0,434,0,1 +436,0,435,3006,3,0,3,0,435,0,1 +437,0,436,4008,4,0,4,0,436,0,1 +438,0,437,500A,5,0,5,0,437,0,1 +439,0,438,600C,6,0,6,0,438,0,1 +440,0,439,700E,7,0,7,0,439,0,1 +441,0,440,8010,8,0,8,0,440,0,1 +442,0,441,9012,9,0,9,0,441,0,1 +443,0,442,A014,10,0,10,0,442,0,1 +444,0,443,B016,11,0,11,0,443,0,1 +445,0,444,C018,12,0,12,0,444,0,1 +446,0,445,D01A,13,0,13,0,445,0,1 +447,0,446,E01C,14,0,14,0,446,0,1 +448,0,447,F01E,15,0,15,0,447,0,1 +449,0,448,0000,0,0,0,0,448,0,1 +450,0,449,1002,1,0,1,0,449,0,1 +451,0,450,2004,2,0,2,0,450,0,1 +452,0,451,3006,3,0,3,0,451,0,1 +453,0,452,4008,4,0,4,0,452,0,1 +454,0,453,500A,5,0,5,0,453,0,1 +455,0,454,600C,6,0,6,0,454,0,1 +456,0,455,700E,7,0,7,0,455,0,1 +457,0,456,8010,8,0,8,0,456,0,1 +458,0,457,9012,9,0,9,0,457,0,1 +459,0,458,A014,10,0,10,0,458,0,1 +460,0,459,B016,11,0,11,0,459,0,1 +461,0,460,C018,12,0,12,0,460,0,1 +462,0,461,D01A,13,0,13,0,461,0,1 +463,0,462,E01C,14,0,14,0,462,0,1 +464,0,463,F01E,15,0,15,0,463,0,1 +465,0,464,0000,0,0,0,0,464,0,1 +466,0,465,1002,1,0,1,0,465,0,1 +467,0,466,2004,2,0,2,0,466,0,1 +468,0,467,3006,3,0,3,0,467,0,1 +469,0,468,4008,4,0,4,0,468,0,1 +470,0,469,500A,5,0,5,0,469,0,1 +471,0,470,600C,6,0,6,0,470,0,1 +472,0,471,700E,7,0,7,0,471,0,1 +473,0,472,8010,8,0,8,0,472,0,1 +474,0,473,9012,9,0,9,0,473,0,1 +475,0,474,A014,10,0,10,0,474,0,1 +476,0,475,B016,11,0,11,0,475,0,1 +477,0,476,C018,12,0,12,0,476,0,1 +478,0,477,D01A,13,0,13,0,477,0,1 +479,0,478,E01C,14,0,14,0,478,0,1 +480,0,479,F01E,15,0,15,0,479,0,1 +481,0,480,0000,0,0,0,0,480,0,1 +482,0,481,1002,1,0,1,0,481,0,1 +483,0,482,2004,2,0,2,0,482,0,1 +484,0,483,3006,3,0,3,0,483,0,1 +485,0,484,4008,4,0,4,0,484,0,1 +486,0,485,500A,5,0,5,0,485,0,1 +487,0,486,600C,6,0,6,0,486,0,1 +488,0,487,700E,7,0,7,0,487,0,1 +489,0,488,8010,8,0,8,0,488,0,1 +490,0,489,9012,9,0,9,0,489,0,1 +491,0,490,A014,10,0,10,0,490,0,1 +492,0,491,B016,11,0,11,0,491,0,1 +493,0,492,C018,12,0,12,0,492,0,1 +494,0,493,D01A,13,0,13,0,493,0,1 +495,0,494,E01C,14,0,14,0,494,0,1 +496,0,495,F01E,15,0,15,0,495,0,1 +497,0,496,0000,0,0,0,0,496,0,1 +498,0,497,1002,1,0,1,0,497,0,1 +499,0,498,2004,2,0,2,0,498,0,1 +500,0,499,3006,3,0,3,0,499,0,1 +501,0,500,4008,4,0,4,0,500,0,1 +502,0,501,500A,5,0,5,0,501,0,1 +503,0,502,600C,6,0,6,0,502,0,1 +504,0,503,700E,7,0,7,0,503,0,1 +505,0,504,8010,8,0,8,0,504,0,1 +506,0,505,9012,9,0,9,0,505,0,1 +507,0,506,A014,10,0,10,0,506,0,1 +508,0,507,B016,11,0,11,0,507,0,1 +509,0,508,C018,12,0,12,0,508,0,1 +510,0,509,D01A,13,0,13,0,509,0,1 +511,0,510,E01C,14,0,14,0,510,0,1 +512,0,511,F01E,15,0,15,0,511,0,1 +513,0,512,0000,0,0,0,0,512,0,1 +514,0,513,1002,1,0,1,0,513,0,1 +515,0,514,2004,2,0,2,0,514,0,1 +516,0,515,3006,3,0,3,0,515,0,1 +517,0,516,4008,4,0,4,0,516,0,1 +518,0,517,500A,5,0,5,0,517,0,1 +519,0,518,600C,6,0,6,0,518,0,1 +520,0,519,700E,7,0,7,0,519,0,1 +521,0,520,8010,8,0,8,0,520,0,1 +522,0,521,9012,9,0,9,0,521,0,1 +523,0,522,A014,10,0,10,0,522,0,1 +524,0,523,B016,11,0,11,0,523,0,1 +525,0,524,C018,12,0,12,0,524,0,1 +526,0,525,D01A,13,0,13,0,525,0,1 +527,0,526,E01C,14,0,14,0,526,0,1 +528,0,527,F01E,15,0,15,0,527,0,1 +529,0,528,0000,0,0,0,0,528,0,1 +530,0,529,1002,1,0,1,0,529,0,1 +531,0,530,2004,2,0,2,0,530,0,1 +532,0,531,3006,3,0,3,0,531,0,1 +533,0,532,4008,4,0,4,0,532,0,1 +534,0,533,500A,5,0,5,0,533,0,1 +535,0,534,600C,6,0,6,0,534,0,1 +536,0,535,700E,7,0,7,0,535,0,1 +537,0,536,8010,8,0,8,0,536,0,1 +538,0,537,9012,9,0,9,0,537,0,1 +539,0,538,A014,10,0,10,0,538,0,1 +540,0,539,B016,11,0,11,0,539,0,1 +541,0,540,C018,12,0,12,0,540,0,1 +542,0,541,D01A,13,0,13,0,541,0,1 +543,0,542,E01C,14,0,14,0,542,0,1 +544,0,543,F01E,15,0,15,0,543,0,1 +545,0,544,0000,0,0,0,0,544,0,1 +546,0,545,1002,1,0,1,0,545,0,1 +547,0,546,2004,2,0,2,0,546,0,1 +548,0,547,3006,3,0,3,0,547,0,1 +549,0,548,4008,4,0,4,0,548,0,1 +550,0,549,500A,5,0,5,0,549,0,1 +551,0,550,600C,6,0,6,0,550,0,1 +552,0,551,700E,7,0,7,0,551,0,1 +553,0,552,8010,8,0,8,0,552,0,1 +554,0,553,9012,9,0,9,0,553,0,1 +555,0,554,A014,10,0,10,0,554,0,1 +556,0,555,B016,11,0,11,0,555,0,1 +557,0,556,C018,12,0,12,0,556,0,1 +558,0,557,D01A,13,0,13,0,557,0,1 +559,0,558,E01C,14,0,14,0,558,0,1 +560,0,559,F01E,15,0,15,0,559,0,1 +561,0,560,0000,0,0,0,0,560,0,1 +562,0,561,1002,1,0,1,0,561,0,1 +563,0,562,2004,2,0,2,0,562,0,1 +564,0,563,3006,3,0,3,0,563,0,1 +565,0,564,4008,4,0,4,0,564,0,1 +566,0,565,500A,5,0,5,0,565,0,1 +567,0,566,600C,6,0,6,0,566,0,1 +568,0,567,700E,7,0,7,0,567,0,1 +569,0,568,8010,8,0,8,0,568,0,1 +570,0,569,9012,9,0,9,0,569,0,1 +571,0,570,A014,10,0,10,0,570,0,1 +572,0,571,B016,11,0,11,0,571,0,1 +573,0,572,C018,12,0,12,0,572,0,1 +574,0,573,D01A,13,0,13,0,573,0,1 +575,0,574,E01C,14,0,14,0,574,0,1 +576,0,575,F01E,15,0,15,0,575,0,1 +577,0,576,0000,0,0,0,0,576,0,1 +578,0,577,1002,1,0,1,0,577,0,1 +579,0,578,2004,2,0,2,0,578,0,1 +580,0,579,3006,3,0,3,0,579,0,1 +581,0,580,4008,4,0,4,0,580,0,1 +582,0,581,500A,5,0,5,0,581,0,1 +583,0,582,600C,6,0,6,0,582,0,1 +584,0,583,700E,7,0,7,0,583,0,1 +585,0,584,8010,8,0,8,0,584,0,1 +586,0,585,9012,9,0,9,0,585,0,1 +587,0,586,A014,10,0,10,0,586,0,1 +588,0,587,B016,11,0,11,0,587,0,1 +589,0,588,C018,12,0,12,0,588,0,1 +590,0,589,D01A,13,0,13,0,589,0,1 +591,0,590,E01C,14,0,14,0,590,0,1 +592,0,591,F01E,15,0,15,0,591,0,1 +593,0,592,0000,0,0,0,0,592,0,1 +594,0,593,1002,1,0,1,0,593,0,1 +595,0,594,2004,2,0,2,0,594,0,1 +596,0,595,3006,3,0,3,0,595,0,1 +597,0,596,4008,4,0,4,0,596,0,1 +598,0,597,500A,5,0,5,0,597,0,1 +599,0,598,600C,6,0,6,0,598,0,1 +600,0,599,700E,7,0,7,0,599,0,1 +601,0,600,8010,8,0,8,0,600,0,1 +602,0,601,9012,9,0,9,0,601,0,1 +603,0,602,A014,10,0,10,0,602,0,1 +604,0,603,B016,11,0,11,0,603,0,1 +605,0,604,C018,12,0,12,0,604,0,1 +606,0,605,D01A,13,0,13,0,605,0,1 +607,0,606,E01C,14,0,14,0,606,0,1 +608,0,607,F01E,15,0,15,0,607,0,1 +609,0,608,0000,0,0,0,0,608,0,1 +610,0,609,1002,1,0,1,0,609,0,1 +611,0,610,2004,2,0,2,0,610,0,1 +612,0,611,3006,3,0,3,0,611,0,1 +613,0,612,4008,4,0,4,0,612,0,1 +614,0,613,500A,5,0,5,0,613,0,1 +615,0,614,600C,6,0,6,0,614,0,1 +616,0,615,700E,7,0,7,0,615,0,1 +617,0,616,8010,8,0,8,0,616,0,1 +618,0,617,9012,9,0,9,0,617,0,1 +619,0,618,A014,10,0,10,0,618,0,1 +620,0,619,B016,11,0,11,0,619,0,1 +621,0,620,C018,12,0,12,0,620,0,1 +622,0,621,D01A,13,0,13,0,621,0,1 +623,0,622,E01C,14,0,14,0,622,0,1 +624,0,623,F01E,15,0,15,0,623,0,1 +625,0,624,0000,0,0,0,0,624,0,1 +626,0,625,1002,1,0,1,0,625,0,1 +627,0,626,2004,2,0,2,0,626,0,1 +628,0,627,3006,3,0,3,0,627,0,1 +629,0,628,4008,4,0,4,0,628,0,1 +630,0,629,500A,5,0,5,0,629,0,1 +631,0,630,600C,6,0,6,0,630,0,1 +632,0,631,700E,7,0,7,0,631,0,1 +633,0,632,8010,8,0,8,0,632,0,1 +634,0,633,9012,9,0,9,0,633,0,1 +635,0,634,A014,10,0,10,0,634,0,1 +636,0,635,B016,11,0,11,0,635,0,1 +637,0,636,C018,12,0,12,0,636,0,1 +638,0,637,D01A,13,0,13,0,637,0,1 +639,0,638,E01C,14,0,14,0,638,0,1 +640,0,639,F01E,15,0,15,0,639,0,1 diff --git a/tests/test_output/vga_output_data.txt b/tests/test_output/vga_output_data.txt new file mode 100644 index 0000000..54a8c4b --- /dev/null +++ b/tests/test_output/vga_output_data.txt @@ -0,0 +1,6 @@ +# Format: cycle,h_count,v_count,red,green,blue +144000,0,0,0,0,0 +144001,0,0,0,0,0 +144002,0,0,0,0,0 +144003,0,0,0,0,0 +144004,1,0,0,0,0 diff --git a/tests/vga/constants.py b/tests/vga/constants.py index bed173c..1e37f2c 100644 --- a/tests/vga/constants.py +++ b/tests/vga/constants.py @@ -1,52 +1,17 @@ -# VGA 640x480 @ 60Hz timing constants -# These match the values in hdl/vga_out/vga_out.v - -# Horizontal timing (in pixels) +# VGA 640x480@60Hz timing constants (match vga_out.v localparam values) +# Horizontal timing VISIBLE_H = 640 FRONT_PORCH_H = 16 SYNC_PULSE_H = 96 BACK_PORCH_H = 48 TOTAL_H = 800 # VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H + BACK_PORCH_H -# Vertical timing (in lines) +# Vertical timing VISIBLE_V = 480 FRONT_PORCH_V = 10 SYNC_PULSE_V = 2 BACK_PORCH_V = 33 TOTAL_V = 525 # VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V + BACK_PORCH_V -# Clock divider - module advances pixel counter every 4 clocks -CLOCKS_PER_PIXEL = 4 - -# Derived timing constants (in clock cycles) -CLOCKS_PER_LINE = TOTAL_H * CLOCKS_PER_PIXEL # 3200 clocks -CLOCKS_PER_FRAME = TOTAL_V * CLOCKS_PER_LINE # 1,680,000 clocks - -# Horizontal region boundaries (pixel positions) -H_VISIBLE_END = VISIBLE_H # 640 -H_FRONT_PORCH_END = H_VISIBLE_END + FRONT_PORCH_H # 656 -H_SYNC_END = H_FRONT_PORCH_END + SYNC_PULSE_H # 752 -# H_BACK_PORCH_END = TOTAL_H = 800 - -# Vertical region boundaries (line positions) -V_VISIBLE_END = VISIBLE_V # 480 -V_FRONT_PORCH_END = V_VISIBLE_END + FRONT_PORCH_V # 490 -V_SYNC_END = V_FRONT_PORCH_END + SYNC_PULSE_V # 492 -# V_BACK_PORCH_END = TOTAL_V = 525 - - -def make_pixel(r, g, b): - """Create a 16-bit pixel value from 4-bit RGB components. - - Format: [15:12]=R, [10:7]=G, [4:1]=B (bits 11, 6, 5, 0 unused) - """ - return ((r & 0xF) << 12) | ((g & 0xF) << 7) | ((b & 0xF) << 1) - - -# Test color constants -PIXEL_RED = make_pixel(0xF, 0x0, 0x0) -PIXEL_GREEN = make_pixel(0x0, 0xF, 0x0) -PIXEL_BLUE = make_pixel(0x0, 0x0, 0xF) -PIXEL_WHITE = make_pixel(0xF, 0xF, 0xF) -PIXEL_BLACK = make_pixel(0x0, 0x0, 0x0) -PIXEL_TEST = make_pixel(0xA, 0x5, 0x3) # Arbitrary test pattern +# Useful for frame calculations +CLOCKS_PER_FRAME = TOTAL_H * TOTAL_V # 420,000 clocks per frame \ No newline at end of file diff --git a/tests/vga/unit_tests/test_vga_comprehensive.py b/tests/vga/unit_tests/test_vga_comprehensive.py new file mode 100644 index 0000000..247c470 --- /dev/null +++ b/tests/vga/unit_tests/test_vga_comprehensive.py @@ -0,0 +1,185 @@ +import cocotb +from cocotb.triggers import RisingEdge +from cocotb.clock import Clock +from vga.constants import * + +async def simple_vdma_simulator(dut): + """Simple VDMA that sends white pixels whenever tready is high""" + white_pixel = 0xF79E # White: R=15, G=15, B=15 (bits 15:12, 10:7, 4:1) + + while True: + if dut.vga_out.s_axis_tready.value: + dut.s_axis_tdata.value = white_pixel + dut.s_axis_tvalid.value = 1 + else: + dut.s_axis_tvalid.value = 0 + + await RisingEdge(dut.i_Clock) + +@cocotb.test() +async def test_vga_divided_clock_timing(dut): + clock = Clock(dut.i_Clock, 10, units="ns") # 100MHz clock (25MHz VGA pixel clock) + cocotb.start_soon(clock.start()) + + dut.i_Reset.value = 1 + dut.s_axis_tvalid.value = 0 + dut.s_axis_tdata.value = 0 + + for _ in range(10): + await RisingEdge(dut.i_Clock) + + dut.i_Reset.value = 0 + await RisingEdge(dut.i_Clock) + + # Test cycles: 5 VGA horizontal lines * 4 system clocks per VGA clock + test_cycles = 5 * TOTAL_H * 4 + for cycle in range(test_cycles): + if cycle % 4000 == 0: + dut._log.info(f"Progress: {cycle}/{test_cycles} ({100*cycle//test_cycles}%)") + + h_count = int(dut.vga_out.r_H_Counter.value) + v_count = int(dut.vga_out.r_V_Counter.value) + clock_count = int(dut.vga_out.r_Clock_Counter.value) + + # Counter bounds checks + assert 0 <= h_count < TOTAL_H, f"H counter {h_count} out of range" + assert 0 <= v_count < TOTAL_V, f"V counter {v_count} out of range" + assert 0 <= clock_count <= 3, f"Clock counter {clock_count} out of range" + + # Horizontal sync timing + if h_count < VISIBLE_H + FRONT_PORCH_H: + assert dut.vga_out.o_Horizontal_Sync.value == 1, f"Hsync should be high at H{h_count}" + elif h_count < VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H: + assert dut.vga_out.o_Horizontal_Sync.value == 0, f"Hsync should be low at H{h_count}" + else: + assert dut.vga_out.o_Horizontal_Sync.value == 1, f"Hsync should be high at H{h_count}" + + # Vertical sync timing + if v_count < VISIBLE_V + FRONT_PORCH_V: + assert dut.vga_out.o_Vertical_Sync.value == 1, f"Vsync should be high at V{v_count}" + elif v_count < VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V: + assert dut.vga_out.o_Vertical_Sync.value == 0, f"Vsync should be low at V{v_count}" + else: + assert dut.vga_out.o_Vertical_Sync.value == 1, f"Vsync should be high at V{v_count}" + + fsync_v_position = VISIBLE_V + if h_count == 0 and v_count == fsync_v_position: + assert dut.vga_out.o_mm2s_fsync.value == 1, f"Fsync missing at start of back porch" + else: + assert dut.vga_out.o_mm2s_fsync.value == 0, f"Fsync spurious at H{h_count}V{v_count}" + + # VGA counters should only update when clock_count == 3 + if clock_count == 3: + prev_h = h_count + prev_v = v_count + await RisingEdge(dut.i_Clock) + new_h = int(dut.vga_out.r_H_Counter.value) + new_v = int(dut.vga_out.r_V_Counter.value) + + # Verify counters incremented correctly + if prev_h == TOTAL_H - 1: + assert new_h == 0, f"H counter should wrap: {prev_h} -> {new_h}" + if prev_v == TOTAL_V - 1: + assert new_v == 0, f"V counter should wrap: {prev_v} -> {new_v}" + else: + assert new_v == prev_v + 1, f"V counter should increment: {prev_v} -> {new_v}" + else: + assert new_h == prev_h + 1, f"H counter should increment: {prev_h} -> {new_h}" + assert new_v == prev_v, f"V counter should stay same: {prev_v} -> {new_v}" + else: + await RisingEdge(dut.i_Clock) + +@cocotb.test() +async def test_vga_with_simple_vdma(dut): + clock = Clock(dut.i_Clock, 10, units="ns") # 100MHz clock (25MHz VGA pixel clock) + cocotb.start_soon(clock.start()) + + dut.i_Reset.value = 1 + dut.s_axis_tvalid.value = 0 + dut.s_axis_tdata.value = 0 + + for _ in range(10): + await RisingEdge(dut.i_Clock) + + dut.i_Reset.value = 0 + await RisingEdge(dut.i_Clock) + + # Start VDMA simulator in background + cocotb.start_soon(simple_vdma_simulator(dut)) + + # Test cycles: 5 VGA horizontal lines * 4 system clocks per VGA clock + test_cycles = 5 * TOTAL_H * 4 + for cycle in range(test_cycles): + if cycle % 4000 == 0: + dut._log.info(f"Progress: {cycle}/{test_cycles} ({100*cycle//test_cycles}%)") + + h_count = int(dut.vga_out.r_H_Counter.value) + v_count = int(dut.vga_out.r_V_Counter.value) + clock_count = int(dut.vga_out.r_Clock_Counter.value) + + # Counter bounds checks + assert 0 <= h_count < TOTAL_H, f"H counter {h_count} out of range" + assert 0 <= v_count < TOTAL_V, f"V counter {v_count} out of range" + assert 0 <= clock_count <= 3, f"Clock counter {clock_count} out of range" + + # Horizontal sync timing + if h_count < VISIBLE_H + FRONT_PORCH_H: + assert dut.vga_out.o_Horizontal_Sync.value == 1, f"Hsync should be high at H{h_count}" + elif h_count < VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H: + assert dut.vga_out.o_Horizontal_Sync.value == 0, f"Hsync should be low at H{h_count}" + else: + assert dut.vga_out.o_Horizontal_Sync.value == 1, f"Hsync should be high at H{h_count}" + + # Vertical sync timing + if v_count < VISIBLE_V + FRONT_PORCH_V: + assert dut.vga_out.o_Vertical_Sync.value == 1, f"Vsync should be high at V{v_count}" + elif v_count < VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V: + assert dut.vga_out.o_Vertical_Sync.value == 0, f"Vsync should be low at V{v_count}" + else: + assert dut.vga_out.o_Vertical_Sync.value == 1, f"Vsync should be high at V{v_count}" + + fsync_v_position = VISIBLE_V + if h_count == 0 and v_count == fsync_v_position: + assert dut.vga_out.o_mm2s_fsync.value == 1, f"Fsync missing at start of back porch" + else: + assert dut.vga_out.o_mm2s_fsync.value == 0, f"Fsync spurious at H{h_count}V{v_count}" + + # RGB validation - white pixel 0xF79E decodes to Red=15, Green=15, Blue=15 + if h_count < VISIBLE_H and v_count < VISIBLE_V: + expected_red = 15 + expected_green = 15 # 0xF79E bits [10:7] = 0xF + expected_blue = 15 # 0xF79E bits [4:1] = 0xF + + actual_red = int(dut.vga_out.o_Red.value) + actual_green = int(dut.vga_out.o_Green.value) + actual_blue = int(dut.vga_out.o_Blue.value) + + assert actual_red == expected_red, f"Red mismatch at H{h_count}V{v_count}: expected {expected_red}, got {actual_red}" + assert actual_green == expected_green, f"Green mismatch at H{h_count}V{v_count}: expected {expected_green}, got {actual_green}" + assert actual_blue == expected_blue, f"Blue mismatch at H{h_count}V{v_count}: expected {expected_blue}, got {actual_blue}" + elif h_count >= VISIBLE_H or v_count >= VISIBLE_V: + # RGB should be black during blanking + assert int(dut.vga_out.o_Red.value) == 0, f"Red not black during blanking at H{h_count}V{v_count}" + assert int(dut.vga_out.o_Green.value) == 0, f"Green not black during blanking at H{h_count}V{v_count}" + assert int(dut.vga_out.o_Blue.value) == 0, f"Blue not black during blanking at H{h_count}V{v_count}" + + # VGA counters should only update when clock_count == 3 + if clock_count == 3: + prev_h = h_count + prev_v = v_count + await RisingEdge(dut.i_Clock) + new_h = int(dut.vga_out.r_H_Counter.value) + new_v = int(dut.vga_out.r_V_Counter.value) + + # Verify counters incremented correctly + if prev_h == TOTAL_H - 1: + assert new_h == 0, f"H counter should wrap: {prev_h} -> {new_h}" + if prev_v == TOTAL_V - 1: + assert new_v == 0, f"V counter should wrap: {prev_v} -> {new_v}" + else: + assert new_v == prev_v + 1, f"V counter should increment: {prev_v} -> {new_v}" + else: + assert new_h == prev_h + 1, f"H counter should increment: {prev_h} -> {new_h}" + assert new_v == prev_v, f"V counter should stay same: {prev_v} -> {new_v}" + else: + await RisingEdge(dut.i_Clock) \ No newline at end of file diff --git a/tests/vga/unit_tests/test_vga_pattern.py b/tests/vga/unit_tests/test_vga_pattern.py new file mode 100644 index 0000000..b28081c --- /dev/null +++ b/tests/vga/unit_tests/test_vga_pattern.py @@ -0,0 +1,272 @@ +import cocotb +from cocotb.triggers import RisingEdge +from cocotb.clock import Clock +from vga.constants import * +import os + +def calculate_pixel_rgb(row, col): + """Calculate RGB values based on row and column position""" + red = col % 16 # Red varies with column + green = row % 16 # Green varies with row + blue = (col + row) % 16 # Blue varies with both + return red, green, blue + +def save_pixel_data(pixel_list, filename): + """Save pixel data to text file for easy comparison""" + os.makedirs("test_output", exist_ok=True) + + with open(f"test_output/{filename}", 'w') as f: + f.write("# Format: cycle,h_count,v_count,red,green,blue\n") + for entry in pixel_list: + cycle, h, v, r, g, b = entry + f.write(f"{cycle},{h},{v},{r},{g},{b}\n") + print(f"Saved pixel data: test_output/{filename} ({len(pixel_list)} pixels)") + +def save_vdma_data(vdma_list, filename): + """Save VDMA data to text file for easy comparison""" + os.makedirs("test_output", exist_ok=True) + + with open(f"test_output/{filename}", 'w') as f: + f.write("# Format: cycle,row,col,pixel_data_hex,red,green,blue,blanking_prefetch_done,fill_addr,active_buffer,tready\n") + for entry in vdma_list: + if len(entry) == 7: + # Legacy format without diagnostics + cycle, row, col, pixel_data, r, g, b = entry + f.write(f"{cycle},{row},{col},{pixel_data:04X},{r},{g},{b},,,\n") + else: + # New format with diagnostics + cycle, row, col, pixel_data, r, g, b, blanking_prefetch_done, fill_addr, active_buffer, tready = entry + f.write(f"{cycle},{row},{col},{pixel_data:04X},{r},{g},{b},{blanking_prefetch_done},{fill_addr},{active_buffer},{tready}\n") + print(f"Saved VDMA data: test_output/{filename} ({len(vdma_list)} pixels)") + +async def pattern_vdma_simulator(dut, capture_data=None): + """VDMA that maintains its own counters and uses frame sync for synchronization""" + current_row = 0 + current_col = 0 + prev_fsync = 0 + cycle = 0 + + while True: + # Check for frame sync rising edge to reset position + fsync = int(dut.vga_out.o_mm2s_fsync.value) + if fsync and not prev_fsync: + current_row = 0 + current_col = 0 + prev_fsync = fsync + + # Always provide data when we have it (proactive DMA) + if current_row < VISIBLE_V: + # Generate RGB based on VDMA's own position counters using shared formula + red, green, blue = calculate_pixel_rgb(current_row, current_col) + + # Pack into 16-bit pixel: Red[15:12], Green[10:7], Blue[4:1] + pixel_data = (red << 12) | (green << 7) | (blue << 1) + + dut.s_axis_tdata.value = pixel_data + dut.s_axis_tvalid.value = 1 + + # Capture VDMA data when requested and handshake occurs + if capture_data is not None and dut.vga_out.s_axis_tready.value: + # Get diagnostic signals for debugging buffer management + blanking_prefetch_done = int(dut.vga_out.r_Blanking_Prefetch_Done.value) + fill_addr = int(dut.vga_out.r_Fill_Addr.value) + active_buffer = int(dut.vga_out.r_Active_Buffer.value) + tready = int(dut.vga_out.s_axis_tready.value) + + capture_data.append((cycle, current_row, current_col, pixel_data, red, green, blue, + blanking_prefetch_done, fill_addr, active_buffer, tready)) + + # Advance VDMA's position counters only when handshake completes + if dut.vga_out.s_axis_tready.value: + current_col += 1 + if current_col >= VISIBLE_H: + current_col = 0 + current_row += 1 + else: + dut.s_axis_tvalid.value = 0 + + cycle += 1 + await RisingEdge(dut.i_Clock) + +@cocotb.test() +async def test_vga_with_pattern_vdma(dut): + clock = Clock(dut.i_Clock, 10, units="ns") # 100MHz clock (25MHz VGA pixel clock) + cocotb.start_soon(clock.start()) + + dut.i_Reset.value = 1 + dut.s_axis_tvalid.value = 0 + dut.s_axis_tdata.value = 0 + + for _ in range(10): + await RisingEdge(dut.i_Clock) + + dut.i_Reset.value = 0 + await RisingEdge(dut.i_Clock) + + # Start pattern VDMA simulator in background + cocotb.start_soon(pattern_vdma_simulator(dut)) + + # Test cycles: 10 VGA horizontal lines * 4 system clocks per VGA clock (same as other tests) + test_cycles = 10 * TOTAL_H * 4 + for cycle in range(test_cycles): + if cycle % 4000 == 0: + dut._log.info(f"Progress: {cycle}/{test_cycles} ({100*cycle//test_cycles}%)") + + h_count = int(dut.vga_out.r_H_Counter.value) + v_count = int(dut.vga_out.r_V_Counter.value) + clock_count = int(dut.vga_out.r_Clock_Counter.value) + + # Counter bounds checks + assert 0 <= h_count < TOTAL_H, f"H counter {h_count} out of range" + assert 0 <= v_count < TOTAL_V, f"V counter {v_count} out of range" + assert 0 <= clock_count <= 3, f"Clock counter {clock_count} out of range" + + # Removed verbose VDMA logging and debug logs + + # RGB pattern validation - Simple validation for 10 lines + if h_count < VISIBLE_H and v_count < VISIBLE_V: + actual_red = int(dut.vga_out.o_Red.value) + actual_green = int(dut.vga_out.o_Green.value) + actual_blue = int(dut.vga_out.o_Blue.value) + + # Calculate expected RGB values for this exact position + exp_red, exp_green, exp_blue = calculate_pixel_rgb(v_count, h_count) + + + assert actual_red == exp_red, f"Red mismatch at H{h_count}V{v_count}: expected {exp_red}, got {actual_red}" + assert actual_green == exp_green, f"Green mismatch at H{h_count}V{v_count}: expected {exp_green}, got {actual_green}" + assert actual_blue == exp_blue, f"Blue mismatch at H{h_count}V{v_count}: expected {exp_blue}, got {actual_blue}" + elif h_count >= VISIBLE_H or v_count >= VISIBLE_V: + # RGB should be black during blanking + assert int(dut.vga_out.o_Red.value) == 0, f"Red not black during blanking at H{h_count}V{v_count}" + assert int(dut.vga_out.o_Green.value) == 0, f"Green not black during blanking at H{h_count}V{v_count}" + assert int(dut.vga_out.o_Blue.value) == 0, f"Blue not black during blanking at H{h_count}V{v_count}" + + # VGA counters should only update when clock_count == 3 + if clock_count == 3: + prev_h = h_count + prev_v = v_count + await RisingEdge(dut.i_Clock) + new_h = int(dut.vga_out.r_H_Counter.value) + new_v = int(dut.vga_out.r_V_Counter.value) + + # Verify counters incremented correctly + if prev_h == TOTAL_H - 1: + assert new_h == 0, f"H counter should wrap: {prev_h} -> {new_h}" + if prev_v == TOTAL_V - 1: + assert new_v == 0, f"V counter should wrap: {prev_v} -> {new_v}" + else: + assert new_v == prev_v + 1, f"V counter should increment: {prev_v} -> {new_v}" + else: + assert new_h == prev_h + 1, f"H counter should increment: {prev_h} -> {new_h}" + assert new_v == prev_v, f"V counter should stay same: {prev_v} -> {new_v}" + else: + await RisingEdge(dut.i_Clock) + +@cocotb.test() +async def test_vga_frame_transition(dut): + """Test VGA frame transitions to ensure timing improvements work across frame boundaries""" + clock = Clock(dut.i_Clock, 10, units="ns") # 100MHz clock (25MHz VGA pixel clock) + cocotb.start_soon(clock.start()) + + dut.i_Reset.value = 1 + dut.s_axis_tvalid.value = 0 + dut.s_axis_tdata.value = 0 + + for _ in range(10): + await RisingEdge(dut.i_Clock) + + dut.i_Reset.value = 0 + await RisingEdge(dut.i_Clock) + + # Create data capture lists + vga_output_data = [] + vdma_input_data = [] + + # Start pattern VDMA simulator in background with data capture + cocotb.start_soon(pattern_vdma_simulator(dut, vdma_input_data)) + + # Test cycles: Cover frame transition - ~75 lines to see frame boundary + transition_lines = 75 + test_cycles = transition_lines * TOTAL_H * 4 + + try: + for cycle in range(test_cycles): + if cycle % 50000 == 0: + dut._log.info(f"Frame transition test progress: {cycle}/{test_cycles} ({100*cycle//test_cycles}%)") + + h_count = int(dut.vga_out.r_H_Counter.value) + v_count = int(dut.vga_out.r_V_Counter.value) + clock_count = int(dut.vga_out.r_Clock_Counter.value) + + # Log key timing events + if h_count == 0 and clock_count == 0: + fsync_v_pos = VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V + (BACK_PORCH_V >> 1) # INITIAL_V_COUNT + if v_count == fsync_v_pos: + dut._log.info(f"🔄 Frame sync at V={v_count} - VDMA resetting for next frame") + elif v_count == 0: + dut._log.info("🎯 Frame 2 visible area starts - testing improved VGA timing!") + elif v_count < 5: # Debug first few rows + dut._log.info(f"📍 Early visible row V={v_count}") + + # Debug pixel mismatch at the specific failing positions + if (h_count <= 1 and v_count == 3) or (h_count == 0 and v_count <= 4): + actual_red = int(dut.vga_out.o_Red.value) + actual_green = int(dut.vga_out.o_Green.value) + actual_blue = int(dut.vga_out.o_Blue.value) + exp_red, exp_green, exp_blue = calculate_pixel_rgb(v_count, h_count) + dut._log.info(f"🔍 H{h_count}V{v_count} debug: actual RGB=({actual_red},{actual_green},{actual_blue}) expected=({exp_red},{exp_green},{exp_blue})") + dut._log.info(f" Buffer state: active={int(dut.vga_out.r_Active_Buffer.value)}, fill_addr={int(dut.vga_out.r_Fill_Addr.value)}") + dut._log.info(f" VDMA: tready={int(dut.vga_out.s_axis_tready.value)}, tvalid={int(dut.s_axis_tvalid.value)}, tdata=0x{int(dut.s_axis_tdata.value):04X}") + dut._log.info(f" Frame sync: {int(dut.vga_out.o_mm2s_fsync.value)}") + + # Validate visible pixels + if h_count < VISIBLE_H and v_count < VISIBLE_V: + actual_red = int(dut.vga_out.o_Red.value) + actual_green = int(dut.vga_out.o_Green.value) + actual_blue = int(dut.vga_out.o_Blue.value) + + # Capture VGA output data + vga_output_data.append((cycle, h_count, v_count, actual_red, actual_green, actual_blue)) + + exp_red, exp_green, exp_blue = calculate_pixel_rgb(v_count, h_count) + + # Skip assertions on first 2 rows to collect more data + # if v_count >= 2: + assert actual_red == exp_red, f"Red mismatch at H{h_count}V{v_count}: expected {exp_red}, got {actual_red}" + assert actual_green == exp_green, f"Green mismatch at H{h_count}V{v_count}: expected {exp_green}, got {actual_green}" + assert actual_blue == exp_blue, f"Blue mismatch at H{h_count}V{v_count}: expected {exp_blue}, got {actual_blue}" + + elif h_count >= VISIBLE_H or v_count >= VISIBLE_V: + # RGB should be black during blanking + assert int(dut.vga_out.o_Red.value) == 0, f"Red not black during blanking at H{h_count}V{v_count}" + assert int(dut.vga_out.o_Green.value) == 0, f"Green not black during blanking at H{h_count}V{v_count}" + assert int(dut.vga_out.o_Blue.value) == 0, f"Blue not black during blanking at H{h_count}V{v_count}" + + # VGA counters should only update when clock_count == 3 + if clock_count == 3: + prev_h = h_count + prev_v = v_count + await RisingEdge(dut.i_Clock) + new_h = int(dut.vga_out.r_H_Counter.value) + new_v = int(dut.vga_out.r_V_Counter.value) + + # Verify counters incremented correctly + if prev_h == TOTAL_H - 1: + assert new_h == 0, f"H counter should wrap: {prev_h} -> {new_h}" + if prev_v == TOTAL_V - 1: + assert new_v == 0, f"V counter should wrap: {prev_v} -> {new_v}" + else: + assert new_v == prev_v + 1, f"V counter should increment: {prev_v} -> {new_v}" + else: + assert new_h == prev_h + 1, f"H counter should increment: {prev_h} -> {new_h}" + assert new_v == prev_v, f"V counter should stay same: {prev_v} -> {new_v}" + else: + await RisingEdge(dut.i_Clock) + + finally: + # Save captured data to files for comparison, even if test fails + save_pixel_data(vga_output_data, "vga_output_data.txt") + save_vdma_data(vdma_input_data, "vdma_input_data.txt") + + dut._log.info(f"Test completed. Captured {len(vga_output_data)} VGA pixels and {len(vdma_input_data)} VDMA pixels") \ No newline at end of file diff --git a/tests/vga/unit_tests/vga_unit_tests_harness.v b/tests/vga/unit_tests/vga_unit_tests_harness.v index 088f05e..5034091 100644 --- a/tests/vga/unit_tests/vga_unit_tests_harness.v +++ b/tests/vga/unit_tests/vga_unit_tests_harness.v @@ -3,21 +3,14 @@ module vga_unit_tests_harness (); wire i_Clock; + reg i_Reset; reg [15:0] s_axis_tdata; reg s_axis_tvalid; wire s_axis_tready; - /* Mock VDMA input interface */ initial begin - s_axis_tdata = 16'h0000; - s_axis_tvalid = 1'b0; - #100; - s_axis_tvalid = 1'b1; - repeat (640 * 480) begin - @(posedge i_Clock); - s_axis_tdata = s_axis_tdata + 16'h0001; - end + i_Reset = 1'b1; s_axis_tvalid = 1'b0; end @@ -33,6 +26,7 @@ module vga_unit_tests_harness (); .BITS_PER_COLOR_CHANNEL(4) ) vga_out ( .i_Clock(i_Clock), + .i_Reset(i_Reset), .s_axis_tdata(s_axis_tdata), .s_axis_tvalid(s_axis_tvalid), .s_axis_tready(s_axis_tready), diff --git a/tests/vga/utils.py b/tests/vga/utils.py index fcd5b36..e69de29 100644 --- a/tests/vga/utils.py +++ b/tests/vga/utils.py @@ -1,93 +0,0 @@ -from cocotb.triggers import ClockCycles, RisingEdge - -from vga.constants import CLOCKS_PER_PIXEL, CLOCKS_PER_LINE, TOTAL_H, TOTAL_V - - -async def wait_pixels(dut, num_pixels): - """Wait for the specified number of pixel periods.""" - await ClockCycles(dut.i_Clock, num_pixels * CLOCKS_PER_PIXEL) - - -async def wait_lines(dut, num_lines): - """Wait for the specified number of complete lines.""" - await ClockCycles(dut.i_Clock, num_lines * CLOCKS_PER_LINE) - - -async def wait_for_frame_start(dut): - """Wait until we're at the start of a frame (position near 0,0). - - For tests that need to start from a known position. - """ - # Wait a couple cycles to let signals settle at simulation start - await ClockCycles(dut.i_Clock, 2) - - # Check if we're already at or very near frame start (first 10 pixels of line 0) - h, v = get_current_position(dut) - if v == 0 and h < 10: - # Already at frame start, just return - return True - - # Need to wait for next frame - calculate how many pixels - pixels_remaining_in_line = TOTAL_H - h - lines_remaining = TOTAL_V - v - 1 # -1 because current line is partial - - # Total pixels to next frame start - pixels_to_frame_start = pixels_remaining_in_line + (lines_remaining * TOTAL_H) - - # Jump ahead to frame start - if pixels_to_frame_start > 5: - await wait_pixels(dut, pixels_to_frame_start - 2) - - # Poll for exact frame start (when counters roll over to 0,0) - for _ in range(50): - await RisingEdge(dut.i_Clock) - h, v = get_current_position(dut) - if v == 0 and h < 5: - return True - - raise TimeoutError("wait_for_frame_start: failed to find frame start") - - -async def wait_for_hsync_pulse(dut): - """Wait for horizontal sync to go low (active).""" - # If already in hsync, wait for it to end first - while dut.o_Horizontal_Sync.value == 0: - await RisingEdge(dut.i_Clock) - - # Now wait for next hsync - for _ in range(TOTAL_H * CLOCKS_PER_PIXEL + 100): - await RisingEdge(dut.i_Clock) - if dut.o_Horizontal_Sync.value == 0: - return True - - raise TimeoutError("wait_for_hsync_pulse timed out") - - -async def wait_for_vsync_pulse(dut): - """Wait for vertical sync to go low (active).""" - # If already in vsync, wait for it to end first - while dut.o_Vertical_Sync.value == 0: - await RisingEdge(dut.i_Clock) - - # Now wait for next vsync - could be up to a full frame - for _ in range(TOTAL_H * TOTAL_V * CLOCKS_PER_PIXEL + 100): - await RisingEdge(dut.i_Clock) - if dut.o_Vertical_Sync.value == 0: - return True - - raise TimeoutError("wait_for_vsync_pulse timed out") - - -def get_current_position(dut): - """Get current H and V counter positions from internal registers.""" - h_counter = dut.vga_out.r_H_Counter.value.integer - v_counter = dut.vga_out.r_V_Counter.value.integer - return h_counter, v_counter - - -def extract_rgb(dut): - """Extract R, G, B values from VGA outputs.""" - r = dut.o_Red.value.integer - g = dut.o_Green.value.integer - b = dut.o_Blue.value.integer - return r, g, b From 370de83ac5e95ae3a084fa7d9d6f80d5e5de30c9 Mon Sep 17 00:00:00 2001 From: Ema Dervisevic Date: Wed, 28 Jan 2026 18:55:13 +0100 Subject: [PATCH 3/8] save working version --- hdl/vga_out/vga_out.v | 128 ++-- tests/Makefile | 3 +- tests/test_output/vdma_input_data.txt | 641 ------------------ tests/test_output/vga_output_data.txt | 6 - .../vga/unit_tests/test_vga_comprehensive.py | 195 +++--- tests/vga/unit_tests/test_vga_pattern.py | 272 -------- tests/vga/utils.py | 49 ++ 7 files changed, 247 insertions(+), 1047 deletions(-) delete mode 100644 tests/test_output/vdma_input_data.txt delete mode 100644 tests/test_output/vga_output_data.txt delete mode 100644 tests/vga/unit_tests/test_vga_pattern.py diff --git a/hdl/vga_out/vga_out.v b/hdl/vga_out/vga_out.v index d63145a..15c5a93 100644 --- a/hdl/vga_out/vga_out.v +++ b/hdl/vga_out/vga_out.v @@ -41,14 +41,15 @@ module vga_out #( reg [15:0] r_H_Counter = 0; reg [15:0] r_V_Counter = 0; - // Dual row buffers for pixel data reg [15:0] r_Row_Buffer_0 [0:VISIBLE_H-1]; reg [15:0] r_Row_Buffer_1 [0:VISIBLE_H-1]; - // Buffer control signals - reg r_Active_Buffer = 0; // 0 = reading from buffer_0, 1 = reading from buffer_1 - reg [9:0] r_Fill_Addr = 0; // Address for filling buffer (0 to VISIBLE_H-1) - reg r_Blanking_Prefetch_Done = 0; + reg r_Display_Active = 1'b0; + reg r_Display_Buffer = 1'b0; + reg r_Load_Buffer = 1'b0; + reg [1:0] r_Row_Count = 0; + reg [9:0] r_Load_Col = 0; + reg r_Loading = 1'b0; localparam [1:0] STATE_VISIBLE = 0; localparam [1:0] STATE_FRONT_PORCH = 1; @@ -73,15 +74,54 @@ module vga_out #( else r_V_State = STATE_BACK_PORCH; end + wire w_Pixel_Clock_Tick = (r_Clock_Counter == 3); + + wire w_Fsync_Level = (r_H_Counter == 0) && (r_V_Counter == VISIBLE_V); + + assign s_axis_tready = (r_Row_Count < 2) && !w_Fsync_Level; + + wire w_Load_Handshake = s_axis_tvalid && s_axis_tready; + wire w_Load_Complete = w_Load_Handshake && (r_Load_Col == VISIBLE_H - 1); + + wire w_Row_Consume = w_Pixel_Clock_Tick && (r_V_Counter < VISIBLE_V) && + (r_H_Counter == VISIBLE_H - 1) && r_Display_Active && (r_Row_Count > 0); + + wire w_Line_Start = w_Pixel_Clock_Tick && (r_H_Counter == TOTAL_H - 1); + + wire w_Fsync_Event = w_Pixel_Clock_Tick && (r_H_Counter == 0) && (r_V_Counter == VISIBLE_V); + always @(posedge i_Clock or posedge i_Reset) begin if (i_Reset) begin r_Clock_Counter <= 0; r_H_Counter <= 0; r_V_Counter <= VISIBLE_V; // Start vertical counter in blanking period + r_Display_Active <= 1'b0; + r_Display_Buffer <= 1'b0; + r_Load_Buffer <= 1'b0; + r_Row_Count <= 0; + r_Load_Col <= 0; + r_Loading <= 1'b0; end else begin r_Clock_Counter <= r_Clock_Counter + 1; - if (r_Clock_Counter == 3) begin // VGA pixel clock tick every 4th cycle + // AXI-Stream row loading logic + if (w_Load_Handshake) begin + r_Loading <= 1'b1; + if (r_Load_Buffer == 1'b0) + r_Row_Buffer_0[r_Load_Col] <= s_axis_tdata; + else + r_Row_Buffer_1[r_Load_Col] <= s_axis_tdata; + + if (r_Load_Col == VISIBLE_H - 1) begin + r_Load_Col <= 0; + r_Loading <= 1'b0; + r_Load_Buffer <= ~r_Load_Buffer; + end else begin + r_Load_Col <= r_Load_Col + 1; + end + end + + if (w_Pixel_Clock_Tick) begin // VGA pixel clock tick every 4th cycle if(r_H_Counter == TOTAL_H - 1) begin // If we've reached the end of the horizontal line r_H_Counter <= 0; if (r_V_Counter == TOTAL_V - 1) // If we've reached the end of the vertical frame @@ -92,57 +132,55 @@ module vga_out #( end else begin r_H_Counter <= r_H_Counter + 1; end - end - end - end - assign s_axis_tready = !i_Reset && r_V_State == STATE_VISIBLE ? r_Fill_Addr < VISIBLE_H - 1 : !r_Blanking_Prefetch_Done; - - wire w_Frame_Sync_Position = (r_H_Counter == 0) && (r_V_Counter == VISIBLE_V); - assign o_mm2s_fsync = w_Frame_Sync_Position; + // Start-of-line handling (after wrap) to activate display buffer + if (w_Line_Start) begin + if ((r_V_Counter == TOTAL_V - 1) ? 1'b1 : ((r_V_Counter + 1) < VISIBLE_V)) begin + r_Display_Active <= (r_Row_Count > 0); + end else begin + r_Display_Active <= 1'b0; + end + end - // Buffer filling and switching logic - always @(posedge i_Clock or posedge i_Reset) begin - if (i_Reset) begin - r_Fill_Addr <= 0; - r_Active_Buffer <= 0; - r_Blanking_Prefetch_Done <= 0; - end else begin - if (s_axis_tready && s_axis_tvalid) begin - if (r_Active_Buffer) begin - r_Row_Buffer_0[r_Fill_Addr] <= s_axis_tdata; - end else begin - r_Row_Buffer_1[r_Fill_Addr] <= s_axis_tdata; + // End-of-visible-line consumption + if (w_Row_Consume) begin + r_Display_Buffer <= ~r_Display_Buffer; + r_Display_Active <= 1'b0; end - if (r_Fill_Addr < VISIBLE_H - 1) begin - r_Fill_Addr <= r_Fill_Addr + 1; - end else if (r_Fill_Addr == VISIBLE_H - 1) begin - r_Fill_Addr <= VISIBLE_H; + // Frame sync: reset row buffering at start of vertical back porch + if (w_Fsync_Event) begin + r_Display_Active <= 1'b0; + r_Display_Buffer <= 1'b0; + r_Load_Buffer <= 1'b0; + r_Load_Col <= 0; + r_Loading <= 1'b0; end end - // Switch buffers and start loading new row soon as we've drawn the visible area of the line - if (r_H_Counter == VISIBLE_H && r_V_State == STATE_VISIBLE) begin - r_Active_Buffer <= ~r_Active_Buffer; - r_Fill_Addr <= 0; - end else if(r_Fill_Addr == VISIBLE_H - 1 && r_V_State != STATE_VISIBLE && !r_Blanking_Prefetch_Done) begin - r_Active_Buffer <= ~r_Active_Buffer; - r_Fill_Addr <= 0; - r_Blanking_Prefetch_Done <= 1; + // Row count update (handles simultaneous load/consume) + if (w_Fsync_Event) begin + r_Row_Count <= 0; + end else begin + case ({w_Load_Complete, w_Row_Consume}) + 2'b10: r_Row_Count <= r_Row_Count + 1; + 2'b01: r_Row_Count <= r_Row_Count - 1; + default: r_Row_Count <= r_Row_Count; + endcase end end end - // VGA reads from opposite buffer that VDMA is filling (proper double buffering) - wire [15:0] w_Current_Pixel = w_Visible ? - (r_Active_Buffer ? r_Row_Buffer_1[r_H_Counter[9:0]] : r_Row_Buffer_0[r_H_Counter[9:0]]) : 16'h0000; + assign o_mm2s_fsync = w_Fsync_Level; - assign o_Red = w_Visible ? w_Current_Pixel[15:12] : {BITS_PER_COLOR_CHANNEL{1'b0}}; - assign o_Green = w_Visible ? w_Current_Pixel[10:7] : {BITS_PER_COLOR_CHANNEL{1'b0}}; - assign o_Blue = w_Visible ? w_Current_Pixel[4:1] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + wire [15:0] w_Current_Pixel = (r_H_Counter < VISIBLE_H) + ? ((r_Display_Buffer == 1'b0) ? r_Row_Buffer_0[r_H_Counter[9:0]] : r_Row_Buffer_1[r_H_Counter[9:0]]) + : 16'h0000; + assign o_Red = (w_Visible && r_Display_Active) ? w_Current_Pixel[15:12] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Green = (w_Visible && r_Display_Active) ? w_Current_Pixel[10:7] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Blue = (w_Visible && r_Display_Active) ? w_Current_Pixel[4:1] : {BITS_PER_COLOR_CHANNEL{1'b0}}; - assign o_Horizontal_Sync = ~(r_H_State == STATE_SYNC); // Invert for active low - assign o_Vertical_Sync = ~(r_V_State == STATE_SYNC); // Invert for active low + assign o_Horizontal_Sync = ~(r_H_State == STATE_SYNC); + assign o_Vertical_Sync = ~(r_V_State == STATE_SYNC); endmodule diff --git a/tests/Makefile b/tests/Makefile index 9a3b595..7f7a1a4 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -74,8 +74,7 @@ CPU_INTEGRATION_TESTS_MODULE = "cpu.integration_tests.test_instruction_fetch, \ cpu.integration_tests.test_debug_write_pc" VGA_UNIT_TESTS_TOPLEVEL = vga_unit_tests_harness -VGA_UNIT_TESTS_MODULE = "vga.unit_tests.test_vga_comprehensive, \ - vga.unit_tests.test_vga_pattern" +VGA_UNIT_TESTS_MODULE = "vga.unit_tests.test_vga_comprehensive" TEST_TYPE ?= unit diff --git a/tests/test_output/vdma_input_data.txt b/tests/test_output/vdma_input_data.txt deleted file mode 100644 index 683be95..0000000 --- a/tests/test_output/vdma_input_data.txt +++ /dev/null @@ -1,641 +0,0 @@ -# Format: cycle,row,col,pixel_data_hex,red,green,blue,blanking_prefetch_done,fill_addr,active_buffer,tready -1,0,0,0000,0,0,0,0,0,0,1 -2,0,1,1002,1,0,1,0,1,0,1 -3,0,2,2004,2,0,2,0,2,0,1 -4,0,3,3006,3,0,3,0,3,0,1 -5,0,4,4008,4,0,4,0,4,0,1 -6,0,5,500A,5,0,5,0,5,0,1 -7,0,6,600C,6,0,6,0,6,0,1 -8,0,7,700E,7,0,7,0,7,0,1 -9,0,8,8010,8,0,8,0,8,0,1 -10,0,9,9012,9,0,9,0,9,0,1 -11,0,10,A014,10,0,10,0,10,0,1 -12,0,11,B016,11,0,11,0,11,0,1 -13,0,12,C018,12,0,12,0,12,0,1 -14,0,13,D01A,13,0,13,0,13,0,1 -15,0,14,E01C,14,0,14,0,14,0,1 -16,0,15,F01E,15,0,15,0,15,0,1 -17,0,16,0000,0,0,0,0,16,0,1 -18,0,17,1002,1,0,1,0,17,0,1 -19,0,18,2004,2,0,2,0,18,0,1 -20,0,19,3006,3,0,3,0,19,0,1 -21,0,20,4008,4,0,4,0,20,0,1 -22,0,21,500A,5,0,5,0,21,0,1 -23,0,22,600C,6,0,6,0,22,0,1 -24,0,23,700E,7,0,7,0,23,0,1 -25,0,24,8010,8,0,8,0,24,0,1 -26,0,25,9012,9,0,9,0,25,0,1 -27,0,26,A014,10,0,10,0,26,0,1 -28,0,27,B016,11,0,11,0,27,0,1 -29,0,28,C018,12,0,12,0,28,0,1 -30,0,29,D01A,13,0,13,0,29,0,1 -31,0,30,E01C,14,0,14,0,30,0,1 -32,0,31,F01E,15,0,15,0,31,0,1 -33,0,32,0000,0,0,0,0,32,0,1 -34,0,33,1002,1,0,1,0,33,0,1 -35,0,34,2004,2,0,2,0,34,0,1 -36,0,35,3006,3,0,3,0,35,0,1 -37,0,36,4008,4,0,4,0,36,0,1 -38,0,37,500A,5,0,5,0,37,0,1 -39,0,38,600C,6,0,6,0,38,0,1 -40,0,39,700E,7,0,7,0,39,0,1 -41,0,40,8010,8,0,8,0,40,0,1 -42,0,41,9012,9,0,9,0,41,0,1 -43,0,42,A014,10,0,10,0,42,0,1 -44,0,43,B016,11,0,11,0,43,0,1 -45,0,44,C018,12,0,12,0,44,0,1 -46,0,45,D01A,13,0,13,0,45,0,1 -47,0,46,E01C,14,0,14,0,46,0,1 -48,0,47,F01E,15,0,15,0,47,0,1 -49,0,48,0000,0,0,0,0,48,0,1 -50,0,49,1002,1,0,1,0,49,0,1 -51,0,50,2004,2,0,2,0,50,0,1 -52,0,51,3006,3,0,3,0,51,0,1 -53,0,52,4008,4,0,4,0,52,0,1 -54,0,53,500A,5,0,5,0,53,0,1 -55,0,54,600C,6,0,6,0,54,0,1 -56,0,55,700E,7,0,7,0,55,0,1 -57,0,56,8010,8,0,8,0,56,0,1 -58,0,57,9012,9,0,9,0,57,0,1 -59,0,58,A014,10,0,10,0,58,0,1 -60,0,59,B016,11,0,11,0,59,0,1 -61,0,60,C018,12,0,12,0,60,0,1 -62,0,61,D01A,13,0,13,0,61,0,1 -63,0,62,E01C,14,0,14,0,62,0,1 -64,0,63,F01E,15,0,15,0,63,0,1 -65,0,64,0000,0,0,0,0,64,0,1 -66,0,65,1002,1,0,1,0,65,0,1 -67,0,66,2004,2,0,2,0,66,0,1 -68,0,67,3006,3,0,3,0,67,0,1 -69,0,68,4008,4,0,4,0,68,0,1 -70,0,69,500A,5,0,5,0,69,0,1 -71,0,70,600C,6,0,6,0,70,0,1 -72,0,71,700E,7,0,7,0,71,0,1 -73,0,72,8010,8,0,8,0,72,0,1 -74,0,73,9012,9,0,9,0,73,0,1 -75,0,74,A014,10,0,10,0,74,0,1 -76,0,75,B016,11,0,11,0,75,0,1 -77,0,76,C018,12,0,12,0,76,0,1 -78,0,77,D01A,13,0,13,0,77,0,1 -79,0,78,E01C,14,0,14,0,78,0,1 -80,0,79,F01E,15,0,15,0,79,0,1 -81,0,80,0000,0,0,0,0,80,0,1 -82,0,81,1002,1,0,1,0,81,0,1 -83,0,82,2004,2,0,2,0,82,0,1 -84,0,83,3006,3,0,3,0,83,0,1 -85,0,84,4008,4,0,4,0,84,0,1 -86,0,85,500A,5,0,5,0,85,0,1 -87,0,86,600C,6,0,6,0,86,0,1 -88,0,87,700E,7,0,7,0,87,0,1 -89,0,88,8010,8,0,8,0,88,0,1 -90,0,89,9012,9,0,9,0,89,0,1 -91,0,90,A014,10,0,10,0,90,0,1 -92,0,91,B016,11,0,11,0,91,0,1 -93,0,92,C018,12,0,12,0,92,0,1 -94,0,93,D01A,13,0,13,0,93,0,1 -95,0,94,E01C,14,0,14,0,94,0,1 -96,0,95,F01E,15,0,15,0,95,0,1 -97,0,96,0000,0,0,0,0,96,0,1 -98,0,97,1002,1,0,1,0,97,0,1 -99,0,98,2004,2,0,2,0,98,0,1 -100,0,99,3006,3,0,3,0,99,0,1 -101,0,100,4008,4,0,4,0,100,0,1 -102,0,101,500A,5,0,5,0,101,0,1 -103,0,102,600C,6,0,6,0,102,0,1 -104,0,103,700E,7,0,7,0,103,0,1 -105,0,104,8010,8,0,8,0,104,0,1 -106,0,105,9012,9,0,9,0,105,0,1 -107,0,106,A014,10,0,10,0,106,0,1 -108,0,107,B016,11,0,11,0,107,0,1 -109,0,108,C018,12,0,12,0,108,0,1 -110,0,109,D01A,13,0,13,0,109,0,1 -111,0,110,E01C,14,0,14,0,110,0,1 -112,0,111,F01E,15,0,15,0,111,0,1 -113,0,112,0000,0,0,0,0,112,0,1 -114,0,113,1002,1,0,1,0,113,0,1 -115,0,114,2004,2,0,2,0,114,0,1 -116,0,115,3006,3,0,3,0,115,0,1 -117,0,116,4008,4,0,4,0,116,0,1 -118,0,117,500A,5,0,5,0,117,0,1 -119,0,118,600C,6,0,6,0,118,0,1 -120,0,119,700E,7,0,7,0,119,0,1 -121,0,120,8010,8,0,8,0,120,0,1 -122,0,121,9012,9,0,9,0,121,0,1 -123,0,122,A014,10,0,10,0,122,0,1 -124,0,123,B016,11,0,11,0,123,0,1 -125,0,124,C018,12,0,12,0,124,0,1 -126,0,125,D01A,13,0,13,0,125,0,1 -127,0,126,E01C,14,0,14,0,126,0,1 -128,0,127,F01E,15,0,15,0,127,0,1 -129,0,128,0000,0,0,0,0,128,0,1 -130,0,129,1002,1,0,1,0,129,0,1 -131,0,130,2004,2,0,2,0,130,0,1 -132,0,131,3006,3,0,3,0,131,0,1 -133,0,132,4008,4,0,4,0,132,0,1 -134,0,133,500A,5,0,5,0,133,0,1 -135,0,134,600C,6,0,6,0,134,0,1 -136,0,135,700E,7,0,7,0,135,0,1 -137,0,136,8010,8,0,8,0,136,0,1 -138,0,137,9012,9,0,9,0,137,0,1 -139,0,138,A014,10,0,10,0,138,0,1 -140,0,139,B016,11,0,11,0,139,0,1 -141,0,140,C018,12,0,12,0,140,0,1 -142,0,141,D01A,13,0,13,0,141,0,1 -143,0,142,E01C,14,0,14,0,142,0,1 -144,0,143,F01E,15,0,15,0,143,0,1 -145,0,144,0000,0,0,0,0,144,0,1 -146,0,145,1002,1,0,1,0,145,0,1 -147,0,146,2004,2,0,2,0,146,0,1 -148,0,147,3006,3,0,3,0,147,0,1 -149,0,148,4008,4,0,4,0,148,0,1 -150,0,149,500A,5,0,5,0,149,0,1 -151,0,150,600C,6,0,6,0,150,0,1 -152,0,151,700E,7,0,7,0,151,0,1 -153,0,152,8010,8,0,8,0,152,0,1 -154,0,153,9012,9,0,9,0,153,0,1 -155,0,154,A014,10,0,10,0,154,0,1 -156,0,155,B016,11,0,11,0,155,0,1 -157,0,156,C018,12,0,12,0,156,0,1 -158,0,157,D01A,13,0,13,0,157,0,1 -159,0,158,E01C,14,0,14,0,158,0,1 -160,0,159,F01E,15,0,15,0,159,0,1 -161,0,160,0000,0,0,0,0,160,0,1 -162,0,161,1002,1,0,1,0,161,0,1 -163,0,162,2004,2,0,2,0,162,0,1 -164,0,163,3006,3,0,3,0,163,0,1 -165,0,164,4008,4,0,4,0,164,0,1 -166,0,165,500A,5,0,5,0,165,0,1 -167,0,166,600C,6,0,6,0,166,0,1 -168,0,167,700E,7,0,7,0,167,0,1 -169,0,168,8010,8,0,8,0,168,0,1 -170,0,169,9012,9,0,9,0,169,0,1 -171,0,170,A014,10,0,10,0,170,0,1 -172,0,171,B016,11,0,11,0,171,0,1 -173,0,172,C018,12,0,12,0,172,0,1 -174,0,173,D01A,13,0,13,0,173,0,1 -175,0,174,E01C,14,0,14,0,174,0,1 -176,0,175,F01E,15,0,15,0,175,0,1 -177,0,176,0000,0,0,0,0,176,0,1 -178,0,177,1002,1,0,1,0,177,0,1 -179,0,178,2004,2,0,2,0,178,0,1 -180,0,179,3006,3,0,3,0,179,0,1 -181,0,180,4008,4,0,4,0,180,0,1 -182,0,181,500A,5,0,5,0,181,0,1 -183,0,182,600C,6,0,6,0,182,0,1 -184,0,183,700E,7,0,7,0,183,0,1 -185,0,184,8010,8,0,8,0,184,0,1 -186,0,185,9012,9,0,9,0,185,0,1 -187,0,186,A014,10,0,10,0,186,0,1 -188,0,187,B016,11,0,11,0,187,0,1 -189,0,188,C018,12,0,12,0,188,0,1 -190,0,189,D01A,13,0,13,0,189,0,1 -191,0,190,E01C,14,0,14,0,190,0,1 -192,0,191,F01E,15,0,15,0,191,0,1 -193,0,192,0000,0,0,0,0,192,0,1 -194,0,193,1002,1,0,1,0,193,0,1 -195,0,194,2004,2,0,2,0,194,0,1 -196,0,195,3006,3,0,3,0,195,0,1 -197,0,196,4008,4,0,4,0,196,0,1 -198,0,197,500A,5,0,5,0,197,0,1 -199,0,198,600C,6,0,6,0,198,0,1 -200,0,199,700E,7,0,7,0,199,0,1 -201,0,200,8010,8,0,8,0,200,0,1 -202,0,201,9012,9,0,9,0,201,0,1 -203,0,202,A014,10,0,10,0,202,0,1 -204,0,203,B016,11,0,11,0,203,0,1 -205,0,204,C018,12,0,12,0,204,0,1 -206,0,205,D01A,13,0,13,0,205,0,1 -207,0,206,E01C,14,0,14,0,206,0,1 -208,0,207,F01E,15,0,15,0,207,0,1 -209,0,208,0000,0,0,0,0,208,0,1 -210,0,209,1002,1,0,1,0,209,0,1 -211,0,210,2004,2,0,2,0,210,0,1 -212,0,211,3006,3,0,3,0,211,0,1 -213,0,212,4008,4,0,4,0,212,0,1 -214,0,213,500A,5,0,5,0,213,0,1 -215,0,214,600C,6,0,6,0,214,0,1 -216,0,215,700E,7,0,7,0,215,0,1 -217,0,216,8010,8,0,8,0,216,0,1 -218,0,217,9012,9,0,9,0,217,0,1 -219,0,218,A014,10,0,10,0,218,0,1 -220,0,219,B016,11,0,11,0,219,0,1 -221,0,220,C018,12,0,12,0,220,0,1 -222,0,221,D01A,13,0,13,0,221,0,1 -223,0,222,E01C,14,0,14,0,222,0,1 -224,0,223,F01E,15,0,15,0,223,0,1 -225,0,224,0000,0,0,0,0,224,0,1 -226,0,225,1002,1,0,1,0,225,0,1 -227,0,226,2004,2,0,2,0,226,0,1 -228,0,227,3006,3,0,3,0,227,0,1 -229,0,228,4008,4,0,4,0,228,0,1 -230,0,229,500A,5,0,5,0,229,0,1 -231,0,230,600C,6,0,6,0,230,0,1 -232,0,231,700E,7,0,7,0,231,0,1 -233,0,232,8010,8,0,8,0,232,0,1 -234,0,233,9012,9,0,9,0,233,0,1 -235,0,234,A014,10,0,10,0,234,0,1 -236,0,235,B016,11,0,11,0,235,0,1 -237,0,236,C018,12,0,12,0,236,0,1 -238,0,237,D01A,13,0,13,0,237,0,1 -239,0,238,E01C,14,0,14,0,238,0,1 -240,0,239,F01E,15,0,15,0,239,0,1 -241,0,240,0000,0,0,0,0,240,0,1 -242,0,241,1002,1,0,1,0,241,0,1 -243,0,242,2004,2,0,2,0,242,0,1 -244,0,243,3006,3,0,3,0,243,0,1 -245,0,244,4008,4,0,4,0,244,0,1 -246,0,245,500A,5,0,5,0,245,0,1 -247,0,246,600C,6,0,6,0,246,0,1 -248,0,247,700E,7,0,7,0,247,0,1 -249,0,248,8010,8,0,8,0,248,0,1 -250,0,249,9012,9,0,9,0,249,0,1 -251,0,250,A014,10,0,10,0,250,0,1 -252,0,251,B016,11,0,11,0,251,0,1 -253,0,252,C018,12,0,12,0,252,0,1 -254,0,253,D01A,13,0,13,0,253,0,1 -255,0,254,E01C,14,0,14,0,254,0,1 -256,0,255,F01E,15,0,15,0,255,0,1 -257,0,256,0000,0,0,0,0,256,0,1 -258,0,257,1002,1,0,1,0,257,0,1 -259,0,258,2004,2,0,2,0,258,0,1 -260,0,259,3006,3,0,3,0,259,0,1 -261,0,260,4008,4,0,4,0,260,0,1 -262,0,261,500A,5,0,5,0,261,0,1 -263,0,262,600C,6,0,6,0,262,0,1 -264,0,263,700E,7,0,7,0,263,0,1 -265,0,264,8010,8,0,8,0,264,0,1 -266,0,265,9012,9,0,9,0,265,0,1 -267,0,266,A014,10,0,10,0,266,0,1 -268,0,267,B016,11,0,11,0,267,0,1 -269,0,268,C018,12,0,12,0,268,0,1 -270,0,269,D01A,13,0,13,0,269,0,1 -271,0,270,E01C,14,0,14,0,270,0,1 -272,0,271,F01E,15,0,15,0,271,0,1 -273,0,272,0000,0,0,0,0,272,0,1 -274,0,273,1002,1,0,1,0,273,0,1 -275,0,274,2004,2,0,2,0,274,0,1 -276,0,275,3006,3,0,3,0,275,0,1 -277,0,276,4008,4,0,4,0,276,0,1 -278,0,277,500A,5,0,5,0,277,0,1 -279,0,278,600C,6,0,6,0,278,0,1 -280,0,279,700E,7,0,7,0,279,0,1 -281,0,280,8010,8,0,8,0,280,0,1 -282,0,281,9012,9,0,9,0,281,0,1 -283,0,282,A014,10,0,10,0,282,0,1 -284,0,283,B016,11,0,11,0,283,0,1 -285,0,284,C018,12,0,12,0,284,0,1 -286,0,285,D01A,13,0,13,0,285,0,1 -287,0,286,E01C,14,0,14,0,286,0,1 -288,0,287,F01E,15,0,15,0,287,0,1 -289,0,288,0000,0,0,0,0,288,0,1 -290,0,289,1002,1,0,1,0,289,0,1 -291,0,290,2004,2,0,2,0,290,0,1 -292,0,291,3006,3,0,3,0,291,0,1 -293,0,292,4008,4,0,4,0,292,0,1 -294,0,293,500A,5,0,5,0,293,0,1 -295,0,294,600C,6,0,6,0,294,0,1 -296,0,295,700E,7,0,7,0,295,0,1 -297,0,296,8010,8,0,8,0,296,0,1 -298,0,297,9012,9,0,9,0,297,0,1 -299,0,298,A014,10,0,10,0,298,0,1 -300,0,299,B016,11,0,11,0,299,0,1 -301,0,300,C018,12,0,12,0,300,0,1 -302,0,301,D01A,13,0,13,0,301,0,1 -303,0,302,E01C,14,0,14,0,302,0,1 -304,0,303,F01E,15,0,15,0,303,0,1 -305,0,304,0000,0,0,0,0,304,0,1 -306,0,305,1002,1,0,1,0,305,0,1 -307,0,306,2004,2,0,2,0,306,0,1 -308,0,307,3006,3,0,3,0,307,0,1 -309,0,308,4008,4,0,4,0,308,0,1 -310,0,309,500A,5,0,5,0,309,0,1 -311,0,310,600C,6,0,6,0,310,0,1 -312,0,311,700E,7,0,7,0,311,0,1 -313,0,312,8010,8,0,8,0,312,0,1 -314,0,313,9012,9,0,9,0,313,0,1 -315,0,314,A014,10,0,10,0,314,0,1 -316,0,315,B016,11,0,11,0,315,0,1 -317,0,316,C018,12,0,12,0,316,0,1 -318,0,317,D01A,13,0,13,0,317,0,1 -319,0,318,E01C,14,0,14,0,318,0,1 -320,0,319,F01E,15,0,15,0,319,0,1 -321,0,320,0000,0,0,0,0,320,0,1 -322,0,321,1002,1,0,1,0,321,0,1 -323,0,322,2004,2,0,2,0,322,0,1 -324,0,323,3006,3,0,3,0,323,0,1 -325,0,324,4008,4,0,4,0,324,0,1 -326,0,325,500A,5,0,5,0,325,0,1 -327,0,326,600C,6,0,6,0,326,0,1 -328,0,327,700E,7,0,7,0,327,0,1 -329,0,328,8010,8,0,8,0,328,0,1 -330,0,329,9012,9,0,9,0,329,0,1 -331,0,330,A014,10,0,10,0,330,0,1 -332,0,331,B016,11,0,11,0,331,0,1 -333,0,332,C018,12,0,12,0,332,0,1 -334,0,333,D01A,13,0,13,0,333,0,1 -335,0,334,E01C,14,0,14,0,334,0,1 -336,0,335,F01E,15,0,15,0,335,0,1 -337,0,336,0000,0,0,0,0,336,0,1 -338,0,337,1002,1,0,1,0,337,0,1 -339,0,338,2004,2,0,2,0,338,0,1 -340,0,339,3006,3,0,3,0,339,0,1 -341,0,340,4008,4,0,4,0,340,0,1 -342,0,341,500A,5,0,5,0,341,0,1 -343,0,342,600C,6,0,6,0,342,0,1 -344,0,343,700E,7,0,7,0,343,0,1 -345,0,344,8010,8,0,8,0,344,0,1 -346,0,345,9012,9,0,9,0,345,0,1 -347,0,346,A014,10,0,10,0,346,0,1 -348,0,347,B016,11,0,11,0,347,0,1 -349,0,348,C018,12,0,12,0,348,0,1 -350,0,349,D01A,13,0,13,0,349,0,1 -351,0,350,E01C,14,0,14,0,350,0,1 -352,0,351,F01E,15,0,15,0,351,0,1 -353,0,352,0000,0,0,0,0,352,0,1 -354,0,353,1002,1,0,1,0,353,0,1 -355,0,354,2004,2,0,2,0,354,0,1 -356,0,355,3006,3,0,3,0,355,0,1 -357,0,356,4008,4,0,4,0,356,0,1 -358,0,357,500A,5,0,5,0,357,0,1 -359,0,358,600C,6,0,6,0,358,0,1 -360,0,359,700E,7,0,7,0,359,0,1 -361,0,360,8010,8,0,8,0,360,0,1 -362,0,361,9012,9,0,9,0,361,0,1 -363,0,362,A014,10,0,10,0,362,0,1 -364,0,363,B016,11,0,11,0,363,0,1 -365,0,364,C018,12,0,12,0,364,0,1 -366,0,365,D01A,13,0,13,0,365,0,1 -367,0,366,E01C,14,0,14,0,366,0,1 -368,0,367,F01E,15,0,15,0,367,0,1 -369,0,368,0000,0,0,0,0,368,0,1 -370,0,369,1002,1,0,1,0,369,0,1 -371,0,370,2004,2,0,2,0,370,0,1 -372,0,371,3006,3,0,3,0,371,0,1 -373,0,372,4008,4,0,4,0,372,0,1 -374,0,373,500A,5,0,5,0,373,0,1 -375,0,374,600C,6,0,6,0,374,0,1 -376,0,375,700E,7,0,7,0,375,0,1 -377,0,376,8010,8,0,8,0,376,0,1 -378,0,377,9012,9,0,9,0,377,0,1 -379,0,378,A014,10,0,10,0,378,0,1 -380,0,379,B016,11,0,11,0,379,0,1 -381,0,380,C018,12,0,12,0,380,0,1 -382,0,381,D01A,13,0,13,0,381,0,1 -383,0,382,E01C,14,0,14,0,382,0,1 -384,0,383,F01E,15,0,15,0,383,0,1 -385,0,384,0000,0,0,0,0,384,0,1 -386,0,385,1002,1,0,1,0,385,0,1 -387,0,386,2004,2,0,2,0,386,0,1 -388,0,387,3006,3,0,3,0,387,0,1 -389,0,388,4008,4,0,4,0,388,0,1 -390,0,389,500A,5,0,5,0,389,0,1 -391,0,390,600C,6,0,6,0,390,0,1 -392,0,391,700E,7,0,7,0,391,0,1 -393,0,392,8010,8,0,8,0,392,0,1 -394,0,393,9012,9,0,9,0,393,0,1 -395,0,394,A014,10,0,10,0,394,0,1 -396,0,395,B016,11,0,11,0,395,0,1 -397,0,396,C018,12,0,12,0,396,0,1 -398,0,397,D01A,13,0,13,0,397,0,1 -399,0,398,E01C,14,0,14,0,398,0,1 -400,0,399,F01E,15,0,15,0,399,0,1 -401,0,400,0000,0,0,0,0,400,0,1 -402,0,401,1002,1,0,1,0,401,0,1 -403,0,402,2004,2,0,2,0,402,0,1 -404,0,403,3006,3,0,3,0,403,0,1 -405,0,404,4008,4,0,4,0,404,0,1 -406,0,405,500A,5,0,5,0,405,0,1 -407,0,406,600C,6,0,6,0,406,0,1 -408,0,407,700E,7,0,7,0,407,0,1 -409,0,408,8010,8,0,8,0,408,0,1 -410,0,409,9012,9,0,9,0,409,0,1 -411,0,410,A014,10,0,10,0,410,0,1 -412,0,411,B016,11,0,11,0,411,0,1 -413,0,412,C018,12,0,12,0,412,0,1 -414,0,413,D01A,13,0,13,0,413,0,1 -415,0,414,E01C,14,0,14,0,414,0,1 -416,0,415,F01E,15,0,15,0,415,0,1 -417,0,416,0000,0,0,0,0,416,0,1 -418,0,417,1002,1,0,1,0,417,0,1 -419,0,418,2004,2,0,2,0,418,0,1 -420,0,419,3006,3,0,3,0,419,0,1 -421,0,420,4008,4,0,4,0,420,0,1 -422,0,421,500A,5,0,5,0,421,0,1 -423,0,422,600C,6,0,6,0,422,0,1 -424,0,423,700E,7,0,7,0,423,0,1 -425,0,424,8010,8,0,8,0,424,0,1 -426,0,425,9012,9,0,9,0,425,0,1 -427,0,426,A014,10,0,10,0,426,0,1 -428,0,427,B016,11,0,11,0,427,0,1 -429,0,428,C018,12,0,12,0,428,0,1 -430,0,429,D01A,13,0,13,0,429,0,1 -431,0,430,E01C,14,0,14,0,430,0,1 -432,0,431,F01E,15,0,15,0,431,0,1 -433,0,432,0000,0,0,0,0,432,0,1 -434,0,433,1002,1,0,1,0,433,0,1 -435,0,434,2004,2,0,2,0,434,0,1 -436,0,435,3006,3,0,3,0,435,0,1 -437,0,436,4008,4,0,4,0,436,0,1 -438,0,437,500A,5,0,5,0,437,0,1 -439,0,438,600C,6,0,6,0,438,0,1 -440,0,439,700E,7,0,7,0,439,0,1 -441,0,440,8010,8,0,8,0,440,0,1 -442,0,441,9012,9,0,9,0,441,0,1 -443,0,442,A014,10,0,10,0,442,0,1 -444,0,443,B016,11,0,11,0,443,0,1 -445,0,444,C018,12,0,12,0,444,0,1 -446,0,445,D01A,13,0,13,0,445,0,1 -447,0,446,E01C,14,0,14,0,446,0,1 -448,0,447,F01E,15,0,15,0,447,0,1 -449,0,448,0000,0,0,0,0,448,0,1 -450,0,449,1002,1,0,1,0,449,0,1 -451,0,450,2004,2,0,2,0,450,0,1 -452,0,451,3006,3,0,3,0,451,0,1 -453,0,452,4008,4,0,4,0,452,0,1 -454,0,453,500A,5,0,5,0,453,0,1 -455,0,454,600C,6,0,6,0,454,0,1 -456,0,455,700E,7,0,7,0,455,0,1 -457,0,456,8010,8,0,8,0,456,0,1 -458,0,457,9012,9,0,9,0,457,0,1 -459,0,458,A014,10,0,10,0,458,0,1 -460,0,459,B016,11,0,11,0,459,0,1 -461,0,460,C018,12,0,12,0,460,0,1 -462,0,461,D01A,13,0,13,0,461,0,1 -463,0,462,E01C,14,0,14,0,462,0,1 -464,0,463,F01E,15,0,15,0,463,0,1 -465,0,464,0000,0,0,0,0,464,0,1 -466,0,465,1002,1,0,1,0,465,0,1 -467,0,466,2004,2,0,2,0,466,0,1 -468,0,467,3006,3,0,3,0,467,0,1 -469,0,468,4008,4,0,4,0,468,0,1 -470,0,469,500A,5,0,5,0,469,0,1 -471,0,470,600C,6,0,6,0,470,0,1 -472,0,471,700E,7,0,7,0,471,0,1 -473,0,472,8010,8,0,8,0,472,0,1 -474,0,473,9012,9,0,9,0,473,0,1 -475,0,474,A014,10,0,10,0,474,0,1 -476,0,475,B016,11,0,11,0,475,0,1 -477,0,476,C018,12,0,12,0,476,0,1 -478,0,477,D01A,13,0,13,0,477,0,1 -479,0,478,E01C,14,0,14,0,478,0,1 -480,0,479,F01E,15,0,15,0,479,0,1 -481,0,480,0000,0,0,0,0,480,0,1 -482,0,481,1002,1,0,1,0,481,0,1 -483,0,482,2004,2,0,2,0,482,0,1 -484,0,483,3006,3,0,3,0,483,0,1 -485,0,484,4008,4,0,4,0,484,0,1 -486,0,485,500A,5,0,5,0,485,0,1 -487,0,486,600C,6,0,6,0,486,0,1 -488,0,487,700E,7,0,7,0,487,0,1 -489,0,488,8010,8,0,8,0,488,0,1 -490,0,489,9012,9,0,9,0,489,0,1 -491,0,490,A014,10,0,10,0,490,0,1 -492,0,491,B016,11,0,11,0,491,0,1 -493,0,492,C018,12,0,12,0,492,0,1 -494,0,493,D01A,13,0,13,0,493,0,1 -495,0,494,E01C,14,0,14,0,494,0,1 -496,0,495,F01E,15,0,15,0,495,0,1 -497,0,496,0000,0,0,0,0,496,0,1 -498,0,497,1002,1,0,1,0,497,0,1 -499,0,498,2004,2,0,2,0,498,0,1 -500,0,499,3006,3,0,3,0,499,0,1 -501,0,500,4008,4,0,4,0,500,0,1 -502,0,501,500A,5,0,5,0,501,0,1 -503,0,502,600C,6,0,6,0,502,0,1 -504,0,503,700E,7,0,7,0,503,0,1 -505,0,504,8010,8,0,8,0,504,0,1 -506,0,505,9012,9,0,9,0,505,0,1 -507,0,506,A014,10,0,10,0,506,0,1 -508,0,507,B016,11,0,11,0,507,0,1 -509,0,508,C018,12,0,12,0,508,0,1 -510,0,509,D01A,13,0,13,0,509,0,1 -511,0,510,E01C,14,0,14,0,510,0,1 -512,0,511,F01E,15,0,15,0,511,0,1 -513,0,512,0000,0,0,0,0,512,0,1 -514,0,513,1002,1,0,1,0,513,0,1 -515,0,514,2004,2,0,2,0,514,0,1 -516,0,515,3006,3,0,3,0,515,0,1 -517,0,516,4008,4,0,4,0,516,0,1 -518,0,517,500A,5,0,5,0,517,0,1 -519,0,518,600C,6,0,6,0,518,0,1 -520,0,519,700E,7,0,7,0,519,0,1 -521,0,520,8010,8,0,8,0,520,0,1 -522,0,521,9012,9,0,9,0,521,0,1 -523,0,522,A014,10,0,10,0,522,0,1 -524,0,523,B016,11,0,11,0,523,0,1 -525,0,524,C018,12,0,12,0,524,0,1 -526,0,525,D01A,13,0,13,0,525,0,1 -527,0,526,E01C,14,0,14,0,526,0,1 -528,0,527,F01E,15,0,15,0,527,0,1 -529,0,528,0000,0,0,0,0,528,0,1 -530,0,529,1002,1,0,1,0,529,0,1 -531,0,530,2004,2,0,2,0,530,0,1 -532,0,531,3006,3,0,3,0,531,0,1 -533,0,532,4008,4,0,4,0,532,0,1 -534,0,533,500A,5,0,5,0,533,0,1 -535,0,534,600C,6,0,6,0,534,0,1 -536,0,535,700E,7,0,7,0,535,0,1 -537,0,536,8010,8,0,8,0,536,0,1 -538,0,537,9012,9,0,9,0,537,0,1 -539,0,538,A014,10,0,10,0,538,0,1 -540,0,539,B016,11,0,11,0,539,0,1 -541,0,540,C018,12,0,12,0,540,0,1 -542,0,541,D01A,13,0,13,0,541,0,1 -543,0,542,E01C,14,0,14,0,542,0,1 -544,0,543,F01E,15,0,15,0,543,0,1 -545,0,544,0000,0,0,0,0,544,0,1 -546,0,545,1002,1,0,1,0,545,0,1 -547,0,546,2004,2,0,2,0,546,0,1 -548,0,547,3006,3,0,3,0,547,0,1 -549,0,548,4008,4,0,4,0,548,0,1 -550,0,549,500A,5,0,5,0,549,0,1 -551,0,550,600C,6,0,6,0,550,0,1 -552,0,551,700E,7,0,7,0,551,0,1 -553,0,552,8010,8,0,8,0,552,0,1 -554,0,553,9012,9,0,9,0,553,0,1 -555,0,554,A014,10,0,10,0,554,0,1 -556,0,555,B016,11,0,11,0,555,0,1 -557,0,556,C018,12,0,12,0,556,0,1 -558,0,557,D01A,13,0,13,0,557,0,1 -559,0,558,E01C,14,0,14,0,558,0,1 -560,0,559,F01E,15,0,15,0,559,0,1 -561,0,560,0000,0,0,0,0,560,0,1 -562,0,561,1002,1,0,1,0,561,0,1 -563,0,562,2004,2,0,2,0,562,0,1 -564,0,563,3006,3,0,3,0,563,0,1 -565,0,564,4008,4,0,4,0,564,0,1 -566,0,565,500A,5,0,5,0,565,0,1 -567,0,566,600C,6,0,6,0,566,0,1 -568,0,567,700E,7,0,7,0,567,0,1 -569,0,568,8010,8,0,8,0,568,0,1 -570,0,569,9012,9,0,9,0,569,0,1 -571,0,570,A014,10,0,10,0,570,0,1 -572,0,571,B016,11,0,11,0,571,0,1 -573,0,572,C018,12,0,12,0,572,0,1 -574,0,573,D01A,13,0,13,0,573,0,1 -575,0,574,E01C,14,0,14,0,574,0,1 -576,0,575,F01E,15,0,15,0,575,0,1 -577,0,576,0000,0,0,0,0,576,0,1 -578,0,577,1002,1,0,1,0,577,0,1 -579,0,578,2004,2,0,2,0,578,0,1 -580,0,579,3006,3,0,3,0,579,0,1 -581,0,580,4008,4,0,4,0,580,0,1 -582,0,581,500A,5,0,5,0,581,0,1 -583,0,582,600C,6,0,6,0,582,0,1 -584,0,583,700E,7,0,7,0,583,0,1 -585,0,584,8010,8,0,8,0,584,0,1 -586,0,585,9012,9,0,9,0,585,0,1 -587,0,586,A014,10,0,10,0,586,0,1 -588,0,587,B016,11,0,11,0,587,0,1 -589,0,588,C018,12,0,12,0,588,0,1 -590,0,589,D01A,13,0,13,0,589,0,1 -591,0,590,E01C,14,0,14,0,590,0,1 -592,0,591,F01E,15,0,15,0,591,0,1 -593,0,592,0000,0,0,0,0,592,0,1 -594,0,593,1002,1,0,1,0,593,0,1 -595,0,594,2004,2,0,2,0,594,0,1 -596,0,595,3006,3,0,3,0,595,0,1 -597,0,596,4008,4,0,4,0,596,0,1 -598,0,597,500A,5,0,5,0,597,0,1 -599,0,598,600C,6,0,6,0,598,0,1 -600,0,599,700E,7,0,7,0,599,0,1 -601,0,600,8010,8,0,8,0,600,0,1 -602,0,601,9012,9,0,9,0,601,0,1 -603,0,602,A014,10,0,10,0,602,0,1 -604,0,603,B016,11,0,11,0,603,0,1 -605,0,604,C018,12,0,12,0,604,0,1 -606,0,605,D01A,13,0,13,0,605,0,1 -607,0,606,E01C,14,0,14,0,606,0,1 -608,0,607,F01E,15,0,15,0,607,0,1 -609,0,608,0000,0,0,0,0,608,0,1 -610,0,609,1002,1,0,1,0,609,0,1 -611,0,610,2004,2,0,2,0,610,0,1 -612,0,611,3006,3,0,3,0,611,0,1 -613,0,612,4008,4,0,4,0,612,0,1 -614,0,613,500A,5,0,5,0,613,0,1 -615,0,614,600C,6,0,6,0,614,0,1 -616,0,615,700E,7,0,7,0,615,0,1 -617,0,616,8010,8,0,8,0,616,0,1 -618,0,617,9012,9,0,9,0,617,0,1 -619,0,618,A014,10,0,10,0,618,0,1 -620,0,619,B016,11,0,11,0,619,0,1 -621,0,620,C018,12,0,12,0,620,0,1 -622,0,621,D01A,13,0,13,0,621,0,1 -623,0,622,E01C,14,0,14,0,622,0,1 -624,0,623,F01E,15,0,15,0,623,0,1 -625,0,624,0000,0,0,0,0,624,0,1 -626,0,625,1002,1,0,1,0,625,0,1 -627,0,626,2004,2,0,2,0,626,0,1 -628,0,627,3006,3,0,3,0,627,0,1 -629,0,628,4008,4,0,4,0,628,0,1 -630,0,629,500A,5,0,5,0,629,0,1 -631,0,630,600C,6,0,6,0,630,0,1 -632,0,631,700E,7,0,7,0,631,0,1 -633,0,632,8010,8,0,8,0,632,0,1 -634,0,633,9012,9,0,9,0,633,0,1 -635,0,634,A014,10,0,10,0,634,0,1 -636,0,635,B016,11,0,11,0,635,0,1 -637,0,636,C018,12,0,12,0,636,0,1 -638,0,637,D01A,13,0,13,0,637,0,1 -639,0,638,E01C,14,0,14,0,638,0,1 -640,0,639,F01E,15,0,15,0,639,0,1 diff --git a/tests/test_output/vga_output_data.txt b/tests/test_output/vga_output_data.txt deleted file mode 100644 index 54a8c4b..0000000 --- a/tests/test_output/vga_output_data.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Format: cycle,h_count,v_count,red,green,blue -144000,0,0,0,0,0 -144001,0,0,0,0,0 -144002,0,0,0,0,0 -144003,0,0,0,0,0 -144004,1,0,0,0,0 diff --git a/tests/vga/unit_tests/test_vga_comprehensive.py b/tests/vga/unit_tests/test_vga_comprehensive.py index 247c470..0f65b8b 100644 --- a/tests/vga/unit_tests/test_vga_comprehensive.py +++ b/tests/vga/unit_tests/test_vga_comprehensive.py @@ -2,19 +2,26 @@ from cocotb.triggers import RisingEdge from cocotb.clock import Clock from vga.constants import * +from vga.utils import pack_rgb, drive_vdma_stream -async def simple_vdma_simulator(dut): - """Simple VDMA that sends white pixels whenever tready is high""" - white_pixel = 0xF79E # White: R=15, G=15, B=15 (bits 15:12, 10:7, 4:1) +async def wait_pixel_tick(dut): + """Wait for the VGA pixel clock tick (every 4 system clocks).""" while True: - if dut.vga_out.s_axis_tready.value: - dut.s_axis_tdata.value = white_pixel - dut.s_axis_tvalid.value = 1 - else: - dut.s_axis_tvalid.value = 0 - await RisingEdge(dut.i_Clock) + if int(dut.vga_out.r_Clock_Counter.value) == 0: + return + + +def pattern_pixel(x, y): + red = x & 0xF + green = (x + y) & 0xF + blue = y & 0xF + return pack_rgb(red, green, blue) + + +def pattern_expected(x, y): + return (x & 0xF), ((x + y) & 0xF), (y & 0xF) @cocotb.test() async def test_vga_divided_clock_timing(dut): @@ -89,9 +96,10 @@ async def test_vga_divided_clock_timing(dut): else: await RisingEdge(dut.i_Clock) + @cocotb.test() -async def test_vga_with_simple_vdma(dut): - clock = Clock(dut.i_Clock, 10, units="ns") # 100MHz clock (25MHz VGA pixel clock) +async def test_vga_simple_vdma(dut): + clock = Clock(dut.i_Clock, 10, units="ns") cocotb.start_soon(clock.start()) dut.i_Reset.value = 1 @@ -104,82 +112,107 @@ async def test_vga_with_simple_vdma(dut): dut.i_Reset.value = 0 await RisingEdge(dut.i_Clock) - # Start VDMA simulator in background - cocotb.start_soon(simple_vdma_simulator(dut)) + white_pixel = pack_rgb(0xF, 0xF, 0xF) + vdma_task = cocotb.start_soon(drive_vdma_stream(dut, lambda x, y: white_pixel, rows=4, cols=VISIBLE_H)) - # Test cycles: 5 VGA horizontal lines * 4 system clocks per VGA clock - test_cycles = 5 * TOTAL_H * 4 - for cycle in range(test_cycles): - if cycle % 4000 == 0: - dut._log.info(f"Progress: {cycle}/{test_cycles} ({100*cycle//test_cycles}%)") + # Wait for the first visible pixel of the frame + while True: + await wait_pixel_tick(dut) + if int(dut.vga_out.r_V_Counter.value) == 0 and int(dut.vga_out.r_H_Counter.value) == 0: + break - h_count = int(dut.vga_out.r_H_Counter.value) - v_count = int(dut.vga_out.r_V_Counter.value) - clock_count = int(dut.vga_out.r_Clock_Counter.value) + # Check first 32 pixels of the first visible row + for _ in range(32): + assert dut.o_Red.value.integer == 0xF + assert dut.o_Green.value.integer == 0xF + assert dut.o_Blue.value.integer == 0xF + await wait_pixel_tick(dut) - # Counter bounds checks - assert 0 <= h_count < TOTAL_H, f"H counter {h_count} out of range" - assert 0 <= v_count < TOTAL_V, f"V counter {v_count} out of range" - assert 0 <= clock_count <= 3, f"Clock counter {clock_count} out of range" + await vdma_task - # Horizontal sync timing - if h_count < VISIBLE_H + FRONT_PORCH_H: - assert dut.vga_out.o_Horizontal_Sync.value == 1, f"Hsync should be high at H{h_count}" - elif h_count < VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H: - assert dut.vga_out.o_Horizontal_Sync.value == 0, f"Hsync should be low at H{h_count}" - else: - assert dut.vga_out.o_Horizontal_Sync.value == 1, f"Hsync should be high at H{h_count}" - # Vertical sync timing - if v_count < VISIBLE_V + FRONT_PORCH_V: - assert dut.vga_out.o_Vertical_Sync.value == 1, f"Vsync should be high at V{v_count}" - elif v_count < VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V: - assert dut.vga_out.o_Vertical_Sync.value == 0, f"Vsync should be low at V{v_count}" - else: - assert dut.vga_out.o_Vertical_Sync.value == 1, f"Vsync should be high at V{v_count}" +@cocotb.test() +async def test_vga_pattern_vdma(dut): + clock = Clock(dut.i_Clock, 10, units="ns") + cocotb.start_soon(clock.start()) - fsync_v_position = VISIBLE_V - if h_count == 0 and v_count == fsync_v_position: - assert dut.vga_out.o_mm2s_fsync.value == 1, f"Fsync missing at start of back porch" - else: - assert dut.vga_out.o_mm2s_fsync.value == 0, f"Fsync spurious at H{h_count}V{v_count}" + dut.i_Reset.value = 1 + dut.s_axis_tvalid.value = 0 + dut.s_axis_tdata.value = 0 - # RGB validation - white pixel 0xF79E decodes to Red=15, Green=15, Blue=15 - if h_count < VISIBLE_H and v_count < VISIBLE_V: - expected_red = 15 - expected_green = 15 # 0xF79E bits [10:7] = 0xF - expected_blue = 15 # 0xF79E bits [4:1] = 0xF - - actual_red = int(dut.vga_out.o_Red.value) - actual_green = int(dut.vga_out.o_Green.value) - actual_blue = int(dut.vga_out.o_Blue.value) - - assert actual_red == expected_red, f"Red mismatch at H{h_count}V{v_count}: expected {expected_red}, got {actual_red}" - assert actual_green == expected_green, f"Green mismatch at H{h_count}V{v_count}: expected {expected_green}, got {actual_green}" - assert actual_blue == expected_blue, f"Blue mismatch at H{h_count}V{v_count}: expected {expected_blue}, got {actual_blue}" - elif h_count >= VISIBLE_H or v_count >= VISIBLE_V: - # RGB should be black during blanking - assert int(dut.vga_out.o_Red.value) == 0, f"Red not black during blanking at H{h_count}V{v_count}" - assert int(dut.vga_out.o_Green.value) == 0, f"Green not black during blanking at H{h_count}V{v_count}" - assert int(dut.vga_out.o_Blue.value) == 0, f"Blue not black during blanking at H{h_count}V{v_count}" + for _ in range(10): + await RisingEdge(dut.i_Clock) - # VGA counters should only update when clock_count == 3 - if clock_count == 3: - prev_h = h_count - prev_v = v_count - await RisingEdge(dut.i_Clock) - new_h = int(dut.vga_out.r_H_Counter.value) - new_v = int(dut.vga_out.r_V_Counter.value) + dut.i_Reset.value = 0 + await RisingEdge(dut.i_Clock) - # Verify counters incremented correctly - if prev_h == TOTAL_H - 1: - assert new_h == 0, f"H counter should wrap: {prev_h} -> {new_h}" - if prev_v == TOTAL_V - 1: - assert new_v == 0, f"V counter should wrap: {prev_v} -> {new_v}" - else: - assert new_v == prev_v + 1, f"V counter should increment: {prev_v} -> {new_v}" - else: - assert new_h == prev_h + 1, f"H counter should increment: {prev_h} -> {new_h}" - assert new_v == prev_v, f"V counter should stay same: {prev_v} -> {new_v}" - else: - await RisingEdge(dut.i_Clock) \ No newline at end of file + vdma_task = cocotb.start_soon(drive_vdma_stream(dut, pattern_pixel, rows=6, cols=VISIBLE_H)) + + # Wait for start of frame + while True: + await wait_pixel_tick(dut) + if int(dut.vga_out.r_V_Counter.value) == 0 and int(dut.vga_out.r_H_Counter.value) == 0: + break + + # Check a few rows and columns + for y in range(3): + for x in range(16): + exp_r, exp_g, exp_b = pattern_expected(x, y) + act_r = dut.o_Red.value.integer + act_g = dut.o_Green.value.integer + act_b = dut.o_Blue.value.integer + assert act_r == exp_r, f"R mismatch at ({x},{y}): got {act_r}, expected {exp_r}" + assert act_g == exp_g, f"G mismatch at ({x},{y}): got {act_g}, expected {exp_g}" + assert act_b == exp_b, f"B mismatch at ({x},{y}): got {act_b}, expected {exp_b}" + await wait_pixel_tick(dut) + + # Skip to next line start + while int(dut.vga_out.r_H_Counter.value) != 0: + await wait_pixel_tick(dut) + + await vdma_task + + +@cocotb.test() +async def test_vga_realistic_vdma(dut): + clock = Clock(dut.i_Clock, 10, units="ns") + cocotb.start_soon(clock.start()) + + dut.i_Reset.value = 1 + dut.s_axis_tvalid.value = 0 + dut.s_axis_tdata.value = 0 + + for _ in range(10): + await RisingEdge(dut.i_Clock) + + dut.i_Reset.value = 0 + await RisingEdge(dut.i_Clock) + + def gap_func(x, y): + # Deterministic small latency based on position + return (x + y) % 3 + + vdma_task = cocotb.start_soon(drive_vdma_stream(dut, pattern_pixel, rows=6, cols=VISIBLE_H, gap_func=gap_func)) + + # Wait for start of frame + while True: + await wait_pixel_tick(dut) + if int(dut.vga_out.r_V_Counter.value) == 0 and int(dut.vga_out.r_H_Counter.value) == 0: + break + + # Check a few pixels across two rows + for y in range(2): + for x in range(20): + exp_r, exp_g, exp_b = pattern_expected(x, y) + act_r = dut.o_Red.value.integer + act_g = dut.o_Green.value.integer + act_b = dut.o_Blue.value.integer + assert act_r == exp_r, f"R mismatch at ({x},{y}): got {act_r}, expected {exp_r}" + assert act_g == exp_g, f"G mismatch at ({x},{y}): got {act_g}, expected {exp_g}" + assert act_b == exp_b, f"B mismatch at ({x},{y}): got {act_b}, expected {exp_b}" + await wait_pixel_tick(dut) + + while int(dut.vga_out.r_H_Counter.value) != 0: + await wait_pixel_tick(dut) + + await vdma_task diff --git a/tests/vga/unit_tests/test_vga_pattern.py b/tests/vga/unit_tests/test_vga_pattern.py deleted file mode 100644 index b28081c..0000000 --- a/tests/vga/unit_tests/test_vga_pattern.py +++ /dev/null @@ -1,272 +0,0 @@ -import cocotb -from cocotb.triggers import RisingEdge -from cocotb.clock import Clock -from vga.constants import * -import os - -def calculate_pixel_rgb(row, col): - """Calculate RGB values based on row and column position""" - red = col % 16 # Red varies with column - green = row % 16 # Green varies with row - blue = (col + row) % 16 # Blue varies with both - return red, green, blue - -def save_pixel_data(pixel_list, filename): - """Save pixel data to text file for easy comparison""" - os.makedirs("test_output", exist_ok=True) - - with open(f"test_output/{filename}", 'w') as f: - f.write("# Format: cycle,h_count,v_count,red,green,blue\n") - for entry in pixel_list: - cycle, h, v, r, g, b = entry - f.write(f"{cycle},{h},{v},{r},{g},{b}\n") - print(f"Saved pixel data: test_output/{filename} ({len(pixel_list)} pixels)") - -def save_vdma_data(vdma_list, filename): - """Save VDMA data to text file for easy comparison""" - os.makedirs("test_output", exist_ok=True) - - with open(f"test_output/{filename}", 'w') as f: - f.write("# Format: cycle,row,col,pixel_data_hex,red,green,blue,blanking_prefetch_done,fill_addr,active_buffer,tready\n") - for entry in vdma_list: - if len(entry) == 7: - # Legacy format without diagnostics - cycle, row, col, pixel_data, r, g, b = entry - f.write(f"{cycle},{row},{col},{pixel_data:04X},{r},{g},{b},,,\n") - else: - # New format with diagnostics - cycle, row, col, pixel_data, r, g, b, blanking_prefetch_done, fill_addr, active_buffer, tready = entry - f.write(f"{cycle},{row},{col},{pixel_data:04X},{r},{g},{b},{blanking_prefetch_done},{fill_addr},{active_buffer},{tready}\n") - print(f"Saved VDMA data: test_output/{filename} ({len(vdma_list)} pixels)") - -async def pattern_vdma_simulator(dut, capture_data=None): - """VDMA that maintains its own counters and uses frame sync for synchronization""" - current_row = 0 - current_col = 0 - prev_fsync = 0 - cycle = 0 - - while True: - # Check for frame sync rising edge to reset position - fsync = int(dut.vga_out.o_mm2s_fsync.value) - if fsync and not prev_fsync: - current_row = 0 - current_col = 0 - prev_fsync = fsync - - # Always provide data when we have it (proactive DMA) - if current_row < VISIBLE_V: - # Generate RGB based on VDMA's own position counters using shared formula - red, green, blue = calculate_pixel_rgb(current_row, current_col) - - # Pack into 16-bit pixel: Red[15:12], Green[10:7], Blue[4:1] - pixel_data = (red << 12) | (green << 7) | (blue << 1) - - dut.s_axis_tdata.value = pixel_data - dut.s_axis_tvalid.value = 1 - - # Capture VDMA data when requested and handshake occurs - if capture_data is not None and dut.vga_out.s_axis_tready.value: - # Get diagnostic signals for debugging buffer management - blanking_prefetch_done = int(dut.vga_out.r_Blanking_Prefetch_Done.value) - fill_addr = int(dut.vga_out.r_Fill_Addr.value) - active_buffer = int(dut.vga_out.r_Active_Buffer.value) - tready = int(dut.vga_out.s_axis_tready.value) - - capture_data.append((cycle, current_row, current_col, pixel_data, red, green, blue, - blanking_prefetch_done, fill_addr, active_buffer, tready)) - - # Advance VDMA's position counters only when handshake completes - if dut.vga_out.s_axis_tready.value: - current_col += 1 - if current_col >= VISIBLE_H: - current_col = 0 - current_row += 1 - else: - dut.s_axis_tvalid.value = 0 - - cycle += 1 - await RisingEdge(dut.i_Clock) - -@cocotb.test() -async def test_vga_with_pattern_vdma(dut): - clock = Clock(dut.i_Clock, 10, units="ns") # 100MHz clock (25MHz VGA pixel clock) - cocotb.start_soon(clock.start()) - - dut.i_Reset.value = 1 - dut.s_axis_tvalid.value = 0 - dut.s_axis_tdata.value = 0 - - for _ in range(10): - await RisingEdge(dut.i_Clock) - - dut.i_Reset.value = 0 - await RisingEdge(dut.i_Clock) - - # Start pattern VDMA simulator in background - cocotb.start_soon(pattern_vdma_simulator(dut)) - - # Test cycles: 10 VGA horizontal lines * 4 system clocks per VGA clock (same as other tests) - test_cycles = 10 * TOTAL_H * 4 - for cycle in range(test_cycles): - if cycle % 4000 == 0: - dut._log.info(f"Progress: {cycle}/{test_cycles} ({100*cycle//test_cycles}%)") - - h_count = int(dut.vga_out.r_H_Counter.value) - v_count = int(dut.vga_out.r_V_Counter.value) - clock_count = int(dut.vga_out.r_Clock_Counter.value) - - # Counter bounds checks - assert 0 <= h_count < TOTAL_H, f"H counter {h_count} out of range" - assert 0 <= v_count < TOTAL_V, f"V counter {v_count} out of range" - assert 0 <= clock_count <= 3, f"Clock counter {clock_count} out of range" - - # Removed verbose VDMA logging and debug logs - - # RGB pattern validation - Simple validation for 10 lines - if h_count < VISIBLE_H and v_count < VISIBLE_V: - actual_red = int(dut.vga_out.o_Red.value) - actual_green = int(dut.vga_out.o_Green.value) - actual_blue = int(dut.vga_out.o_Blue.value) - - # Calculate expected RGB values for this exact position - exp_red, exp_green, exp_blue = calculate_pixel_rgb(v_count, h_count) - - - assert actual_red == exp_red, f"Red mismatch at H{h_count}V{v_count}: expected {exp_red}, got {actual_red}" - assert actual_green == exp_green, f"Green mismatch at H{h_count}V{v_count}: expected {exp_green}, got {actual_green}" - assert actual_blue == exp_blue, f"Blue mismatch at H{h_count}V{v_count}: expected {exp_blue}, got {actual_blue}" - elif h_count >= VISIBLE_H or v_count >= VISIBLE_V: - # RGB should be black during blanking - assert int(dut.vga_out.o_Red.value) == 0, f"Red not black during blanking at H{h_count}V{v_count}" - assert int(dut.vga_out.o_Green.value) == 0, f"Green not black during blanking at H{h_count}V{v_count}" - assert int(dut.vga_out.o_Blue.value) == 0, f"Blue not black during blanking at H{h_count}V{v_count}" - - # VGA counters should only update when clock_count == 3 - if clock_count == 3: - prev_h = h_count - prev_v = v_count - await RisingEdge(dut.i_Clock) - new_h = int(dut.vga_out.r_H_Counter.value) - new_v = int(dut.vga_out.r_V_Counter.value) - - # Verify counters incremented correctly - if prev_h == TOTAL_H - 1: - assert new_h == 0, f"H counter should wrap: {prev_h} -> {new_h}" - if prev_v == TOTAL_V - 1: - assert new_v == 0, f"V counter should wrap: {prev_v} -> {new_v}" - else: - assert new_v == prev_v + 1, f"V counter should increment: {prev_v} -> {new_v}" - else: - assert new_h == prev_h + 1, f"H counter should increment: {prev_h} -> {new_h}" - assert new_v == prev_v, f"V counter should stay same: {prev_v} -> {new_v}" - else: - await RisingEdge(dut.i_Clock) - -@cocotb.test() -async def test_vga_frame_transition(dut): - """Test VGA frame transitions to ensure timing improvements work across frame boundaries""" - clock = Clock(dut.i_Clock, 10, units="ns") # 100MHz clock (25MHz VGA pixel clock) - cocotb.start_soon(clock.start()) - - dut.i_Reset.value = 1 - dut.s_axis_tvalid.value = 0 - dut.s_axis_tdata.value = 0 - - for _ in range(10): - await RisingEdge(dut.i_Clock) - - dut.i_Reset.value = 0 - await RisingEdge(dut.i_Clock) - - # Create data capture lists - vga_output_data = [] - vdma_input_data = [] - - # Start pattern VDMA simulator in background with data capture - cocotb.start_soon(pattern_vdma_simulator(dut, vdma_input_data)) - - # Test cycles: Cover frame transition - ~75 lines to see frame boundary - transition_lines = 75 - test_cycles = transition_lines * TOTAL_H * 4 - - try: - for cycle in range(test_cycles): - if cycle % 50000 == 0: - dut._log.info(f"Frame transition test progress: {cycle}/{test_cycles} ({100*cycle//test_cycles}%)") - - h_count = int(dut.vga_out.r_H_Counter.value) - v_count = int(dut.vga_out.r_V_Counter.value) - clock_count = int(dut.vga_out.r_Clock_Counter.value) - - # Log key timing events - if h_count == 0 and clock_count == 0: - fsync_v_pos = VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V + (BACK_PORCH_V >> 1) # INITIAL_V_COUNT - if v_count == fsync_v_pos: - dut._log.info(f"🔄 Frame sync at V={v_count} - VDMA resetting for next frame") - elif v_count == 0: - dut._log.info("🎯 Frame 2 visible area starts - testing improved VGA timing!") - elif v_count < 5: # Debug first few rows - dut._log.info(f"📍 Early visible row V={v_count}") - - # Debug pixel mismatch at the specific failing positions - if (h_count <= 1 and v_count == 3) or (h_count == 0 and v_count <= 4): - actual_red = int(dut.vga_out.o_Red.value) - actual_green = int(dut.vga_out.o_Green.value) - actual_blue = int(dut.vga_out.o_Blue.value) - exp_red, exp_green, exp_blue = calculate_pixel_rgb(v_count, h_count) - dut._log.info(f"🔍 H{h_count}V{v_count} debug: actual RGB=({actual_red},{actual_green},{actual_blue}) expected=({exp_red},{exp_green},{exp_blue})") - dut._log.info(f" Buffer state: active={int(dut.vga_out.r_Active_Buffer.value)}, fill_addr={int(dut.vga_out.r_Fill_Addr.value)}") - dut._log.info(f" VDMA: tready={int(dut.vga_out.s_axis_tready.value)}, tvalid={int(dut.s_axis_tvalid.value)}, tdata=0x{int(dut.s_axis_tdata.value):04X}") - dut._log.info(f" Frame sync: {int(dut.vga_out.o_mm2s_fsync.value)}") - - # Validate visible pixels - if h_count < VISIBLE_H and v_count < VISIBLE_V: - actual_red = int(dut.vga_out.o_Red.value) - actual_green = int(dut.vga_out.o_Green.value) - actual_blue = int(dut.vga_out.o_Blue.value) - - # Capture VGA output data - vga_output_data.append((cycle, h_count, v_count, actual_red, actual_green, actual_blue)) - - exp_red, exp_green, exp_blue = calculate_pixel_rgb(v_count, h_count) - - # Skip assertions on first 2 rows to collect more data - # if v_count >= 2: - assert actual_red == exp_red, f"Red mismatch at H{h_count}V{v_count}: expected {exp_red}, got {actual_red}" - assert actual_green == exp_green, f"Green mismatch at H{h_count}V{v_count}: expected {exp_green}, got {actual_green}" - assert actual_blue == exp_blue, f"Blue mismatch at H{h_count}V{v_count}: expected {exp_blue}, got {actual_blue}" - - elif h_count >= VISIBLE_H or v_count >= VISIBLE_V: - # RGB should be black during blanking - assert int(dut.vga_out.o_Red.value) == 0, f"Red not black during blanking at H{h_count}V{v_count}" - assert int(dut.vga_out.o_Green.value) == 0, f"Green not black during blanking at H{h_count}V{v_count}" - assert int(dut.vga_out.o_Blue.value) == 0, f"Blue not black during blanking at H{h_count}V{v_count}" - - # VGA counters should only update when clock_count == 3 - if clock_count == 3: - prev_h = h_count - prev_v = v_count - await RisingEdge(dut.i_Clock) - new_h = int(dut.vga_out.r_H_Counter.value) - new_v = int(dut.vga_out.r_V_Counter.value) - - # Verify counters incremented correctly - if prev_h == TOTAL_H - 1: - assert new_h == 0, f"H counter should wrap: {prev_h} -> {new_h}" - if prev_v == TOTAL_V - 1: - assert new_v == 0, f"V counter should wrap: {prev_v} -> {new_v}" - else: - assert new_v == prev_v + 1, f"V counter should increment: {prev_v} -> {new_v}" - else: - assert new_h == prev_h + 1, f"H counter should increment: {prev_h} -> {new_h}" - assert new_v == prev_v, f"V counter should stay same: {prev_v} -> {new_v}" - else: - await RisingEdge(dut.i_Clock) - - finally: - # Save captured data to files for comparison, even if test fails - save_pixel_data(vga_output_data, "vga_output_data.txt") - save_vdma_data(vdma_input_data, "vdma_input_data.txt") - - dut._log.info(f"Test completed. Captured {len(vga_output_data)} VGA pixels and {len(vdma_input_data)} VDMA pixels") \ No newline at end of file diff --git a/tests/vga/utils.py b/tests/vga/utils.py index e69de29..bd54990 100644 --- a/tests/vga/utils.py +++ b/tests/vga/utils.py @@ -0,0 +1,49 @@ +from cocotb.triggers import RisingEdge + +from vga.constants import VISIBLE_H, VISIBLE_V + + +def pack_rgb(red, green, blue): + """Pack 4-bit RGB into 16-bit pixel format used by vga_out.""" + return ((red & 0xF) << 12) | ((green & 0xF) << 7) | ((blue & 0xF) << 1) + + +async def wait_for_fsync(dut): + """Wait for mm2s fsync pulse.""" + while True: + await RisingEdge(dut.i_Clock) + if dut.o_mm2s_fsync.value.integer == 1: + return + + +async def drive_vdma_stream(dut, pixel_func, rows=VISIBLE_V, cols=VISIBLE_H, gap_func=None): + """Drive AXI-Stream pixel data honoring tready and optional gaps.""" + await wait_for_fsync(dut) + + x = 0 + y = 0 + gap = 0 + + dut.s_axis_tvalid.value = 0 + dut.s_axis_tdata.value = 0 + + while y < rows: + if gap > 0: + dut.s_axis_tvalid.value = 0 + gap -= 1 + await RisingEdge(dut.i_Clock) + continue + + dut.s_axis_tdata.value = pixel_func(x, y) + dut.s_axis_tvalid.value = 1 + await RisingEdge(dut.i_Clock) + + if dut.s_axis_tready.value.integer == 1: + x += 1 + if x >= cols: + x = 0 + y += 1 + if gap_func is not None: + gap = int(gap_func(x, y)) + + dut.s_axis_tvalid.value = 0 From 93abe2fbd1cc85c675f4ac8ca01fdffb03a305cf Mon Sep 17 00:00:00 2001 From: Ema Dervisevic Date: Wed, 28 Jan 2026 20:04:18 +0100 Subject: [PATCH 4/8] Done --- hdl/vga_out/vga_out.v | 133 ++++++--------- .../vga/unit_tests/test_vga_comprehensive.py | 155 +++++++++++++++++- tests/vga/utils.py | 8 +- 3 files changed, 212 insertions(+), 84 deletions(-) diff --git a/hdl/vga_out/vga_out.v b/hdl/vga_out/vga_out.v index 15c5a93..f495a16 100644 --- a/hdl/vga_out/vga_out.v +++ b/hdl/vga_out/vga_out.v @@ -41,15 +41,14 @@ module vga_out #( reg [15:0] r_H_Counter = 0; reg [15:0] r_V_Counter = 0; + // Dual row buffers for pixel data reg [15:0] r_Row_Buffer_0 [0:VISIBLE_H-1]; reg [15:0] r_Row_Buffer_1 [0:VISIBLE_H-1]; - reg r_Display_Active = 1'b0; - reg r_Display_Buffer = 1'b0; - reg r_Load_Buffer = 1'b0; - reg [1:0] r_Row_Count = 0; - reg [9:0] r_Load_Col = 0; - reg r_Loading = 1'b0; + // Buffer control signals + reg r_Active_Buffer = 0; // 0 = reading from buffer_0, 1 = reading from buffer_1 + reg [9:0] r_Fill_Addr = 0; // Address for filling buffer (0 to VISIBLE_H) + reg r_Blanking_Prefetch_Done = 0; localparam [1:0] STATE_VISIBLE = 0; localparam [1:0] STATE_FRONT_PORCH = 1; @@ -76,51 +75,14 @@ module vga_out #( wire w_Pixel_Clock_Tick = (r_Clock_Counter == 3); - wire w_Fsync_Level = (r_H_Counter == 0) && (r_V_Counter == VISIBLE_V); - - assign s_axis_tready = (r_Row_Count < 2) && !w_Fsync_Level; - - wire w_Load_Handshake = s_axis_tvalid && s_axis_tready; - wire w_Load_Complete = w_Load_Handshake && (r_Load_Col == VISIBLE_H - 1); - - wire w_Row_Consume = w_Pixel_Clock_Tick && (r_V_Counter < VISIBLE_V) && - (r_H_Counter == VISIBLE_H - 1) && r_Display_Active && (r_Row_Count > 0); - - wire w_Line_Start = w_Pixel_Clock_Tick && (r_H_Counter == TOTAL_H - 1); - - wire w_Fsync_Event = w_Pixel_Clock_Tick && (r_H_Counter == 0) && (r_V_Counter == VISIBLE_V); - always @(posedge i_Clock or posedge i_Reset) begin if (i_Reset) begin r_Clock_Counter <= 0; r_H_Counter <= 0; r_V_Counter <= VISIBLE_V; // Start vertical counter in blanking period - r_Display_Active <= 1'b0; - r_Display_Buffer <= 1'b0; - r_Load_Buffer <= 1'b0; - r_Row_Count <= 0; - r_Load_Col <= 0; - r_Loading <= 1'b0; end else begin r_Clock_Counter <= r_Clock_Counter + 1; - // AXI-Stream row loading logic - if (w_Load_Handshake) begin - r_Loading <= 1'b1; - if (r_Load_Buffer == 1'b0) - r_Row_Buffer_0[r_Load_Col] <= s_axis_tdata; - else - r_Row_Buffer_1[r_Load_Col] <= s_axis_tdata; - - if (r_Load_Col == VISIBLE_H - 1) begin - r_Load_Col <= 0; - r_Loading <= 1'b0; - r_Load_Buffer <= ~r_Load_Buffer; - end else begin - r_Load_Col <= r_Load_Col + 1; - end - end - if (w_Pixel_Clock_Tick) begin // VGA pixel clock tick every 4th cycle if(r_H_Counter == TOTAL_H - 1) begin // If we've reached the end of the horizontal line r_H_Counter <= 0; @@ -132,55 +94,66 @@ module vga_out #( end else begin r_H_Counter <= r_H_Counter + 1; end + end + end + end - // Start-of-line handling (after wrap) to activate display buffer - if (w_Line_Start) begin - if ((r_V_Counter == TOTAL_V - 1) ? 1'b1 : ((r_V_Counter + 1) < VISIBLE_V)) begin - r_Display_Active <= (r_Row_Count > 0); + wire w_Frame_Sync_Level = (r_H_Counter == 0) && (r_V_Counter == VISIBLE_V); + wire w_Frame_Sync_Event = w_Pixel_Clock_Tick && w_Frame_Sync_Level; + assign o_mm2s_fsync = w_Frame_Sync_Level; + + assign s_axis_tready = (!i_Reset) && (!w_Frame_Sync_Level) && + ((r_V_State == STATE_VISIBLE) ? (r_Fill_Addr < VISIBLE_H) + : (!r_Blanking_Prefetch_Done && (r_Fill_Addr < VISIBLE_H))); + + // Buffer filling and switching logic + always @(posedge i_Clock or posedge i_Reset) begin + if (i_Reset) begin + r_Fill_Addr <= 0; + r_Active_Buffer <= 0; + r_Blanking_Prefetch_Done <= 0; + end else begin + if (w_Frame_Sync_Event) begin + r_Fill_Addr <= 0; + r_Active_Buffer <= 0; + r_Blanking_Prefetch_Done <= 0; + end + + if (s_axis_tready && s_axis_tvalid) begin + if (r_Fill_Addr < VISIBLE_H) begin + if (r_Active_Buffer) begin + r_Row_Buffer_0[r_Fill_Addr] <= s_axis_tdata; end else begin - r_Display_Active <= 1'b0; + r_Row_Buffer_1[r_Fill_Addr] <= s_axis_tdata; end - end - - // End-of-visible-line consumption - if (w_Row_Consume) begin - r_Display_Buffer <= ~r_Display_Buffer; - r_Display_Active <= 1'b0; - end - // Frame sync: reset row buffering at start of vertical back porch - if (w_Fsync_Event) begin - r_Display_Active <= 1'b0; - r_Display_Buffer <= 1'b0; - r_Load_Buffer <= 1'b0; - r_Load_Col <= 0; - r_Loading <= 1'b0; + if (r_Fill_Addr < VISIBLE_H - 1) begin + r_Fill_Addr <= r_Fill_Addr + 1; + end else begin + r_Fill_Addr <= VISIBLE_H; + end end end - // Row count update (handles simultaneous load/consume) - if (w_Fsync_Event) begin - r_Row_Count <= 0; - end else begin - case ({w_Load_Complete, w_Row_Consume}) - 2'b10: r_Row_Count <= r_Row_Count + 1; - 2'b01: r_Row_Count <= r_Row_Count - 1; - default: r_Row_Count <= r_Row_Count; - endcase + if (w_Pixel_Clock_Tick && (r_H_Counter == VISIBLE_H) && (r_V_State == STATE_VISIBLE)) begin + r_Active_Buffer <= ~r_Active_Buffer; + r_Fill_Addr <= 0; + end else if ((r_Fill_Addr == VISIBLE_H) && (r_V_State != STATE_VISIBLE) && !r_Blanking_Prefetch_Done) begin + r_Active_Buffer <= ~r_Active_Buffer; + r_Fill_Addr <= 0; + r_Blanking_Prefetch_Done <= 1; end end end - assign o_mm2s_fsync = w_Fsync_Level; + wire [15:0] w_Current_Pixel = w_Visible ? + (r_Active_Buffer ? r_Row_Buffer_1[r_H_Counter[9:0]] : r_Row_Buffer_0[r_H_Counter[9:0]]) : 16'h0000; - wire [15:0] w_Current_Pixel = (r_H_Counter < VISIBLE_H) - ? ((r_Display_Buffer == 1'b0) ? r_Row_Buffer_0[r_H_Counter[9:0]] : r_Row_Buffer_1[r_H_Counter[9:0]]) - : 16'h0000; - assign o_Red = (w_Visible && r_Display_Active) ? w_Current_Pixel[15:12] : {BITS_PER_COLOR_CHANNEL{1'b0}}; - assign o_Green = (w_Visible && r_Display_Active) ? w_Current_Pixel[10:7] : {BITS_PER_COLOR_CHANNEL{1'b0}}; - assign o_Blue = (w_Visible && r_Display_Active) ? w_Current_Pixel[4:1] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Red = w_Visible ? w_Current_Pixel[15:12] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Green = w_Visible ? w_Current_Pixel[10:7] : {BITS_PER_COLOR_CHANNEL{1'b0}}; + assign o_Blue = w_Visible ? w_Current_Pixel[4:1] : {BITS_PER_COLOR_CHANNEL{1'b0}}; - assign o_Horizontal_Sync = ~(r_H_State == STATE_SYNC); - assign o_Vertical_Sync = ~(r_V_State == STATE_SYNC); + assign o_Horizontal_Sync = ~(r_H_State == STATE_SYNC); // Invert for active low + assign o_Vertical_Sync = ~(r_V_State == STATE_SYNC); // Invert for active low endmodule diff --git a/tests/vga/unit_tests/test_vga_comprehensive.py b/tests/vga/unit_tests/test_vga_comprehensive.py index 0f65b8b..b60f879 100644 --- a/tests/vga/unit_tests/test_vga_comprehensive.py +++ b/tests/vga/unit_tests/test_vga_comprehensive.py @@ -1,9 +1,12 @@ import cocotb -from cocotb.triggers import RisingEdge +from cocotb.triggers import RisingEdge, Timer from cocotb.clock import Clock +from cocotb.utils import get_sim_time from vga.constants import * from vga.utils import pack_rgb, drive_vdma_stream +import time + async def wait_pixel_tick(dut): """Wait for the VGA pixel clock tick (every 4 system clocks).""" @@ -13,6 +16,23 @@ async def wait_pixel_tick(dut): return +async def wait_for_fsync_rising(dut): + """Wait for a rising edge of the fsync level (robust against multi-cycle high).""" + while int(dut.o_mm2s_fsync.value) == 1: + await RisingEdge(dut.i_Clock) + while True: + await RisingEdge(dut.i_Clock) + if int(dut.o_mm2s_fsync.value) == 1: + return + + +async def wait_for_fsync_event(dut): + """Treat an already-high fsync as an event; otherwise wait for rising.""" + if int(dut.o_mm2s_fsync.value) == 1: + return + await wait_for_fsync_rising(dut) + + def pattern_pixel(x, y): red = x & 0xF green = (x + y) & 0xF @@ -216,3 +236,136 @@ def gap_func(x, y): await wait_pixel_tick(dut) await vdma_task + + +@cocotb.test() +async def test_vga_frame_transition_realistic_vdma(dut): + """Verify that after a frame boundary the pixel stream restarts at (0,0). + + Uses the same "realistic" deterministic gap pattern as test_vga_realistic_vdma. + """ + + clock = Clock(dut.i_Clock, 10, units="ns") + cocotb.start_soon(clock.start()) + + dut.i_Reset.value = 1 + dut.s_axis_tvalid.value = 0 + dut.s_axis_tdata.value = 0 + + for _ in range(10): + await RisingEdge(dut.i_Clock) + + dut.i_Reset.value = 0 + await RisingEdge(dut.i_Clock) + + def gap_func(x, y): + return (x + y) % 3 + + # Single progress log line, throttled by wallclock time. + last_log_wall = time.monotonic() + phase = "init" + + def log_progress(force=False): + nonlocal last_log_wall + now = time.monotonic() + if (not force) and (now - last_log_wall) < 5.0: + return + last_log_wall = now + dut._log.info( + f"[frame_transition] phase={phase} sim_ms={get_sim_time(units='ms'):.3f} " + f"V={int(dut.vga_out.r_V_Counter.value)} H={int(dut.vga_out.r_H_Counter.value)}" + ) + + async def check_first_row(cols_to_check=20): + for x in range(cols_to_check): + exp_r, exp_g, exp_b = pattern_expected(x, 0) + act_r = dut.o_Red.value.integer + act_g = dut.o_Green.value.integer + act_b = dut.o_Blue.value.integer + assert act_r == exp_r, f"R mismatch at ({x},0): got {act_r}, expected {exp_r}" + assert act_g == exp_g, f"G mismatch at ({x},0): got {act_g}, expected {exp_g}" + assert act_b == exp_b, f"B mismatch at ({x},0): got {act_b}, expected {exp_b}" + await wait_pixel_tick(dut) + + # Frame 1: drive just enough data to make row0 valid (use realistic gaps). + vdma_frame1 = cocotb.start_soon( + drive_vdma_stream(dut, pattern_pixel, rows=2, cols=VISIBLE_H, gap_func=gap_func) + ) + + # Frame 1 visible start + phase = "wait_frame1_visible" + log_progress(force=True) + while True: + await wait_pixel_tick(dut) + log_progress() + if int(dut.vga_out.r_V_Counter.value) == 0 and int(dut.vga_out.r_H_Counter.value) == 0: + break + + phase = "check_frame1_row0" + log_progress(force=True) + await check_first_row(cols_to_check=20) + + await vdma_frame1 + dut.s_axis_tvalid.value = 0 + + # Fast-forward to a clean fsync event without an active VDMA task. + # Place the DUT exactly at the fsync coordinate and ensure a pixel-tick edge occurs + # so the RTL executes its per-frame reset (w_Frame_Sync_Event). + phase = "force_fsync_event" + log_progress(force=True) + dut.vga_out.r_V_Counter.value = VISIBLE_V + dut.vga_out.r_H_Counter.value = 0 + dut.vga_out.r_Clock_Counter.value = 3 + await RisingEdge(dut.i_Clock) + + # Advance one more pixel tick so fsync level deasserts (H becomes 1) and prefetch can run. + dut.vga_out.r_Clock_Counter.value = 3 + await RisingEdge(dut.i_Clock) + + # Frame 2: start stream at (0,0) immediately after the forced fsync event. + # The DUT will prefetch one row during vertical blanking. + phase = "drive_frame2_prefetch" + log_progress(force=True) + vdma_frame2 = cocotb.start_soon( + drive_vdma_stream( + dut, + pattern_pixel, + rows=1, + cols=VISIBLE_H, + gap_func=gap_func, + wait_for_fsync_pulse=False, + ) + ) + + phase = "wait_blank_prefetch_done" + log_progress(force=True) + while int(dut.vga_out.r_Blanking_Prefetch_Done.value) == 0: + await RisingEdge(dut.i_Clock) + log_progress() + + await vdma_frame2 + dut.s_axis_tvalid.value = 0 + + # Now fast-forward to the end of the frame so the next pixel tick wraps to V=0,H=0. + phase = "fast_forward_to_frame2_start" + log_progress(force=True) + dut.vga_out.r_V_Counter.value = TOTAL_V - 1 + dut.vga_out.r_H_Counter.value = TOTAL_H - 1 + dut.vga_out.r_Clock_Counter.value = 3 + await RisingEdge(dut.i_Clock) + + # Frame 2 visible start + phase = "wait_frame2_visible" + log_progress(force=True) + while True: + await wait_pixel_tick(dut) + log_progress() + if int(dut.vga_out.r_V_Counter.value) == 0 and int(dut.vga_out.r_H_Counter.value) == 0: + break + + phase = "check_frame2_row0" + log_progress(force=True) + await check_first_row(cols_to_check=20) + + phase = "done" + log_progress(force=True) diff --git a/tests/vga/utils.py b/tests/vga/utils.py index bd54990..097667a 100644 --- a/tests/vga/utils.py +++ b/tests/vga/utils.py @@ -16,9 +16,11 @@ async def wait_for_fsync(dut): return -async def drive_vdma_stream(dut, pixel_func, rows=VISIBLE_V, cols=VISIBLE_H, gap_func=None): - """Drive AXI-Stream pixel data honoring tready and optional gaps.""" - await wait_for_fsync(dut) +async def drive_vdma_stream( + dut, pixel_func, rows=VISIBLE_V, cols=VISIBLE_H, gap_func=None, wait_for_fsync_pulse=True +): + if wait_for_fsync_pulse: + await wait_for_fsync(dut) x = 0 y = 0 From 70734a5cac112800e0550c7d0744d59d787408bd Mon Sep 17 00:00:00 2001 From: Ema Dervisevic Date: Wed, 28 Jan 2026 20:05:52 +0100 Subject: [PATCH 5/8] add action --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 289f7bd..c73de1c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - test-type: [unit, integration] + test-type: [unit, integration, vga] fail-fast: false steps: From 52f7ae4e82cc0f3c3af9397ae299159ad4392b59 Mon Sep 17 00:00:00 2001 From: Ema Dervisevic Date: Wed, 28 Jan 2026 20:08:48 +0100 Subject: [PATCH 6/8] Remove junk --- batch_update_tests.py | 78 ------------------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 batch_update_tests.py diff --git a/batch_update_tests.py b/batch_update_tests.py deleted file mode 100644 index 266eed4..0000000 --- a/batch_update_tests.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -"""Batch update test files with halt/unhalt pattern.""" - -import re -import sys - -files_to_update = [ - 'test_ori', 'test_sub', 'test_xor', 'test_xori', - 'test_sll', 'test_slli', 'test_slt', 'test_slti', 'test_sltiu', 'test_sltu', - 'test_sra', 'test_srai', 'test_srl', 'test_srli', - 'test_lb', 'test_lbu', 'test_lh', 'test_lhu', 'test_lw', - 'test_sb', 'test_sh', 'test_sw', - 'test_lui', 'test_jal', 'test_jalr', -] - -base_path = '/home/emma/gpu/tests/cpu/integration_tests/' - -def update_file_with_halt_unhalt(filepath): - """Add halt/unhalt pattern to test file.""" - with open(filepath, 'r') as f: - content = f.read() - - # Pattern 1: For files with loops (most R-type and I-type ALU instructions) - # Match: await ClockCycles + write_word_to_mem + r_PC + registers + await ClockCycles(PIPELINE_CYCLES) - pattern1 = re.compile( - r'(\s+)(dut\.i_Reset\.value = 0\n\s+await ClockCycles\(dut\.i_Clock, 1\))\n\n' - r'(\s+)((?:write_word_to_mem|write_byte_to_mem|write_half_to_mem)[^\n]+\n)' - r'(\s+)(dut\.cpu\.r_PC\.value = [^\n]+\n)' - r'((?:\s+dut\.cpu\.reg_file\.Registers\[[^\]]+\]\.value = [^\n]+\n)+)' - r'(\s+)(await ClockCycles\(dut\.i_Clock, PIPELINE_CYCLES\))', - re.MULTILINE - ) - - def replacement1(match): - indent = match.group(1) - reset_line = match.group(2) - write_indent = match.group(3) - write_line = match.group(4) - pc_indent = match.group(5) - pc_line = match.group(6) - reg_lines = match.group(7) - await_indent = match.group(8) - await_line = match.group(9) - - return ( - f'{indent}{reset_line}\n\n' - f'{indent}# HALT CPU before setup\n' - f'{indent}await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_HALT)\n' - f'{indent}await wait_for_pipeline_flush(dut)\n\n' - f'{indent}# Set up test while CPU is halted\n' - f'{write_indent}{write_line}' - f'{pc_indent}{pc_line}' - f'{reg_lines}' - f'{await_indent}await ClockCycles(dut.i_Clock, 1)\n\n' - f'{indent}# UNHALT CPU to start execution\n' - f'{indent}await uart_send_byte(dut.i_Clock, dut.cpu.i_Uart_Tx_In, dut.cpu.debug_peripheral.uart_receiver.o_Rx_DV, DEBUG_OP_UNHALT)\n\n' - f'{await_indent}{await_line.replace("PIPELINE_CYCLES)", "PIPELINE_CYCLES + 3)")}' - ) - - # Try pattern 1 - new_content, count = pattern1.subn(replacement1, content) - - if count > 0: - with open(filepath, 'w') as f: - f.write(new_content) - print(f"Updated {filepath} ({count} replacements)") - return True - else: - print(f"No matches found in {filepath} - needs manual update") - return False - -if __name__ == '__main__': - for fname in files_to_update: - fpath = base_path + fname + '_instruction.py' - try: - update_file_with_halt_unhalt(fpath) - except Exception as e: - print(f"Error updating {fname}: {e}") From 94b45f219a201440b13ff6b141160905d9f5e17c Mon Sep 17 00:00:00 2001 From: Ema Dervisevic Date: Wed, 28 Jan 2026 20:10:50 +0100 Subject: [PATCH 7/8] don't trigger action on every push --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c73de1c..b6e39fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: Test on: push: - branches: ['**'] + branches: [main] pull_request: branches: [main] workflow_dispatch: From bffb088ba64d696036b956b5415531bfb69250f0 Mon Sep 17 00:00:00 2001 From: Ema Dervisevic Date: Wed, 28 Jan 2026 20:53:37 +0100 Subject: [PATCH 8/8] remove useless tests that waste my time --- .../integration_tests/test_debug_write_pc.py | 117 +----------------- 1 file changed, 2 insertions(+), 115 deletions(-) diff --git a/tests/cpu/integration_tests/test_debug_write_pc.py b/tests/cpu/integration_tests/test_debug_write_pc.py index 59a24e0..1c0ff15 100644 --- a/tests/cpu/integration_tests/test_debug_write_pc.py +++ b/tests/cpu/integration_tests/test_debug_write_pc.py @@ -1,6 +1,6 @@ import cocotb -from cpu.utils import uart_send_byte, uart_wait_for_byte -from cpu.constants import DEBUG_OP_WRITE_PC, DEBUG_OP_READ_PC +from cpu.utils import uart_send_byte, uart_wait_for_byte, wait_for_pipeline_flush +from cpu.constants import DEBUG_OP_WRITE_PC, DEBUG_OP_READ_PC, DEBUG_OP_UNHALT from cocotb.clock import Clock from cocotb.triggers import ClockCycles @@ -135,116 +135,3 @@ async def test_write_pc_data_patterns(dut): for j, (expected, received) in enumerate(zip(expected_bytes, received_bytes)): assert expected == received, f"Pattern {i}, byte {j}: expected {expected:#04x}, got {received:#04x}" - -@cocotb.test() -async def test_write_pc_cpu_stability(dut): - """Test that WRITE_PC command doesn't break CPU functionality""" - - clock = Clock(dut.i_Clock, wait_ns, "ns") - cocotb.start_soon(clock.start()) - - dut.i_Reset.value = 1 - await ClockCycles(dut.i_Clock, 1) - dut.i_Reset.value = 0 - await ClockCycles(dut.i_Clock, 1) - - # Save initial state - initial_pc = dut.cpu.r_PC.value.integer - - # Test PC modification doesn't affect register file - test_registers = {3: 0x33333333, 4: 0x44444444, 6: 0x66666666} - for reg_addr, value in test_registers.items(): - dut.cpu.reg_file.Registers[reg_addr].value = value - - # Perform WRITE_PC operation (WRITE_PC handles halting internally) - test_pc = 0xDEADBEEF - await send_write_pc_command(dut, test_pc) - await ClockCycles(dut.i_Clock, 50) - - # Verify PC was written - current_pc = dut.cpu.r_PC.value.integer - assert current_pc == test_pc, f"PC write failed: got {current_pc:#010x}, expected {test_pc:#010x}" - - # Verify other registers unchanged - for reg_addr, expected_value in test_registers.items(): - current_value = dut.cpu.reg_file.Registers[reg_addr].value.integer - assert current_value == expected_value, f"Register {reg_addr} changed! Expected {expected_value:#010x}, got {current_value:#010x}" - - # Verify register 0 is still 0 - reg0_value = dut.cpu.reg_file.Registers[0].value.integer - assert reg0_value == 0, f"Register 0 changed! Should be 0, got {reg0_value:#010x}" - - # CPU should continue execution normally after WRITE_PC operation - await ClockCycles(dut.i_Clock, 10) - - # If we reach here without hanging, the CPU is working fine - -@cocotb.test() -async def test_write_pc_boundary_values(dut): - """Test WRITE_PC with boundary and edge case values""" - - clock = Clock(dut.i_Clock, wait_ns, "ns") - cocotb.start_soon(clock.start()) - - dut.i_Reset.value = 1 - await ClockCycles(dut.i_Clock, 1) - dut.i_Reset.value = 0 - await ClockCycles(dut.i_Clock, 1) - - # Test boundary values - boundary_values = [ - 0x00000000, # Minimum value - 0x00000FFC, # Just before ROM boundary (4-byte aligned) - 0x00001000, # ROM boundary (from constants.py) - 0x00001004, # Just after ROM boundary - 0x7FFFFFFC, # Large positive (signed interpretation) - 0x80000000, # Sign bit set (if interpreted as signed) - 0xFFFFFFFC, # Maximum reasonable address (4-byte aligned) - ] - - for test_pc in boundary_values: - await send_write_pc_command(dut, test_pc) - await ClockCycles(dut.i_Clock, 50) - - # Verify using direct PC access - direct_pc = dut.cpu.r_PC.value.integer - assert direct_pc == test_pc, f"Boundary test failed: wrote {test_pc:#010x}, got {direct_pc:#010x}" - -@cocotb.test() -async def test_write_read_pc_integration(dut): - """Integration test: write PC values, then read them back""" - - clock = Clock(dut.i_Clock, wait_ns, "ns") - cocotb.start_soon(clock.start()) - - dut.i_Reset.value = 1 - await ClockCycles(dut.i_Clock, 1) - dut.i_Reset.value = 0 - await ClockCycles(dut.i_Clock, 1) - - # Test data: various PC values - test_pc_values = [ - 0x00001000, - 0x12345678, - 0xCAFEBABE, - 0x87654321, - 0x0000BEEF, - ] - - # Phase 1: Write all PC values and verify with direct access - for test_pc in test_pc_values: - await send_write_pc_command(dut, test_pc) - await ClockCycles(dut.i_Clock, 50) - - # Verify using direct PC access - direct_pc = dut.cpu.r_PC.value.integer - assert direct_pc == test_pc, f"Integration test write failed: wrote {test_pc:#010x}, PC contains {direct_pc:#010x}" - - # Verify using READ_PC command (if it works after WRITE_PC) - try: - read_pc = await read_pc_value(dut) - assert read_pc == test_pc, f"Integration test read failed: wrote {test_pc:#010x}, read back {read_pc:#010x}" - except AssertionError: - # READ_PC might have timing issues after WRITE_PC like READ_REGISTER after WRITE_REGISTER - # This is acceptable as long as direct PC access shows correct value - pass \ No newline at end of file