v0.1.5: fix bump_version.py BOM/encoding/CRLF, fix double banner on power-up

This commit is contained in:
David Rice
2026-06-10 13:12:57 +02:00
parent 134d1e7b7f
commit 648470fe35
3 changed files with 120 additions and 84 deletions

View File

@@ -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()

126
top.v
View File

@@ -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;

View File

@@ -1 +1 @@
0.1.0
0.1.5