Skip to content

Commit 8b8a688

Browse files
authored
Merge pull request #4 from sysprog21/coordinate-validation
Add defense-in-depth coordinate validation
2 parents 9436223 + 14c02c5 commit 8b8a688

File tree

2 files changed

+203
-23
lines changed

2 files changed

+203
-23
lines changed

rtl/nyancat.v

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,41 @@ module nyancat (
280280
);
281281
end
282282
end
283+
284+
// Assertion 9: Coordinate transformation validity (defense-in-depth)
285+
// When in_display=1, verify that coordinate transformations are correct:
286+
// - src_x and src_y must be within [0, FRAME_W-1] and [0, FRAME_H-1]
287+
// - rel_x and rel_y must be within [0, SCALED_W-1] and [0, SCALED_H-1]
288+
// This prevents wild pointer crashes in framebuffer updates
289+
/* verilator lint_off WIDTHEXPAND */
290+
always @(posedge px_clk)
291+
if (past_valid && !reset && !$past(reset) && in_display) begin
292+
if (src_x >= FRAME_W)
293+
$error(
294+
"[ASSERTION FAILED] src_x=%0d exceeds FRAME_W=%0d (in_display=1)",
295+
src_x,
296+
FRAME_W
297+
);
298+
if (src_y >= FRAME_H)
299+
$error(
300+
"[ASSERTION FAILED] src_y=%0d exceeds FRAME_H=%0d (in_display=1)",
301+
src_y,
302+
FRAME_H
303+
);
304+
if (rel_x >= SCALED_W)
305+
$error(
306+
"[ASSERTION FAILED] rel_x=%0d exceeds SCALED_W=%0d (in_display=1)",
307+
rel_x,
308+
SCALED_W
309+
);
310+
if (rel_y >= SCALED_H)
311+
$error(
312+
"[ASSERTION FAILED] rel_y=%0d exceeds SCALED_H=%0d (in_display=1)",
313+
rel_y,
314+
SCALED_H
315+
);
316+
end
317+
/* verilator lint_on WIDTHEXPAND */
283318
`endif
284319

285320
endmodule

sim/main.cpp

Lines changed: 168 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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
759873
inline 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

Comments
 (0)