diff --git a/bump_version.py b/bump_version.py index 15fd3d6..2053507 100644 --- a/bump_version.py +++ b/bump_version.py @@ -1,61 +1,43 @@ #!/usr/bin/env python3 -"""Increment the PATCH component of version.txt and update banner_char in top.v.""" - import re import sys from pathlib import Path VERSION_FILE = Path("version.txt") TOP_V_FILE = Path("top.v") - -# Fixed 9-character field in banner_char; indices 343-351. VERSION_FIELD_LEN = 9 -VERSION_START_IDX = 343 - - -def read_version(): - if not VERSION_FILE.exists(): - sys.exit(f"Error: {VERSION_FILE} not found") - return VERSION_FILE.read_text().strip() - - -def bump_patch(version): - parts = version.split(".") - if len(parts) != 3 or not all(p.isdigit() for p in parts): - sys.exit(f"Error: unexpected version format '{version}'") - major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2]) - return f"{major}.{minor}.{patch + 1}" - - -def update_top_v(new_version): - if not TOP_V_FILE.exists(): - sys.exit(f"Error: {TOP_V_FILE} not found") - - padded = new_version.ljust(VERSION_FIELD_LEN) - if len(padded) > VERSION_FIELD_LEN: - sys.exit(f"Error: version '{new_version}' exceeds {VERSION_FIELD_LEN} characters") - - content = TOP_V_FILE.read_text() - - for offset, ch in enumerate(padded): - idx = VERSION_START_IDX + offset - pattern = rf"(9'd{idx}:\s+banner_char\s*=\s*)[^\n]+;" - replacement = rf'\g<1>"{ch}";' - new_content = re.sub(pattern, replacement, content) - if new_content == content: - sys.exit(f"Error: could not find banner_char entry for index {idx} in {TOP_V_FILE}") - content = new_content - - TOP_V_FILE.write_text(content) - def main(): - old_version = read_version() - new_version = bump_patch(old_version) - update_top_v(new_version) - VERSION_FILE.write_text(new_version + "\n") - print(f"Version bumped to {new_version}") + if not VERSION_FILE.exists(): + sys.exit("Error: version.txt not found") + if not TOP_V_FILE.exists(): + sys.exit("Error: top.v not found") + old_version = VERSION_FILE.read_text(encoding='utf-8').strip() + parts = old_version.split(".") + if len(parts) != 3 or not all(p.isdigit() for p in parts): + sys.exit(f"Error: unexpected version format '{old_version}'") + new_version = f"{parts[0]}.{parts[1]}.{int(parts[2])+1}" + padded = new_version.ljust(VERSION_FIELD_LEN) + + content = TOP_V_FILE.read_text(encoding='utf-8-sig', newline='\n') + + m = re.search(r'version chars at indices (\d+)', content) + if not m: + sys.exit("Error: could not find 'version chars at indices' comment in top.v") + start_idx = int(m.group(1)) + + for offset, ch in enumerate(padded): + idx = start_idx + offset + pattern = rf"(9'd{idx}:\s+banner_char\s*=\s*)[^\n]+;" + new_content, n = re.subn(pattern, lambda mo, c=ch: mo.group(1) + f'"{c}";', content) + if n == 0: + sys.exit(f"Error: could not find banner_char entry for index {idx}") + content = new_content + + TOP_V_FILE.write_text(content, encoding='utf-8', newline='\n') + VERSION_FILE.write_text(new_version + "\n", encoding='utf-8') + print(f"Version bumped to {new_version}") if __name__ == "__main__": main() diff --git a/top.v b/top.v index 63a4e0a..1a93748 100644 --- a/top.v +++ b/top.v @@ -6,31 +6,40 @@ // clk_50mhz - onboard oscillator // rx_clk - DS90CF386 RxCLKOUT (~72 MHz pixel clock) // de - DS90CF386 RxOUT24 -// vsync - DS90CF386 RxOUT25 (currently unused, brought in for future use) -// hsync - DS90CF386 RxOUT26 (currently unused, brought in for future use) +// vsync - DS90CF386 RxOUT25 (unused, kept for pin assignment) +// hsync - DS90CF386 RxOUT26 (unused, kept for pin assignment) +// cntl - DS90CF386 RxOUT27 (unused, kept for pin assignment) +// red[7:0] - DS90CF386 red channel +// green[7:0]- DS90CF386 green channel +// blue[7:0] - DS90CF386 blue channel // rst_n_pin - optional external reset (tie high if not used) // // Output: // uart_tx_pin - to CH340 RX // // Frame reports: -// - On a clean frame, sends "OK\n" once every HEARTBEAT_FRAMES frames. +// - On a clean frame, sends "OK\r\n" once every HEARTBEAT_FRAMES frames. // - On an anomalous frame, immediately sends -// "ERR lines=NNNN width=NNNN\n" -// where NNNN are zero-padded 4-digit decimals. +// "ERR lines=NNNN width=NNNN R=RR G=GG B=BB\r\n" +// where NNNN are zero-padded 4-digit decimals and RR/GG/BB are +// 2-digit uppercase hex of the first pixel of the anomalous frame. module top ( - input wire clk_50mhz, - input wire rx_clk, - input wire de, - input wire vsync, - input wire hsync, - input wire rst_n_pin, - output wire uart_tx_pin + input wire clk_50mhz, + input wire rx_clk, + input wire de, + input wire vsync, + input wire hsync, + input wire cntl, + input wire [7:0] red, + input wire [7:0] green, + input wire [7:0] blue, + input wire rst_n_pin, + output wire uart_tx_pin ); - // unused for now — keep ports alive so pin assignments stick - wire _unused = &{1'b0, vsync, hsync, 1'b0}; + // keep undriven ports alive so pin assignments stick + wire _unused = &{1'b0, vsync, hsync, cntl, 1'b0}; localparam integer HEARTBEAT_FRAMES = 60; @@ -65,6 +74,9 @@ module top ( wire [15:0] lines_pix; wire [15:0] width_pix; wire anomaly_pix; + wire [7:0] first_r_pix; + wire [7:0] first_g_pix; + wire [7:0] first_b_pix; de_monitor #( .EXPECTED_LINES (16'd800), @@ -74,10 +86,16 @@ module top ( .pix_clk (rx_clk), .rst_n (rst_n_pix), .de (de), + .red (red), + .green (green), + .blue (blue), .frame_done (frame_done_pix), .lines_o (lines_pix), .width_o (width_pix), - .anomaly_o (anomaly_pix) + .anomaly_o (anomaly_pix), + .first_r (first_r_pix), + .first_g (first_g_pix), + .first_b (first_b_pix) ); // ---------------------------------------------------------------- @@ -87,8 +105,11 @@ module top ( reg [15:0] width_lat; reg anomaly_lat; reg heartbeat_lat; + reg [7:0] fr_lat; + reg [7:0] fg_lat; + reg [7:0] fb_lat; reg req_tog; - reg [7:0] hb_count; // up to 255 — plenty for HEARTBEAT_FRAMES=60 + reg [7:0] hb_count; // up to 255 — plenty for HEARTBEAT_FRAMES=60 always @(posedge rx_clk or negedge rst_n_pix) begin if (!rst_n_pix) begin @@ -96,12 +117,18 @@ module top ( width_lat <= 16'd0; anomaly_lat <= 1'b0; heartbeat_lat <= 1'b0; + fr_lat <= 8'd0; + fg_lat <= 8'd0; + fb_lat <= 8'd0; req_tog <= 1'b0; hb_count <= 8'd0; end else if (frame_done_pix) begin lines_lat <= lines_pix; width_lat <= width_pix; anomaly_lat <= anomaly_pix; + fr_lat <= first_r_pix; + fg_lat <= first_g_pix; + fb_lat <= first_b_pix; if (hb_count == HEARTBEAT_FRAMES - 1) begin hb_count <= 8'd0; @@ -131,8 +158,11 @@ module top ( reg [15:0] width_u; reg anomaly_u; reg heartbeat_u; + reg [7:0] fr_u; + reg [7:0] fg_u; + reg [7:0] fb_u; - // BCD digit registers — computed in F_CONVERT before transmission + // BCD digit registers — computed in F_CONVERT before transmission reg [3:0] L0_r, L1_r, L2_r, L3_r; reg [3:0] W0_r, W1_r, W2_r, W3_r; reg [1:0] conv_step; @@ -140,18 +170,24 @@ module top ( // Sample latched fields when req_edge fires. Data is stable in // pix domain until the next frame_done (~16 ms), far longer than we - // need for the handful of µs of UART setup. + // need for the handful of µs of UART setup. always @(posedge clk_uart or negedge rst_n_uart) begin if (!rst_n_uart) begin lines_u <= 16'd0; width_u <= 16'd0; anomaly_u <= 1'b0; heartbeat_u <= 1'b0; + fr_u <= 8'd0; + fg_u <= 8'd0; + fb_u <= 8'd0; end else if (req_edge) begin lines_u <= lines_lat; width_u <= width_lat; anomaly_u <= anomaly_lat; heartbeat_u <= heartbeat_lat; + fr_u <= fr_lat; + fg_u <= fg_lat; + fb_u <= fb_lat; end end @@ -159,8 +195,7 @@ module top ( localparam [2:0] F_IDLE = 3'd0, F_CONVERT = 3'd1, F_LOAD = 3'd2, - F_WAIT = 3'd3, - F_BANNER = 3'd4; + F_WAIT = 3'd3; // Banner: ESC[2J ESC[H ESC[33m + 2x51-dash separators + info lines + ESC[0m localparam [8:0] BANNER_LEN = 9'd411; @@ -174,7 +209,15 @@ module top ( reg [7:0] tx_byte; wire tx_busy; - // ERR layout: ESC[31m + "ERR lines=LLLL width=WWWW" + ESC[0m + \r\n (36 bytes) + // Nibble (0-15) to uppercase ASCII hex digit + function [7:0] hex_char(input [3:0] n); + hex_char = (n <= 4'd9) ? ("0" + {4'h0, n}) : ("A" - 8'd10 + {4'h0, n}); + endfunction + + // ERR layout (51 bytes): + // ESC[31m(5) + "ERR lines="(10) + LLLL(4) + " width="(7) + WWWW(4) + // + " R="(3) + RR(2) + " G="(3) + GG(2) + " B="(3) + BB(2) + // + ESC[0m(4) + \r\n(2) function [7:0] err_char(input [7:0] i); case (i) 8'd0: err_char = 8'h1B; @@ -207,12 +250,27 @@ module top ( 8'd27: err_char = "0" + W1_r; 8'd28: err_char = "0" + W2_r; 8'd29: err_char = "0" + W3_r; - 8'd30: err_char = 8'h1B; - 8'd31: err_char = "["; - 8'd32: err_char = "0"; - 8'd33: err_char = "m"; - 8'd34: err_char = 8'h0D; - 8'd35: err_char = 8'h0A; + 8'd30: err_char = " "; + 8'd31: err_char = "R"; + 8'd32: err_char = "="; + 8'd33: err_char = hex_char(fr_u[7:4]); + 8'd34: err_char = hex_char(fr_u[3:0]); + 8'd35: err_char = " "; + 8'd36: err_char = "G"; + 8'd37: err_char = "="; + 8'd38: err_char = hex_char(fg_u[7:4]); + 8'd39: err_char = hex_char(fg_u[3:0]); + 8'd40: err_char = " "; + 8'd41: err_char = "B"; + 8'd42: err_char = "="; + 8'd43: err_char = hex_char(fb_u[7:4]); + 8'd44: err_char = hex_char(fb_u[3:0]); + 8'd45: err_char = 8'h1B; + 8'd46: err_char = "["; + 8'd47: err_char = "0"; + 8'd48: err_char = "m"; + 8'd49: err_char = 8'h0D; + 8'd50: err_char = 8'h0A; default: err_char = 8'h00; endcase endfunction @@ -576,7 +634,7 @@ module top ( 9'd344: banner_char = "."; 9'd345: banner_char = "1"; 9'd346: banner_char = "."; - 9'd347: banner_char = "0"; + 9'd347: banner_char = "5"; 9'd348: banner_char = " "; 9'd349: banner_char = " "; 9'd350: banner_char = " "; @@ -605,7 +663,7 @@ module top ( always @(posedge clk_uart or negedge rst_n_uart) begin if (!rst_n_uart) begin - fstate <= F_BANNER; + fstate <= F_LOAD; idx <= 9'd0; msg_len <= BANNER_LEN; is_banner <= 1'b1; @@ -620,16 +678,12 @@ module top ( end else begin tx_start <= 1'b0; case (fstate) - F_BANNER: begin - // idx/msg_len/is_banner already set in reset block; kick off load - fstate <= F_LOAD; - end F_IDLE: begin is_banner <= 1'b0; - if (req_edge_q && (anomaly_u || heartbeat_u)) begin + if (req_edge_q && (anomaly_u || heartbeat_u) && !is_banner) begin idx <= 9'd0; is_err_msg <= anomaly_u; - msg_len <= anomaly_u ? 9'd36 : 9'd13; + msg_len <= anomaly_u ? 9'd51 : 9'd13; l_rem <= lines_u; w_rem <= width_u; conv_step <= 2'd0; @@ -705,7 +759,7 @@ module top ( else if (w_rem >= 16'd10) begin W2_r <= 4'd1; w_rem <= w_rem - 16'd10; end else W2_r <= 4'd0; end - 2'd3: begin // units — remainder is 0-9 by construction + 2'd3: begin // units — remainder is 0-9 by construction L3_r <= l_rem[3:0]; W3_r <= w_rem[3:0]; fstate <= F_LOAD; diff --git a/version.txt b/version.txt index 6e8bf73..9faa1b7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.1.0 +0.1.5