@@ -322,8 +322,7 @@ class TimingMonitor
322322// Sync Signal State Validator: Glitch detection and phase-aware diagnostics
323323//
324324// Complements TimingMonitor by detecting single-cycle glitches and providing
325- // detailed phase context for sync signal errors. Inspired by ZipCPU vgasim
326- // Pattern 4.
325+ // detailed phase context for sync signal errors.
327326//
328327// Design principles:
329328// - Track estimated hc/vc position based on edge counts
@@ -521,6 +520,112 @@ class SyncValidator
521520 }
522521};
523522
523+ // Coordinate Validator: Defense-in-depth bounds checking for framebuffer access
524+ //
525+ // Validates coordinates before every framebuffer write to prevent wild pointer
526+ // crashes. Complements RTL assertions with C++ side validation.
527+ //
528+ // Design principles:
529+ // - Validate hpos/vpos against screen resolution before framebuffer access
530+ // - Accumulate error count and auto-stop at threshold (10 errors)
531+ // - Report errors with coordinate context for debugging
532+ // - Silent after first frame to avoid spam
533+ class CoordinateValidator
534+ {
535+ private:
536+ int error_count = 0 ;
537+ bool silent_mode = false ;
538+ bool frame_complete = false ;
539+ static constexpr int ERROR_THRESHOLD = 10 ;
540+
541+ public:
542+ CoordinateValidator () = default ;
543+
544+ // Validate coordinates before framebuffer access
545+ // Returns true if coordinates are valid, false otherwise
546+ bool validate (int hpos, int vpos, int row_base)
547+ {
548+ bool valid = true ;
549+
550+ // Check horizontal bounds
551+ if (hpos < 0 || hpos >= H_RES) {
552+ if (!silent_mode && error_count < ERROR_THRESHOLD) {
553+ fprintf (stderr,
554+ " [COORDINATE ERROR] hpos=%d out of bounds [0, %d)\n " ,
555+ hpos, H_RES);
556+ error_count++;
557+ }
558+ valid = false ;
559+ }
560+
561+ // Check vertical bounds
562+ if (vpos < 0 || vpos >= V_RES) {
563+ if (!silent_mode && error_count < ERROR_THRESHOLD) {
564+ fprintf (stderr,
565+ " [COORDINATE ERROR] vpos=%d out of bounds [0, %d)\n " ,
566+ vpos, V_RES);
567+ error_count++;
568+ }
569+ valid = false ;
570+ }
571+
572+ // Check row_base consistency
573+ // row_base should match (vpos * H_RES) << 2 when in valid range
574+ if (vpos >= 0 && vpos < V_RES) {
575+ int expected_row_base = (vpos * H_RES) << 2 ;
576+ if (row_base != expected_row_base) {
577+ if (!silent_mode && error_count < ERROR_THRESHOLD) {
578+ fprintf (stderr,
579+ " [COORDINATE ERROR] row_base mismatch: got %d, "
580+ " expected %d (vpos=%d)\n " ,
581+ row_base, expected_row_base, vpos);
582+ error_count++;
583+ }
584+ valid = false ;
585+ }
586+ }
587+
588+ // Check if threshold exceeded
589+ if (error_count >= ERROR_THRESHOLD) {
590+ if (!silent_mode) {
591+ fprintf (stderr,
592+ " [COORDINATE VALIDATOR] Error threshold reached (%d "
593+ " errors), stopping validation\n " ,
594+ ERROR_THRESHOLD);
595+ silent_mode = true ;
596+ }
597+ }
598+
599+ return valid;
600+ }
601+
602+ // Mark frame completion (called on vsync)
603+ void mark_frame_complete ()
604+ {
605+ if (!frame_complete) {
606+ frame_complete = true ;
607+ silent_mode = true ; // Only report errors from first frame
608+ }
609+ }
610+
611+ void report () const
612+ {
613+ if (error_count == 0 ) {
614+ std::cout << " PASS: Coordinate validation (no bounds errors)\n " ;
615+ } else {
616+ std::cout << " FAIL: Coordinate validation\n " ;
617+ std::cout << " Total coordinate errors: " << error_count << " \n " ;
618+ if (error_count >= ERROR_THRESHOLD) {
619+ std::cout << " (validation stopped at threshold)\n " ;
620+ }
621+ }
622+ }
623+
624+ bool has_errors () const { return error_count > 0 ; }
625+
626+ int get_error_count () const { return error_count; }
627+ };
628+
524629// Standalone PNG encoder (no external dependencies)
525630// Adapted from sysprog21/mado headless-ctl.c
526631
@@ -706,13 +811,15 @@ void print_usage(const char *prog)
706811 std::cout
707812 << " Usage: " << prog << " [options]\n "
708813 << " Options:\n "
709- << " --save-png <file> Save single frame to PNG and exit\n "
710- << " --trace <file.vcd> Enable VCD waveform tracing for debugging\n "
711- << " --trace-clocks <N> Limit VCD trace to first N clock cycles "
814+ << " --save-png <file> Save single frame to PNG and exit\n "
815+ << " --trace <file.vcd> Enable VCD waveform tracing for "
816+ " debugging\n "
817+ << " --trace-clocks <N> Limit VCD trace to first N clock cycles "
712818 " (default: 1 frame)\n "
713- << " --validate-timing Enable real-time VGA timing validation\n "
714- << " --validate-signals Enable sync signal glitch detection\n "
715- << " --help Show this help\n\n "
819+ << " --validate-timing Enable real-time VGA timing validation\n "
820+ << " --validate-signals Enable sync signal glitch detection\n "
821+ << " --validate-coordinates Enable coordinate bounds checking\n "
822+ << " --help Show this help\n\n "
716823 << " Interactive keys:\n "
717824 << " p - Save frame to test.png\n "
718825 << " ESC - Reset animation\n "
@@ -721,16 +828,21 @@ void print_usage(const char *prog)
721828 << " Generate: ./Vvga_nyancat --trace waves.vcd --trace-clocks 10000\n "
722829 << " View: surfer waves.vcd (or gtkwave waves.vcd)\n\n "
723830 << " Validation modes:\n "
724- << " --validate-timing Validates hsync/vsync pulse widths and "
831+ << " --validate-timing Validates hsync/vsync pulse widths and "
725832 " frame "
726833 " dimensions\n "
727- << " Tolerates ±1 clock/line jitter for "
834+ << " Tolerates ±1 clock/line jitter for "
728835 " real-world "
729836 " variations\n "
730- << " --validate-signals Detects glitches and validates sync signal "
837+ << " --validate-signals Detects glitches and validates sync "
838+ " signal "
731839 " state\n "
732- << " Phase-aware diagnostics with position "
733- " context\n " ;
840+ << " Phase-aware diagnostics with position "
841+ " context\n "
842+ << " --validate-coordinates Defense-in-depth coordinate bounds "
843+ " checking\n "
844+ << " Prevents wild pointer crashes "
845+ " (auto-stops at 10 errors)\n " ;
734846}
735847
736848// Simulate VGA frame generation with performance optimizations
@@ -756,6 +868,8 @@ void print_usage(const char *prog)
756868//
757869// Timing validation:
758870// - If monitor is non-null, calls monitor->tick() each clock for validation
871+ // - If coord_validator is non-null, validates coordinates before framebuffer
872+ // writes
759873inline void simulate_frame (Vvga_nyancat *top,
760874 uint8_t *fb,
761875 int &hpos,
@@ -764,7 +878,8 @@ inline void simulate_frame(Vvga_nyancat *top,
764878 VerilatedVcdC *trace = nullptr ,
765879 vluint64_t *trace_time = nullptr ,
766880 TimingMonitor *monitor = nullptr ,
767- SyncValidator *validator = nullptr )
881+ SyncValidator *validator = nullptr ,
882+ CoordinateValidator *coord_validator = nullptr )
768883{
769884 // Precompute row base address for current row
770885 int row_base = (vpos >= 0 && vpos < V_RES) ? (vpos * H_RES) << 2 : -1 ;
@@ -795,19 +910,32 @@ inline void simulate_frame(Vvga_nyancat *top,
795910 hpos = -H_BP;
796911 vpos = -V_BP;
797912 row_base = -1 ; // Reset row base (in blanking)
913+ // Mark frame completion for coordinate validator
914+ if (coord_validator)
915+ coord_validator->mark_frame_complete ();
798916 }
799917
800918 // Fast path: skip processing during blanking intervals
801919 // Only process when in active display region
802920 if (row_base >= 0 ) {
803921 if (hpos >= 0 && hpos < H_RES) {
804- // Direct framebuffer write using precomputed row base
805- int idx = row_base + (hpos << 2 );
806- uint8_t color = top->rrggbb ;
807- fb[idx] = vga2bit_to_8bit (color & 0b11 ); // B
808- fb[idx + 1 ] = vga2bit_to_8bit ((color >> 2 ) & 0b11 ); // G
809- fb[idx + 2 ] = vga2bit_to_8bit ((color >> 4 ) & 0b11 ); // R
810- fb[idx + 3 ] = 255 ; // A
922+ // Coordinate validation before framebuffer write
923+ // (defense-in-depth)
924+ bool coords_valid = true ;
925+ if (coord_validator)
926+ coords_valid =
927+ coord_validator->validate (hpos, vpos, row_base);
928+
929+ // Only update framebuffer if coordinates pass validation
930+ if (coords_valid) {
931+ // Direct framebuffer write using precomputed row base
932+ int idx = row_base + (hpos << 2 );
933+ uint8_t color = top->rrggbb ;
934+ fb[idx] = vga2bit_to_8bit (color & 0b11 ); // B
935+ fb[idx + 1 ] = vga2bit_to_8bit ((color >> 2 ) & 0b11 ); // G
936+ fb[idx + 2 ] = vga2bit_to_8bit ((color >> 4 ) & 0b11 ); // R
937+ fb[idx + 3 ] = 255 ; // A
938+ }
811939 }
812940 }
813941
@@ -831,6 +959,7 @@ int main(int argc, char **argv)
831959 bool save_and_exit = false ;
832960 bool validate_timing = false ;
833961 bool validate_signals = false ;
962+ bool validate_coordinates = false ;
834963 const char *output_file = " test.png" ;
835964 const char *trace_file = nullptr ;
836965 int trace_clocks = CLOCKS_PER_FRAME; // Default: 1 complete frame
@@ -848,6 +977,8 @@ int main(int argc, char **argv)
848977 validate_timing = true ;
849978 } else if (strcmp (argv[i], " --validate-signals" ) == 0 ) {
850979 validate_signals = true ;
980+ } else if (strcmp (argv[i], " --validate-coordinates" ) == 0 ) {
981+ validate_coordinates = true ;
851982 } else if (strcmp (argv[i], " --help" ) == 0 ) {
852983 print_usage (argv[0 ]);
853984 return EXIT_SUCCESS;
@@ -942,6 +1073,15 @@ int main(int argc, char **argv)
9421073 std::cout << " Glitch detection with phase-aware diagnostics\n " ;
9431074 }
9441075
1076+ // Initialize coordinate validator if requested
1077+ CoordinateValidator *coord_validator = nullptr ;
1078+ if (validate_coordinates) {
1079+ coord_validator = new CoordinateValidator ();
1080+ std::cout << " Coordinate validation enabled\n " ;
1081+ std::cout
1082+ << " Defense-in-depth bounds checking (auto-stops at 10 errors)\n " ;
1083+ }
1084+
9451085 bool quit = false ;
9461086
9471087 // Batch mode: generate one frame and exit
@@ -961,7 +1101,7 @@ int main(int argc, char **argv)
9611101 }
9621102
9631103 simulate_frame (top, fb_ptr, hpos, vpos, sim_clocks, trace, &trace_time,
964- monitor, validator);
1104+ monitor, validator, coord_validator );
9651105 if (trace) {
9661106 remaining_trace_clocks -= sim_clocks * 2 ; // 2 edges per clock
9671107 }
@@ -1004,7 +1144,7 @@ int main(int argc, char **argv)
10041144 // Simulate in smaller chunks for responsive input
10051145 // VCD tracing disabled in interactive mode (too much data)
10061146 simulate_frame (top, fb_ptr, hpos, vpos, 50000 , nullptr , nullptr ,
1007- monitor, validator);
1147+ monitor, validator, coord_validator );
10081148
10091149 // Update display after each simulation chunk
10101150 SDL_UpdateTexture (texture, nullptr , fb_ptr, H_RES * 4 );
@@ -1030,6 +1170,11 @@ int main(int argc, char **argv)
10301170 delete validator;
10311171 }
10321172
1173+ if (coord_validator) {
1174+ coord_validator->report ();
1175+ delete coord_validator;
1176+ }
1177+
10331178 if (trace) {
10341179 trace->close ();
10351180 delete trace;
0 commit comments