From 797596ae25c0651324055ee59d53a3f8176fdb6c Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 21 Jun 2023 11:50:57 -0700 Subject: [PATCH] Normalize segment data and pass everything through deflicker and vsync --- rtl/gameandwatch.qip | 1 + rtl/video/lcd.sv | 144 ++++++++++++++++++----------------------- rtl/video/normalize.sv | 53 +++++++++++++++ rtl/video/segments.sv | 38 ++--------- 4 files changed, 123 insertions(+), 113 deletions(-) create mode 100644 rtl/video/normalize.sv diff --git a/rtl/gameandwatch.qip b/rtl/gameandwatch.qip index 0c49749..9332bc5 100644 --- a/rtl/gameandwatch.qip +++ b/rtl/gameandwatch.qip @@ -7,6 +7,7 @@ set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) " set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) "input_config.sv"] set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) "video/counts.sv"] set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) "video/lcd.sv"] +set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) "video/normalize.sv"] set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) "video/rgb_controller.sv"] set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) "video/segments.sv"] set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) "video/video.sv"] diff --git a/rtl/video/lcd.sv b/rtl/video/lcd.sv index 6ac0bff..980ede1 100644 --- a/rtl/video/lcd.sv +++ b/rtl/video/lcd.sv @@ -26,34 +26,48 @@ module lcd ( output wire segment_en ); - // Segments, over all H's, as last seen - reg [15:0] segment_a[4]; - reg [15:0] segment_b[4]; - reg [1:0] segment_bs; - - reg [15:0] cache_segment_a[4]; - reg [15:0] cache_segment_b[4]; - reg [1:0] cache_segment_bs; - - reg [3:0] w_prime[9]; - reg [3:0] w_main[9]; - - reg [3:0] cache_w_prime[9]; - reg [3:0] cache_w_main[9]; - - reg [4:0] decay_w_prime[9][4]; - reg [4:0] decay_w_main[9][4]; - - reg prev_divider_1khz = 0; - - // Comb - reg [1:0] current_h_index; - reg prev_vblank = 0; - localparam DECAY_MAX = 5'h1F; localparam DECAY_MIN_DISPLAY = 5'h10; - segments segments ( + localparam MAX_X_SEGMENT = 9; + localparam MAX_Y_SEGMENT = 16; + localparam MAX_Z_SEGMENT = 4; + + wire [MAX_Z_SEGMENT-1:0] raw_segments[MAX_X_SEGMENT][MAX_Y_SEGMENT]; + reg [MAX_Z_SEGMENT-1:0] decayed_segments[MAX_X_SEGMENT][MAX_Y_SEGMENT]; + reg [MAX_Z_SEGMENT-1:0] vsync_segments[MAX_X_SEGMENT][MAX_Y_SEGMENT]; + + reg [4:0] segment_current_decays[MAX_X_SEGMENT][MAX_Y_SEGMENT][MAX_Z_SEGMENT-1:0]; + + reg prev_divider_1khz = 0; + reg prev_vblank = 0; + + normalize #( + .MAX_X_SEGMENT(MAX_X_SEGMENT), + .MAX_Y_SEGMENT(MAX_Y_SEGMENT), + .MAX_Z_SEGMENT(MAX_Z_SEGMENT) + ) normalize ( + .clk(clk), + + .cpu_id(cpu_id), + + .current_segment_a (current_segment_a), + .current_segment_b (current_segment_b), + .current_segment_bs(current_segment_bs), + + .current_w_prime(current_w_prime), + .current_w_main (current_w_main), + + .output_lcd_h_index(output_lcd_h_index), + + .segments(raw_segments) + ); + + segments #( + .MAX_X_SEGMENT(MAX_X_SEGMENT), + .MAX_Y_SEGMENT(MAX_Y_SEGMENT), + .MAX_Z_SEGMENT(MAX_Z_SEGMENT) + ) lcd_segments ( .clk(clk), .cpu_id(cpu_id), @@ -61,13 +75,7 @@ module lcd ( .mask_data_wr(mask_data_wr), .mask_data(mask_data), - // Segments - .cache_segment_a (cache_segment_a), - .cache_segment_b (cache_segment_b), - .cache_segment_bs(cache_segment_bs), - - .cache_w_prime(cache_w_prime), - .cache_w_main (cache_w_main), + .segments(vsync_segments), .vblank_int(vblank_int), .hblank_int(hblank_int), @@ -78,67 +86,39 @@ module lcd ( ); always @(posedge clk) begin - int i; - int j; + int x, y, z; prev_vblank <= vblank_int; prev_divider_1khz <= divider_1khz; - // TODO: This is very similar to the logic already in `ram.sv` - segment_a[output_lcd_h_index] <= current_segment_a; - segment_b[output_lcd_h_index] <= current_segment_b; - segment_bs[output_lcd_h_index] <= current_segment_bs; - - if (output_lcd_h_index[0]) begin - // W' - w_prime <= current_w_prime; - end else begin - // W - w_main <= current_w_main; - end - + // Pass raw segments through deflicker stage if (divider_1khz && ~prev_divider_1khz) begin - for (i = 0; i < 9; i += 1) begin - for (j = 0; j < 4; j += 1) begin - // W' - // Modify decay - if (w_prime[i][j] && decay_w_prime[i][j] < DECAY_MAX) begin - // Segment is on, and decay isn't max - // Increment decay - decay_w_prime[i][j] <= decay_w_prime[i][j] + 5'h1; - end else if (~w_prime[i][j] && decay_w_prime[i][j] > 5'h0) begin - // Segment is off, and decay isn't min - // Decrement decay - decay_w_prime[i][j] <= decay_w_prime[i][j] - 5'h1; + for (x = 0; x < MAX_X_SEGMENT; x += 1) begin + for (y = 0; y < MAX_Y_SEGMENT; y += 1) begin + for (z = 0; z < MAX_Z_SEGMENT; z += 1) begin + reg [4:0] current_decay; + current_decay = segment_current_decays[x][y][z]; + + if (raw_segments[x][y][z] && current_decay < DECAY_MAX) begin + // Segment is on, and decay isn't max + // Increment decay + segment_current_decays[x][y][z] <= current_decay + 5'h1; + end else if (~raw_segments[x][y][z] && current_decay > 5'h0) begin + // Segment is off, and decay isn't min + // Decrement decay + segment_current_decays[x][y][z] <= current_decay - 5'h1; + end + + // Update segment array (an iteration delayed) + decayed_segments[x][y][z] <= current_decay > DECAY_MIN_DISPLAY; end - - // Update segment array (an iteration delayed) - cache_w_prime[i][j] <= decay_w_prime[i][j] > DECAY_MIN_DISPLAY; - - // W - if (w_main[i][j] && decay_w_main[i][j] < DECAY_MAX) begin - decay_w_main[i][j] <= decay_w_main[i][j] + 5'h1; - end else if (~w_main[i][j] && decay_w_main[i][j] > 5'h0) begin - decay_w_main[i][j] <= decay_w_main[i][j] - 5'h1; - end - - cache_w_main[i][j] <= decay_w_main[i][j] > DECAY_MIN_DISPLAY; end end end + // Pass deflickered segment through vblank buffer stage if (vblank_int && ~prev_vblank) begin - cache_segment_a[0] <= segment_a[0]; - cache_segment_a[1] <= segment_a[1]; - cache_segment_a[2] <= segment_a[2]; - cache_segment_a[3] <= segment_a[3]; - - cache_segment_b[0] <= segment_b[0]; - cache_segment_b[1] <= segment_b[1]; - cache_segment_b[2] <= segment_b[2]; - cache_segment_b[3] <= segment_b[3]; - - cache_segment_bs <= segment_bs; + vsync_segments <= decayed_segments; end end endmodule diff --git a/rtl/video/normalize.sv b/rtl/video/normalize.sv new file mode 100644 index 0000000..13db562 --- /dev/null +++ b/rtl/video/normalize.sv @@ -0,0 +1,53 @@ +module normalize #( + parameter MAX_X_SEGMENT = 9, + parameter MAX_Y_SEGMENT = 16, + parameter MAX_Z_SEGMENT = 4 +) ( + input wire clk, + + input wire [3:0] cpu_id, + + input wire [15:0] current_segment_a, + input wire [15:0] current_segment_b, + input wire current_segment_bs, + + input wire [3:0] current_w_prime[9], + input wire [3:0] current_w_main [9], + + input wire [1:0] output_lcd_h_index, + + // Z is used for one hot bit selection, hence the width + output reg [MAX_Z_SEGMENT-1:0] segments[MAX_X_SEGMENT][MAX_Y_SEGMENT] +); + + always @(posedge clk) begin + int x, y, z; + + case (cpu_id) + 4: begin + // SM5a + for (x = 0; x < MAX_X_SEGMENT; x += 1) begin + // Only 4 Y segments + for (y = 0; y < 4; y += 1) begin + if (output_lcd_h_index[0]) begin + segments[x][y][0] <= current_w_main[x][y]; + end else begin + segments[x][y][1] <= current_w_prime[x][y]; + end + end + end + end + default: begin + // SM510 + for (y = 0; y < MAX_Y_SEGMENT; y += 1) begin + segments[0][y][output_lcd_h_index] <= current_segment_a[y]; + segments[1][y][output_lcd_h_index] <= current_segment_b[y]; + + // BS is a special case as a single bit, and is set for all Y + segments[2][y][output_lcd_h_index] <= current_segment_bs; + end + end + endcase + end + +endmodule diff --git a/rtl/video/segments.sv b/rtl/video/segments.sv index 76c6773..ffe558e 100644 --- a/rtl/video/segments.sv +++ b/rtl/video/segments.sv @@ -1,4 +1,8 @@ -module segments ( +module segments #( + parameter MAX_X_SEGMENT = 9, + parameter MAX_Y_SEGMENT = 16, + parameter MAX_Z_SEGMENT = 4 +) ( input wire clk, input wire [3:0] cpu_id, @@ -6,13 +10,7 @@ module segments ( input wire mask_data_wr, input wire [15:0] mask_data, - // Segments - input wire [15:0] cache_segment_a [4], - input wire [15:0] cache_segment_b [4], - input wire [ 1:0] cache_segment_bs, - - input wire [3:0] cache_w_prime[9], - input wire [3:0] cache_w_main [9], + input wire [MAX_Z_SEGMENT-1:0] segments[MAX_X_SEGMENT][MAX_Y_SEGMENT], // Video counters input wire vblank_int, @@ -53,29 +51,7 @@ module segments ( segment_en = 0; if (has_segment) begin - case (cpu_id) - 4: begin - // SM5a - if (segment_row == 2'h1) begin - // W' - segment_en = cache_w_prime[segment_line_select][segment_column]; - end else begin - // W - segment_en = cache_w_main[segment_line_select][segment_column]; - end - end - default: begin - // SM510 - case (segment_line_select) - 4'h0: segment_en = cache_segment_a[segment_row][segment_column]; - 4'h1: segment_en = cache_segment_b[segment_row][segment_column]; - 4'h2: segment_en = cache_segment_bs[segment_row]; - default: begin - // TODO: What happens in these cases? - end - endcase - end - endcase + segment_en = segments[segment_line_select][segment_column][segment_row]; end end endmodule