806 lines
34 KiB
Verilog
806 lines
34 KiB
Verilog
// top.v
|
|
//
|
|
// LVDS FlatLink monitor top level.
|
|
//
|
|
// Inputs:
|
|
// clk_50mhz - onboard oscillator
|
|
// rx_clk - DS90CF386 RxCLKOUT (~72 MHz pixel clock)
|
|
// de - DS90CF386 RxOUT24
|
|
// 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\r\n" once every HEARTBEAT_FRAMES frames.
|
|
// - On an anomalous frame, immediately sends
|
|
// "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 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
|
|
);
|
|
|
|
// keep undriven ports alive so pin assignments stick
|
|
wire _unused = &{1'b0, vsync, hsync, cntl, 1'b0};
|
|
|
|
localparam integer HEARTBEAT_FRAMES = 60;
|
|
|
|
// ----------------------------------------------------------------
|
|
// Clocking: UART domain runs directly off the 50 MHz oscillator.
|
|
// ----------------------------------------------------------------
|
|
wire clk_uart = clk_50mhz;
|
|
wire pll_locked = 1'b1;
|
|
|
|
// ----------------------------------------------------------------
|
|
// Reset synchronisers
|
|
// ----------------------------------------------------------------
|
|
reg [2:0] rst_sync_uart;
|
|
reg [2:0] rst_sync_pix;
|
|
|
|
always @(posedge clk_uart or negedge rst_n_pin) begin
|
|
if (!rst_n_pin) rst_sync_uart <= 3'b000;
|
|
else rst_sync_uart <= {rst_sync_uart[1:0], pll_locked};
|
|
end
|
|
wire rst_n_uart = rst_sync_uart[2];
|
|
|
|
always @(posedge rx_clk or negedge rst_n_pin) begin
|
|
if (!rst_n_pin) rst_sync_pix <= 3'b000;
|
|
else rst_sync_pix <= {rst_sync_pix[1:0], 1'b1};
|
|
end
|
|
wire rst_n_pix = rst_sync_pix[2];
|
|
|
|
// ----------------------------------------------------------------
|
|
// DE monitor in pixel-clock domain
|
|
// ----------------------------------------------------------------
|
|
wire frame_done_pix;
|
|
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),
|
|
.EXPECTED_WIDTH (16'd1280),
|
|
.FRAME_GAP_PIX (16'd2048)
|
|
) u_mon (
|
|
.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),
|
|
.first_r (first_r_pix),
|
|
.first_g (first_g_pix),
|
|
.first_b (first_b_pix)
|
|
);
|
|
|
|
// ----------------------------------------------------------------
|
|
// Pixel-domain: latch report fields + heartbeat, toggle req
|
|
// ----------------------------------------------------------------
|
|
reg [15:0] lines_lat;
|
|
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
|
|
|
|
always @(posedge rx_clk or negedge rst_n_pix) begin
|
|
if (!rst_n_pix) begin
|
|
lines_lat <= 16'd0;
|
|
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;
|
|
heartbeat_lat <= 1'b1;
|
|
end else begin
|
|
hb_count <= hb_count + 8'd1;
|
|
heartbeat_lat <= 1'b0;
|
|
end
|
|
req_tog <= ~req_tog;
|
|
end
|
|
end
|
|
|
|
// ----------------------------------------------------------------
|
|
// CDC: sync req toggle into UART domain, edge-detect
|
|
// ----------------------------------------------------------------
|
|
reg [2:0] req_sync;
|
|
always @(posedge clk_uart or negedge rst_n_uart) begin
|
|
if (!rst_n_uart) req_sync <= 3'b000;
|
|
else req_sync <= {req_sync[1:0], req_tog};
|
|
end
|
|
wire req_edge = req_sync[2] ^ req_sync[1];
|
|
|
|
// ----------------------------------------------------------------
|
|
// UART-domain: decimal conversion + report FSM
|
|
// ----------------------------------------------------------------
|
|
reg [15:0] lines_u;
|
|
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
|
|
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;
|
|
reg [15:0] l_rem, w_rem;
|
|
|
|
// 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.
|
|
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
|
|
|
|
// FSM that emits the message byte-by-byte.
|
|
localparam [2:0] F_IDLE = 3'd0,
|
|
F_CONVERT = 3'd1,
|
|
F_LOAD = 3'd2,
|
|
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;
|
|
|
|
reg [2:0] fstate;
|
|
reg [8:0] idx; // byte index within message
|
|
reg [8:0] msg_len;
|
|
reg is_banner;
|
|
reg is_err_msg;
|
|
reg tx_start;
|
|
reg [7:0] tx_byte;
|
|
wire tx_busy;
|
|
|
|
// 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;
|
|
8'd1: err_char = "[";
|
|
8'd2: err_char = "3";
|
|
8'd3: err_char = "1";
|
|
8'd4: err_char = "m";
|
|
8'd5: err_char = "E";
|
|
8'd6: err_char = "R";
|
|
8'd7: err_char = "R";
|
|
8'd8: err_char = " ";
|
|
8'd9: err_char = "l";
|
|
8'd10: err_char = "i";
|
|
8'd11: err_char = "n";
|
|
8'd12: err_char = "e";
|
|
8'd13: err_char = "s";
|
|
8'd14: err_char = "=";
|
|
8'd15: err_char = "0" + L0_r;
|
|
8'd16: err_char = "0" + L1_r;
|
|
8'd17: err_char = "0" + L2_r;
|
|
8'd18: err_char = "0" + L3_r;
|
|
8'd19: err_char = " ";
|
|
8'd20: err_char = "w";
|
|
8'd21: err_char = "i";
|
|
8'd22: err_char = "d";
|
|
8'd23: err_char = "t";
|
|
8'd24: err_char = "h";
|
|
8'd25: err_char = "=";
|
|
8'd26: err_char = "0" + W0_r;
|
|
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'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
|
|
|
|
// OK layout: ESC[32m + "OK" + ESC[0m + \r\n (13 bytes)
|
|
function [7:0] ok_char(input [7:0] i);
|
|
case (i)
|
|
8'd0: ok_char = 8'h1B;
|
|
8'd1: ok_char = "[";
|
|
8'd2: ok_char = "3";
|
|
8'd3: ok_char = "2";
|
|
8'd4: ok_char = "m";
|
|
8'd5: ok_char = "O";
|
|
8'd6: ok_char = "K";
|
|
8'd7: ok_char = 8'h1B;
|
|
8'd8: ok_char = "[";
|
|
8'd9: ok_char = "0";
|
|
8'd10: ok_char = "m";
|
|
8'd11: ok_char = 8'h0D;
|
|
8'd12: ok_char = 8'h0A;
|
|
default: ok_char = 8'h00;
|
|
endcase
|
|
endfunction
|
|
|
|
// Banner layout (411 bytes, indices 0-410):
|
|
// 0-11: ESC[2J ESC[H ESC[33m
|
|
// 12-62: '-'x51 63:\r 64:\n
|
|
// 65-72: "ARRIVE\r\n"
|
|
// 73-74: \r\n (blank)
|
|
// 75-112: "PARC LA FAYETTE, 6 RUE ISAAC NEWTON,\r\n"
|
|
// 113-128: "25000 BESANCON\r\n"
|
|
// 129-130: \r\n (blank)
|
|
// 131-163: "Project: LVDS Protocol Analyser\r\n"
|
|
// 164-216: "Board: DS90CF386 Interface (CI NU TEST LVDS LVCMOS)\r\n"
|
|
// 217-239: "S/N: 1000052088 Rev A\r\n"
|
|
// 240-241: \r\n (blank)
|
|
// 242-261: "Author: David Rice\r\n"
|
|
// 262-296: "Role: Senior Electronics Engineer\r\n"
|
|
// 297-326: "Email: david.rice@arrive.com\r\n"
|
|
// 327-328: \r\n (blank)
|
|
// 329-353: "Software Rev: " + 9-char version field + "\r\n"
|
|
// version chars at indices 343-351 (bump_version.py updates these)
|
|
// 354-404: '-'x51 405:\r 406:\n
|
|
// 407-410: ESC[0m
|
|
function [7:0] banner_char(input [8:0] i);
|
|
if ((i >= 9'd12 && i <= 9'd62) || (i >= 9'd354 && i <= 9'd404))
|
|
banner_char = "-";
|
|
else begin
|
|
case (i)
|
|
// ESC[2J
|
|
9'd0: banner_char = 8'h1B;
|
|
9'd1: banner_char = "[";
|
|
9'd2: banner_char = "2";
|
|
9'd3: banner_char = "J";
|
|
// ESC[H
|
|
9'd4: banner_char = 8'h1B;
|
|
9'd5: banner_char = "[";
|
|
9'd6: banner_char = "H";
|
|
// ESC[33m
|
|
9'd7: banner_char = 8'h1B;
|
|
9'd8: banner_char = "[";
|
|
9'd9: banner_char = "3";
|
|
9'd10: banner_char = "3";
|
|
9'd11: banner_char = "m";
|
|
// first separator \r\n
|
|
9'd63: banner_char = 8'h0D;
|
|
9'd64: banner_char = 8'h0A;
|
|
// "ARRIVE\r\n"
|
|
9'd65: banner_char = "A";
|
|
9'd66: banner_char = "R";
|
|
9'd67: banner_char = "R";
|
|
9'd68: banner_char = "I";
|
|
9'd69: banner_char = "V";
|
|
9'd70: banner_char = "E";
|
|
9'd71: banner_char = 8'h0D;
|
|
9'd72: banner_char = 8'h0A;
|
|
// blank line
|
|
9'd73: banner_char = 8'h0D;
|
|
9'd74: banner_char = 8'h0A;
|
|
// "PARC LA FAYETTE, 6 RUE ISAAC NEWTON,\r\n"
|
|
9'd75: banner_char = "P";
|
|
9'd76: banner_char = "A";
|
|
9'd77: banner_char = "R";
|
|
9'd78: banner_char = "C";
|
|
9'd79: banner_char = " ";
|
|
9'd80: banner_char = "L";
|
|
9'd81: banner_char = "A";
|
|
9'd82: banner_char = " ";
|
|
9'd83: banner_char = "F";
|
|
9'd84: banner_char = "A";
|
|
9'd85: banner_char = "Y";
|
|
9'd86: banner_char = "E";
|
|
9'd87: banner_char = "T";
|
|
9'd88: banner_char = "T";
|
|
9'd89: banner_char = "E";
|
|
9'd90: banner_char = ",";
|
|
9'd91: banner_char = " ";
|
|
9'd92: banner_char = "6";
|
|
9'd93: banner_char = " ";
|
|
9'd94: banner_char = "R";
|
|
9'd95: banner_char = "U";
|
|
9'd96: banner_char = "E";
|
|
9'd97: banner_char = " ";
|
|
9'd98: banner_char = "I";
|
|
9'd99: banner_char = "S";
|
|
9'd100: banner_char = "A";
|
|
9'd101: banner_char = "A";
|
|
9'd102: banner_char = "C";
|
|
9'd103: banner_char = " ";
|
|
9'd104: banner_char = "N";
|
|
9'd105: banner_char = "E";
|
|
9'd106: banner_char = "W";
|
|
9'd107: banner_char = "T";
|
|
9'd108: banner_char = "O";
|
|
9'd109: banner_char = "N";
|
|
9'd110: banner_char = ",";
|
|
9'd111: banner_char = 8'h0D;
|
|
9'd112: banner_char = 8'h0A;
|
|
// "25000 BESANCON\r\n"
|
|
9'd113: banner_char = "2";
|
|
9'd114: banner_char = "5";
|
|
9'd115: banner_char = "0";
|
|
9'd116: banner_char = "0";
|
|
9'd117: banner_char = "0";
|
|
9'd118: banner_char = " ";
|
|
9'd119: banner_char = "B";
|
|
9'd120: banner_char = "E";
|
|
9'd121: banner_char = "S";
|
|
9'd122: banner_char = "A";
|
|
9'd123: banner_char = "N";
|
|
9'd124: banner_char = "C";
|
|
9'd125: banner_char = "O";
|
|
9'd126: banner_char = "N";
|
|
9'd127: banner_char = 8'h0D;
|
|
9'd128: banner_char = 8'h0A;
|
|
// blank line
|
|
9'd129: banner_char = 8'h0D;
|
|
9'd130: banner_char = 8'h0A;
|
|
// "Project: LVDS Protocol Analyser\r\n"
|
|
9'd131: banner_char = "P";
|
|
9'd132: banner_char = "r";
|
|
9'd133: banner_char = "o";
|
|
9'd134: banner_char = "j";
|
|
9'd135: banner_char = "e";
|
|
9'd136: banner_char = "c";
|
|
9'd137: banner_char = "t";
|
|
9'd138: banner_char = ":";
|
|
9'd139: banner_char = " ";
|
|
9'd140: banner_char = "L";
|
|
9'd141: banner_char = "V";
|
|
9'd142: banner_char = "D";
|
|
9'd143: banner_char = "S";
|
|
9'd144: banner_char = " ";
|
|
9'd145: banner_char = "P";
|
|
9'd146: banner_char = "r";
|
|
9'd147: banner_char = "o";
|
|
9'd148: banner_char = "t";
|
|
9'd149: banner_char = "o";
|
|
9'd150: banner_char = "c";
|
|
9'd151: banner_char = "o";
|
|
9'd152: banner_char = "l";
|
|
9'd153: banner_char = " ";
|
|
9'd154: banner_char = "A";
|
|
9'd155: banner_char = "n";
|
|
9'd156: banner_char = "a";
|
|
9'd157: banner_char = "l";
|
|
9'd158: banner_char = "y";
|
|
9'd159: banner_char = "s";
|
|
9'd160: banner_char = "e";
|
|
9'd161: banner_char = "r";
|
|
9'd162: banner_char = 8'h0D;
|
|
9'd163: banner_char = 8'h0A;
|
|
// "Board: DS90CF386 Interface (CI NU TEST LVDS LVCMOS)\r\n"
|
|
9'd164: banner_char = "B";
|
|
9'd165: banner_char = "o";
|
|
9'd166: banner_char = "a";
|
|
9'd167: banner_char = "r";
|
|
9'd168: banner_char = "d";
|
|
9'd169: banner_char = ":";
|
|
9'd170: banner_char = " ";
|
|
9'd171: banner_char = "D";
|
|
9'd172: banner_char = "S";
|
|
9'd173: banner_char = "9";
|
|
9'd174: banner_char = "0";
|
|
9'd175: banner_char = "C";
|
|
9'd176: banner_char = "F";
|
|
9'd177: banner_char = "3";
|
|
9'd178: banner_char = "8";
|
|
9'd179: banner_char = "6";
|
|
9'd180: banner_char = " ";
|
|
9'd181: banner_char = "I";
|
|
9'd182: banner_char = "n";
|
|
9'd183: banner_char = "t";
|
|
9'd184: banner_char = "e";
|
|
9'd185: banner_char = "r";
|
|
9'd186: banner_char = "f";
|
|
9'd187: banner_char = "a";
|
|
9'd188: banner_char = "c";
|
|
9'd189: banner_char = "e";
|
|
9'd190: banner_char = " ";
|
|
9'd191: banner_char = "(";
|
|
9'd192: banner_char = "C";
|
|
9'd193: banner_char = "I";
|
|
9'd194: banner_char = " ";
|
|
9'd195: banner_char = "N";
|
|
9'd196: banner_char = "U";
|
|
9'd197: banner_char = " ";
|
|
9'd198: banner_char = "T";
|
|
9'd199: banner_char = "E";
|
|
9'd200: banner_char = "S";
|
|
9'd201: banner_char = "T";
|
|
9'd202: banner_char = " ";
|
|
9'd203: banner_char = "L";
|
|
9'd204: banner_char = "V";
|
|
9'd205: banner_char = "D";
|
|
9'd206: banner_char = "S";
|
|
9'd207: banner_char = " ";
|
|
9'd208: banner_char = "L";
|
|
9'd209: banner_char = "V";
|
|
9'd210: banner_char = "C";
|
|
9'd211: banner_char = "M";
|
|
9'd212: banner_char = "O";
|
|
9'd213: banner_char = "S";
|
|
9'd214: banner_char = ")";
|
|
9'd215: banner_char = 8'h0D;
|
|
9'd216: banner_char = 8'h0A;
|
|
// "S/N: 1000052088 Rev A\r\n"
|
|
9'd217: banner_char = "S";
|
|
9'd218: banner_char = "/";
|
|
9'd219: banner_char = "N";
|
|
9'd220: banner_char = ":";
|
|
9'd221: banner_char = " ";
|
|
9'd222: banner_char = "1";
|
|
9'd223: banner_char = "0";
|
|
9'd224: banner_char = "0";
|
|
9'd225: banner_char = "0";
|
|
9'd226: banner_char = "0";
|
|
9'd227: banner_char = "5";
|
|
9'd228: banner_char = "2";
|
|
9'd229: banner_char = "0";
|
|
9'd230: banner_char = "8";
|
|
9'd231: banner_char = "8";
|
|
9'd232: banner_char = " ";
|
|
9'd233: banner_char = "R";
|
|
9'd234: banner_char = "e";
|
|
9'd235: banner_char = "v";
|
|
9'd236: banner_char = " ";
|
|
9'd237: banner_char = "A";
|
|
9'd238: banner_char = 8'h0D;
|
|
9'd239: banner_char = 8'h0A;
|
|
// blank line
|
|
9'd240: banner_char = 8'h0D;
|
|
9'd241: banner_char = 8'h0A;
|
|
// "Author: David Rice\r\n"
|
|
9'd242: banner_char = "A";
|
|
9'd243: banner_char = "u";
|
|
9'd244: banner_char = "t";
|
|
9'd245: banner_char = "h";
|
|
9'd246: banner_char = "o";
|
|
9'd247: banner_char = "r";
|
|
9'd248: banner_char = ":";
|
|
9'd249: banner_char = " ";
|
|
9'd250: banner_char = "D";
|
|
9'd251: banner_char = "a";
|
|
9'd252: banner_char = "v";
|
|
9'd253: banner_char = "i";
|
|
9'd254: banner_char = "d";
|
|
9'd255: banner_char = " ";
|
|
9'd256: banner_char = "R";
|
|
9'd257: banner_char = "i";
|
|
9'd258: banner_char = "c";
|
|
9'd259: banner_char = "e";
|
|
9'd260: banner_char = 8'h0D;
|
|
9'd261: banner_char = 8'h0A;
|
|
// "Role: Senior Electronics Engineer\r\n"
|
|
9'd262: banner_char = "R";
|
|
9'd263: banner_char = "o";
|
|
9'd264: banner_char = "l";
|
|
9'd265: banner_char = "e";
|
|
9'd266: banner_char = ":";
|
|
9'd267: banner_char = " ";
|
|
9'd268: banner_char = "S";
|
|
9'd269: banner_char = "e";
|
|
9'd270: banner_char = "n";
|
|
9'd271: banner_char = "i";
|
|
9'd272: banner_char = "o";
|
|
9'd273: banner_char = "r";
|
|
9'd274: banner_char = " ";
|
|
9'd275: banner_char = "E";
|
|
9'd276: banner_char = "l";
|
|
9'd277: banner_char = "e";
|
|
9'd278: banner_char = "c";
|
|
9'd279: banner_char = "t";
|
|
9'd280: banner_char = "r";
|
|
9'd281: banner_char = "o";
|
|
9'd282: banner_char = "n";
|
|
9'd283: banner_char = "i";
|
|
9'd284: banner_char = "c";
|
|
9'd285: banner_char = "s";
|
|
9'd286: banner_char = " ";
|
|
9'd287: banner_char = "E";
|
|
9'd288: banner_char = "n";
|
|
9'd289: banner_char = "g";
|
|
9'd290: banner_char = "i";
|
|
9'd291: banner_char = "n";
|
|
9'd292: banner_char = "e";
|
|
9'd293: banner_char = "e";
|
|
9'd294: banner_char = "r";
|
|
9'd295: banner_char = 8'h0D;
|
|
9'd296: banner_char = 8'h0A;
|
|
// "Email: david.rice@arrive.com\r\n"
|
|
9'd297: banner_char = "E";
|
|
9'd298: banner_char = "m";
|
|
9'd299: banner_char = "a";
|
|
9'd300: banner_char = "i";
|
|
9'd301: banner_char = "l";
|
|
9'd302: banner_char = ":";
|
|
9'd303: banner_char = " ";
|
|
9'd304: banner_char = "d";
|
|
9'd305: banner_char = "a";
|
|
9'd306: banner_char = "v";
|
|
9'd307: banner_char = "i";
|
|
9'd308: banner_char = "d";
|
|
9'd309: banner_char = ".";
|
|
9'd310: banner_char = "r";
|
|
9'd311: banner_char = "i";
|
|
9'd312: banner_char = "c";
|
|
9'd313: banner_char = "e";
|
|
9'd314: banner_char = "@";
|
|
9'd315: banner_char = "a";
|
|
9'd316: banner_char = "r";
|
|
9'd317: banner_char = "r";
|
|
9'd318: banner_char = "i";
|
|
9'd319: banner_char = "v";
|
|
9'd320: banner_char = "e";
|
|
9'd321: banner_char = ".";
|
|
9'd322: banner_char = "c";
|
|
9'd323: banner_char = "o";
|
|
9'd324: banner_char = "m";
|
|
9'd325: banner_char = 8'h0D;
|
|
9'd326: banner_char = 8'h0A;
|
|
// blank line
|
|
9'd327: banner_char = 8'h0D;
|
|
9'd328: banner_char = 8'h0A;
|
|
// "Software Rev: " + 9-char version field (indices 343-351) + "\r\n"
|
|
9'd329: banner_char = "S";
|
|
9'd330: banner_char = "o";
|
|
9'd331: banner_char = "f";
|
|
9'd332: banner_char = "t";
|
|
9'd333: banner_char = "w";
|
|
9'd334: banner_char = "a";
|
|
9'd335: banner_char = "r";
|
|
9'd336: banner_char = "e";
|
|
9'd337: banner_char = " ";
|
|
9'd338: banner_char = "R";
|
|
9'd339: banner_char = "e";
|
|
9'd340: banner_char = "v";
|
|
9'd341: banner_char = ":";
|
|
9'd342: banner_char = " ";
|
|
9'd343: banner_char = "0";
|
|
9'd344: banner_char = ".";
|
|
9'd345: banner_char = "1";
|
|
9'd346: banner_char = ".";
|
|
9'd347: banner_char = "6";
|
|
9'd348: banner_char = " ";
|
|
9'd349: banner_char = " ";
|
|
9'd350: banner_char = " ";
|
|
9'd351: banner_char = " ";
|
|
9'd352: banner_char = 8'h0D;
|
|
9'd353: banner_char = 8'h0A;
|
|
// second separator \r\n
|
|
9'd405: banner_char = 8'h0D;
|
|
9'd406: banner_char = 8'h0A;
|
|
// ESC[0m
|
|
9'd407: banner_char = 8'h1B;
|
|
9'd408: banner_char = "[";
|
|
9'd409: banner_char = "0";
|
|
9'd410: banner_char = "m";
|
|
default: banner_char = 8'h00;
|
|
endcase
|
|
end
|
|
endfunction
|
|
|
|
// Trigger: one cycle after req_edge (so lines_u/width_u are updated).
|
|
reg req_edge_q;
|
|
always @(posedge clk_uart or negedge rst_n_uart) begin
|
|
if (!rst_n_uart) req_edge_q <= 1'b0;
|
|
else req_edge_q <= req_edge;
|
|
end
|
|
|
|
always @(posedge clk_uart or negedge rst_n_uart) begin
|
|
if (!rst_n_uart) begin
|
|
fstate <= F_LOAD;
|
|
idx <= 9'd0;
|
|
msg_len <= BANNER_LEN;
|
|
is_banner <= 1'b1;
|
|
is_err_msg <= 1'b0;
|
|
tx_start <= 1'b0;
|
|
tx_byte <= 8'd0;
|
|
conv_step <= 2'd0;
|
|
l_rem <= 16'd0;
|
|
w_rem <= 16'd0;
|
|
L0_r <= 4'd0; L1_r <= 4'd0; L2_r <= 4'd0; L3_r <= 4'd0;
|
|
W0_r <= 4'd0; W1_r <= 4'd0; W2_r <= 4'd0; W3_r <= 4'd0;
|
|
end else begin
|
|
tx_start <= 1'b0;
|
|
case (fstate)
|
|
F_IDLE: begin
|
|
is_banner <= 1'b0;
|
|
if (req_edge_q && (anomaly_u || heartbeat_u) && !is_banner) begin
|
|
idx <= 9'd0;
|
|
is_err_msg <= anomaly_u;
|
|
msg_len <= anomaly_u ? 9'd51 : 9'd13;
|
|
l_rem <= lines_u;
|
|
w_rem <= width_u;
|
|
conv_step <= 2'd0;
|
|
fstate <= F_CONVERT;
|
|
end
|
|
end
|
|
F_CONVERT: begin
|
|
conv_step <= conv_step + 2'd1;
|
|
case (conv_step)
|
|
2'd0: begin // thousands
|
|
if (l_rem >= 16'd9000) begin L0_r <= 4'd9; l_rem <= l_rem - 16'd9000; end
|
|
else if (l_rem >= 16'd8000) begin L0_r <= 4'd8; l_rem <= l_rem - 16'd8000; end
|
|
else if (l_rem >= 16'd7000) begin L0_r <= 4'd7; l_rem <= l_rem - 16'd7000; end
|
|
else if (l_rem >= 16'd6000) begin L0_r <= 4'd6; l_rem <= l_rem - 16'd6000; end
|
|
else if (l_rem >= 16'd5000) begin L0_r <= 4'd5; l_rem <= l_rem - 16'd5000; end
|
|
else if (l_rem >= 16'd4000) begin L0_r <= 4'd4; l_rem <= l_rem - 16'd4000; end
|
|
else if (l_rem >= 16'd3000) begin L0_r <= 4'd3; l_rem <= l_rem - 16'd3000; end
|
|
else if (l_rem >= 16'd2000) begin L0_r <= 4'd2; l_rem <= l_rem - 16'd2000; end
|
|
else if (l_rem >= 16'd1000) begin L0_r <= 4'd1; l_rem <= l_rem - 16'd1000; end
|
|
else L0_r <= 4'd0;
|
|
if (w_rem >= 16'd9000) begin W0_r <= 4'd9; w_rem <= w_rem - 16'd9000; end
|
|
else if (w_rem >= 16'd8000) begin W0_r <= 4'd8; w_rem <= w_rem - 16'd8000; end
|
|
else if (w_rem >= 16'd7000) begin W0_r <= 4'd7; w_rem <= w_rem - 16'd7000; end
|
|
else if (w_rem >= 16'd6000) begin W0_r <= 4'd6; w_rem <= w_rem - 16'd6000; end
|
|
else if (w_rem >= 16'd5000) begin W0_r <= 4'd5; w_rem <= w_rem - 16'd5000; end
|
|
else if (w_rem >= 16'd4000) begin W0_r <= 4'd4; w_rem <= w_rem - 16'd4000; end
|
|
else if (w_rem >= 16'd3000) begin W0_r <= 4'd3; w_rem <= w_rem - 16'd3000; end
|
|
else if (w_rem >= 16'd2000) begin W0_r <= 4'd2; w_rem <= w_rem - 16'd2000; end
|
|
else if (w_rem >= 16'd1000) begin W0_r <= 4'd1; w_rem <= w_rem - 16'd1000; end
|
|
else W0_r <= 4'd0;
|
|
end
|
|
2'd1: begin // hundreds
|
|
if (l_rem >= 16'd900) begin L1_r <= 4'd9; l_rem <= l_rem - 16'd900; end
|
|
else if (l_rem >= 16'd800) begin L1_r <= 4'd8; l_rem <= l_rem - 16'd800; end
|
|
else if (l_rem >= 16'd700) begin L1_r <= 4'd7; l_rem <= l_rem - 16'd700; end
|
|
else if (l_rem >= 16'd600) begin L1_r <= 4'd6; l_rem <= l_rem - 16'd600; end
|
|
else if (l_rem >= 16'd500) begin L1_r <= 4'd5; l_rem <= l_rem - 16'd500; end
|
|
else if (l_rem >= 16'd400) begin L1_r <= 4'd4; l_rem <= l_rem - 16'd400; end
|
|
else if (l_rem >= 16'd300) begin L1_r <= 4'd3; l_rem <= l_rem - 16'd300; end
|
|
else if (l_rem >= 16'd200) begin L1_r <= 4'd2; l_rem <= l_rem - 16'd200; end
|
|
else if (l_rem >= 16'd100) begin L1_r <= 4'd1; l_rem <= l_rem - 16'd100; end
|
|
else L1_r <= 4'd0;
|
|
if (w_rem >= 16'd900) begin W1_r <= 4'd9; w_rem <= w_rem - 16'd900; end
|
|
else if (w_rem >= 16'd800) begin W1_r <= 4'd8; w_rem <= w_rem - 16'd800; end
|
|
else if (w_rem >= 16'd700) begin W1_r <= 4'd7; w_rem <= w_rem - 16'd700; end
|
|
else if (w_rem >= 16'd600) begin W1_r <= 4'd6; w_rem <= w_rem - 16'd600; end
|
|
else if (w_rem >= 16'd500) begin W1_r <= 4'd5; w_rem <= w_rem - 16'd500; end
|
|
else if (w_rem >= 16'd400) begin W1_r <= 4'd4; w_rem <= w_rem - 16'd400; end
|
|
else if (w_rem >= 16'd300) begin W1_r <= 4'd3; w_rem <= w_rem - 16'd300; end
|
|
else if (w_rem >= 16'd200) begin W1_r <= 4'd2; w_rem <= w_rem - 16'd200; end
|
|
else if (w_rem >= 16'd100) begin W1_r <= 4'd1; w_rem <= w_rem - 16'd100; end
|
|
else W1_r <= 4'd0;
|
|
end
|
|
2'd2: begin // tens
|
|
if (l_rem >= 16'd90) begin L2_r <= 4'd9; l_rem <= l_rem - 16'd90; end
|
|
else if (l_rem >= 16'd80) begin L2_r <= 4'd8; l_rem <= l_rem - 16'd80; end
|
|
else if (l_rem >= 16'd70) begin L2_r <= 4'd7; l_rem <= l_rem - 16'd70; end
|
|
else if (l_rem >= 16'd60) begin L2_r <= 4'd6; l_rem <= l_rem - 16'd60; end
|
|
else if (l_rem >= 16'd50) begin L2_r <= 4'd5; l_rem <= l_rem - 16'd50; end
|
|
else if (l_rem >= 16'd40) begin L2_r <= 4'd4; l_rem <= l_rem - 16'd40; end
|
|
else if (l_rem >= 16'd30) begin L2_r <= 4'd3; l_rem <= l_rem - 16'd30; end
|
|
else if (l_rem >= 16'd20) begin L2_r <= 4'd2; l_rem <= l_rem - 16'd20; end
|
|
else if (l_rem >= 16'd10) begin L2_r <= 4'd1; l_rem <= l_rem - 16'd10; end
|
|
else L2_r <= 4'd0;
|
|
if (w_rem >= 16'd90) begin W2_r <= 4'd9; w_rem <= w_rem - 16'd90; end
|
|
else if (w_rem >= 16'd80) begin W2_r <= 4'd8; w_rem <= w_rem - 16'd80; end
|
|
else if (w_rem >= 16'd70) begin W2_r <= 4'd7; w_rem <= w_rem - 16'd70; end
|
|
else if (w_rem >= 16'd60) begin W2_r <= 4'd6; w_rem <= w_rem - 16'd60; end
|
|
else if (w_rem >= 16'd50) begin W2_r <= 4'd5; w_rem <= w_rem - 16'd50; end
|
|
else if (w_rem >= 16'd40) begin W2_r <= 4'd4; w_rem <= w_rem - 16'd40; end
|
|
else if (w_rem >= 16'd30) begin W2_r <= 4'd3; w_rem <= w_rem - 16'd30; end
|
|
else if (w_rem >= 16'd20) begin W2_r <= 4'd2; w_rem <= w_rem - 16'd20; end
|
|
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
|
|
L3_r <= l_rem[3:0];
|
|
W3_r <= w_rem[3:0];
|
|
fstate <= F_LOAD;
|
|
end
|
|
endcase
|
|
end
|
|
F_LOAD: begin
|
|
if (!tx_busy) begin
|
|
tx_byte <= is_banner ? banner_char(idx) :
|
|
is_err_msg ? err_char(idx) : ok_char(idx);
|
|
tx_start <= 1'b1;
|
|
fstate <= F_WAIT;
|
|
end
|
|
end
|
|
F_WAIT: begin
|
|
if (tx_busy == 1'b0 && tx_start == 1'b0) begin
|
|
// byte fully sent (uart drops busy at stop-bit end)
|
|
if (idx == msg_len - 9'd1) begin
|
|
fstate <= F_IDLE;
|
|
end else begin
|
|
idx <= idx + 9'd1;
|
|
fstate <= F_LOAD;
|
|
end
|
|
end
|
|
end
|
|
default: fstate <= F_IDLE;
|
|
endcase
|
|
end
|
|
end
|
|
|
|
uart_tx #(
|
|
.CLK_HZ (50_000_000),
|
|
.BAUD (115_200)
|
|
) u_uart (
|
|
.clk (clk_uart),
|
|
.rst_n (rst_n_uart),
|
|
.start (tx_start),
|
|
.data (tx_byte),
|
|
.tx (uart_tx_pin),
|
|
.busy (tx_busy)
|
|
);
|
|
|
|
endmodule
|