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/.github/workflows/test.yml b/.github/workflows/test.yml index 289f7bd..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: @@ -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: 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/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.v deleted file mode 100644 index 13f7e49..0000000 --- a/hdl/vga_out.v +++ /dev/null @@ -1,80 +0,0 @@ -`timescale 1ns / 1ps - -module vga_out #( - parameter BITS_PER_PIXEL = 4, - parameter FRAMEBUFFER_DEPTH = 640 * 480 -) ( - 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, - output o_Horizontal_Sync, - 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; - localparam SYNC_PULSE_H = 96; - localparam BACK_PORCH_H = 48; - localparam TOTAL_H = VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H + BACK_PORCH_H; // Should be 800 - - // Vertical timing for 640x480 @ 60Hz (approx 25MHz pixel clock) - localparam VISIBLE_V = 480; - localparam FRONT_PORCH_V = 10; - localparam SYNC_PULSE_V = 2; - localparam BACK_PORCH_V = 33; - localparam TOTAL_V = VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V + BACK_PORCH_V; // Should be 525 - - // Vertical and horizontal counter registers - reg [15:0] r_H_Counter = 0; - reg [15:0] r_V_Counter = 0; - - - localparam [1:0] STATE_VISIBLE = 0; - localparam [1:0] STATE_FRONT_PORCH = 1; - localparam [1:0] STATE_SYNC = 2; - localparam [1:0] STATE_BACK_PORCH = 3; - - reg [1:0] r_H_State; - reg [1:0] r_V_State; - - wire w_Visible = r_H_State == STATE_VISIBLE && r_V_State == STATE_VISIBLE; - - always @* begin - // Determine the state of the horizontal and vertical counters - if (r_H_Counter < VISIBLE_H) r_H_State = STATE_VISIBLE; - else if (r_H_Counter < VISIBLE_H + FRONT_PORCH_H) r_H_State = STATE_FRONT_PORCH; - else if (r_H_Counter < VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H) r_H_State = STATE_SYNC; - else r_H_State = STATE_BACK_PORCH; - - if (r_V_Counter < VISIBLE_V) r_V_State = STATE_VISIBLE; - else if (r_V_Counter < VISIBLE_V + FRONT_PORCH_V) r_V_State = STATE_FRONT_PORCH; - else if (r_V_Counter < VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V) r_V_State = STATE_SYNC; - 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; - end - 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 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/hdl/vga_out/vga_out.v b/hdl/vga_out/vga_out.v new file mode 100644 index 0000000..f495a16 --- /dev/null +++ b/hdl/vga_out/vga_out.v @@ -0,0 +1,159 @@ +`timescale 1ns / 1ps + +module vga_out #( + parameter BITS_PER_COLOR_CHANNEL = 4 +) ( + input i_Reset, + input i_Clock, + + // 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 +); + + // Horizontal timing for 640x480 @ 60Hz (approx 25MHz pixel clock) + localparam VISIBLE_H = 640; + localparam FRONT_PORCH_H = 16; + localparam SYNC_PULSE_H = 96; + localparam BACK_PORCH_H = 48; + localparam TOTAL_H = VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H + BACK_PORCH_H; // Should be 800 + + // Vertical timing for 640x480 @ 60Hz (approx 25MHz pixel clock) + localparam VISIBLE_V = 480; + localparam FRONT_PORCH_V = 10; + localparam SYNC_PULSE_V = 2; + 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) + reg r_Blanking_Prefetch_Done = 0; + + localparam [1:0] STATE_VISIBLE = 0; + localparam [1:0] STATE_FRONT_PORCH = 1; + localparam [1:0] STATE_SYNC = 2; + localparam [1:0] STATE_BACK_PORCH = 3; + + reg [1:0] r_H_State; + reg [1:0] r_V_State; + + wire w_Visible = r_H_State == STATE_VISIBLE && r_V_State == STATE_VISIBLE; + + always @* begin + // Determine the state of the horizontal and vertical counters + if (r_H_Counter < VISIBLE_H) r_H_State = STATE_VISIBLE; + else if (r_H_Counter < VISIBLE_H + FRONT_PORCH_H) r_H_State = STATE_FRONT_PORCH; + else if (r_H_Counter < VISIBLE_H + FRONT_PORCH_H + SYNC_PULSE_H) r_H_State = STATE_SYNC; + else r_H_State = STATE_BACK_PORCH; + + if (r_V_Counter < VISIBLE_V) r_V_State = STATE_VISIBLE; + else if (r_V_Counter < VISIBLE_V + FRONT_PORCH_V) r_V_State = STATE_FRONT_PORCH; + else if (r_V_Counter < VISIBLE_V + FRONT_PORCH_V + SYNC_PULSE_V) r_V_State = STATE_SYNC; + else r_V_State = STATE_BACK_PORCH; + end + + wire w_Pixel_Clock_Tick = (r_Clock_Counter == 3); + + 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 (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 + 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 + + 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_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 begin + r_Fill_Addr <= VISIBLE_H; + end + end + end + + 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 + + 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 = 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 + +endmodule diff --git a/tests/Makefile b/tests/Makefile index 6fa07e8..7f7a1a4 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -71,8 +71,10 @@ 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_comprehensive" TEST_TYPE ?= unit @@ -83,6 +85,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 +101,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_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 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..1e37f2c --- /dev/null +++ b/tests/vga/constants.py @@ -0,0 +1,17 @@ +# 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 +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 + +# 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/__init__.py b/tests/vga/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 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..b60f879 --- /dev/null +++ b/tests/vga/unit_tests/test_vga_comprehensive.py @@ -0,0 +1,371 @@ +import cocotb +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).""" + while True: + await RisingEdge(dut.i_Clock) + if int(dut.vga_out.r_Clock_Counter.value) == 0: + 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 + 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): + 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_simple_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) + + 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)) + + # 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 + + # 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) + + await vdma_task + + +@cocotb.test() +async def test_vga_pattern_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) + + 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 + + +@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/unit_tests/vga_unit_tests_harness.v b/tests/vga/unit_tests/vga_unit_tests_harness.v new file mode 100644 index 0000000..5034091 --- /dev/null +++ b/tests/vga/unit_tests/vga_unit_tests_harness.v @@ -0,0 +1,41 @@ +`timescale 1ns / 1ps + +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; + + initial begin + i_Reset = 1'b1; + 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), + .i_Reset(i_Reset), + .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..097667a --- /dev/null +++ b/tests/vga/utils.py @@ -0,0 +1,51 @@ +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, wait_for_fsync_pulse=True +): + if wait_for_fsync_pulse: + 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 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