1pub const SLIP_END: u8 = 0xC0;
20pub const SLIP_ESC: u8 = 0xDB;
22pub const SLIP_ESC_END: u8 = 0xDC;
24pub const SLIP_ESC_ESC: u8 = 0xDD;
26
27pub mod host {
29 pub const PUT: u8 = 0x01;
32 pub const FILL: u8 = 0x02;
34 pub const CURSOR: u8 = 0x03;
36 pub const POINTER: u8 = 0x04;
38 pub const WINDOW: u8 = 0x05;
42 pub const ENGINE: u8 = 0x10;
45 pub const ENGINE_RESET: u8 = 0x11;
48 pub const RESULT: u8 = 0x12;
50 pub const ACTIVITY: u8 = 0x13;
52 pub const TOTALS: u8 = 0x14;
54 pub const PS2_SEND: u8 = 0x20;
56 pub const READ: u8 = 0x30;
58 pub const PING: u8 = 0x3F;
60 pub const ALL: [(&str, u8); 13] = [
61 ("put", PUT),
62 ("fill", FILL),
63 ("cursor", CURSOR),
64 ("pointer", POINTER),
65 ("window", WINDOW),
66 ("engine", ENGINE),
67 ("engine_reset", ENGINE_RESET),
68 ("result", RESULT),
69 ("activity", ACTIVITY),
70 ("totals", TOTALS),
71 ("ps2_send", PS2_SEND),
72 ("read", READ),
73 ("ping", PING),
74 ];
75}
76
77pub mod board {
79 pub const PS2: u8 = 0x81;
82 pub const PS2_SENT: u8 = 0x82;
85 pub const ENGINE: u8 = 0x90;
87 pub const ENGINE_TAKEN: u8 = 0x91;
90 pub const ACTIVITY: u8 = 0x92;
96 pub const TOTALS: u8 = 0x93;
102 pub const CELLS: u8 = 0xB0;
104 pub const PONG: u8 = 0xBF;
109 pub const ALL: [(&str, u8); 8] = [
110 ("ps2", PS2),
111 ("ps2_sent", PS2_SENT),
112 ("engine", ENGINE),
113 ("engine_taken", ENGINE_TAKEN),
114 ("activity", ACTIVITY),
115 ("totals", TOTALS),
116 ("cells", CELLS),
117 ("pong", PONG),
118 ];
119}
120
121pub const PONG_FIELDS: [&str; 11] = [
123 "version",
124 "cols",
125 "rows",
126 "panel_rows",
127 "features",
128 "ps2_edges_hi",
129 "ps2_edges_lo",
130 "ps2_shortest_low_us",
131 "ps2_bytes_hi",
132 "ps2_bytes_lo",
133 "ps2_damaged",
134];
135
136pub const ACTIVITY_FIELDS: [(&str, usize, usize); 10] = [
139 ("frame", 2, 1),
140 ("running", 1, 1),
141 ("block", 2, 1),
142 ("cell", 2, 1),
143 ("depth", 2, 1),
144 ("heap", 2, 1),
145 ("cycles", 4, 1),
146 ("block_clocks", 2, super::wprog_engine_rtl::WPE_HW_MAXBLK),
147 ("cell_clocks", 2, super::wprog_engine_rtl::WPE_HW_MAXCELL),
148 ("frame_again", 2, 1),
149];
150
151pub const TOTALS_FIELDS: [(&str, usize, usize); 3] = [
153 ("cycles", 4, 1),
154 ("block_totals", 4, super::wprog_engine_rtl::WPE_HW_MAXBLK),
155 ("cell_totals", 4, super::wprog_engine_rtl::WPE_HW_MAXCELL),
156];
157
158const fn payload_bytes(fields: &[(&str, usize, usize)]) -> usize {
159 let mut n = 0;
160 let mut i = 0;
161 while i < fields.len() {
162 n += fields[i].1 * fields[i].2;
163 i += 1;
164 }
165 n
166}
167
168pub const fn activity_bytes() -> usize {
170 payload_bytes(&ACTIVITY_FIELDS)
171}
172
173pub const fn totals_bytes() -> usize {
175 payload_bytes(&TOTALS_FIELDS)
176}
177
178pub const VERSION: u8 = 1;
180
181pub const FEATURE_ENGINE: u8 = 0x01;
184
185pub const FEATURE_SMALL: u8 = 0x02;
187
188pub const FEATURE_TOTALS: u8 = 0x04;
190
191pub const SMALL_FIRST_ROW: u8 = 32;
194pub const SMALL_ROWS: u8 = 32;
195pub const SMALL_COLS: u8 = 32;
196
197pub const FONT8_ROWS: [usize; 8] = [2, 4, 5, 7, 8, 10, 11, 13];
200
201pub const ENGINE_FRAME_MAX: usize = 255;
203
204pub const ENGINE_CLOCK_DIVIDER: u32 = 128;
207pub const ENGINE_CLKS_PER_BIT: u32 = 8;
208
209pub const PS2_PARITY_ERROR: u8 = 0x01;
211pub const PS2_FRAMING_ERROR: u8 = 0x02;
213pub const PS2_NO_ACK: u8 = 0x01;
215
216pub const COLS: u8 = 80;
218pub const ROWS: u8 = 30;
220pub const PANEL_ROWS: u8 = 4;
222pub const GLYPHS: usize = 128;
224pub const GLYPH_ROWS: usize = 16;
226
227pub const UI_GLYPHS: &str = "─│┌┐└┘├┤┬┴┼═║╔╗╚╝█▓▒░▀▄▌▐▶◀▲▼•·√";
232pub const DEL_GLYPH: char = '■';
234
235pub fn glyph_slot(ch: char) -> u8 {
238 match ch {
239 ' '..='~' => ch as u8,
240 _ if ch == DEL_GLYPH => 0x7F,
241 _ => UI_GLYPHS.chars().position(|c| c == ch).map_or(b'?', |i| i as u8),
242 }
243}
244
245pub const BAUD: u32 = 1_000_000;
248pub const BOARD_HZ: u32 = 100_000_000;
250
251pub const H_TIMING: (u16, u16, u16, u16) = (640, 16, 96, 48);
254pub const V_TIMING: (u16, u16, u16, u16) = (480, 10, 2, 33);
255pub const CLOCKS_PER_PIXEL: u32 = 4;
256
257pub const PALETTE: [u16; 16] = [
259 0x000, 0x00A, 0x0A0, 0x0AA, 0xA00, 0xA0A, 0xA50, 0xAAA, 0x555, 0x55F, 0x5F5, 0x5FF, 0xF55, 0xF5F, 0xFF5, 0xFFF,
260];
261
262pub const PANEL_TITLE_ROW: u8 = ROWS - PANEL_ROWS;
275pub const PANEL_LABEL_ROW: u8 = PANEL_TITLE_ROW + 1;
276pub const PANEL_BLOCK_ROW: u8 = PANEL_TITLE_ROW + 2;
277pub const PANEL_CELL_ROW: u8 = PANEL_TITLE_ROW + 3;
278pub const PANEL_CYCLES_COL: u8 = 62;
279pub const PANEL_BLOCK_COL: u8 = 8;
280pub const PANEL_BLOCK_TILE: u8 = 6;
281pub const PANEL_CELL_COL: u8 = 8;
282pub const PANEL_STACK_COL: u8 = 58;
283pub const PANEL_HEAP_COL: u8 = 69;
284
285pub const HEAT: [u16; 16] = [
290 0x112, 0x124, 0x136, 0x148, 0x16A, 0x18B, 0x1AB, 0x2C9, 0x4D6, 0x7D4, 0xAD3, 0xCC2, 0xEA1, 0xF81, 0xF51, 0xF22,
291];
292pub const PANEL_OUTLINE: u16 = 0xFFF;
295pub const PANEL_DIGIT: u16 = 0xFFF;
296pub const PANEL_STACK_LIT: u16 = 0x4D6;
297pub const PANEL_HEAP_LIT: u16 = 0x1AB;
298pub const PANEL_GAUGE_UNLIT: u16 = 0x112;
299
300pub const fn panel_frame_samples() -> u32 {
304 let ht = (H_TIMING.0 + H_TIMING.1 + H_TIMING.2 + H_TIMING.3) as u32;
305 let vt = (V_TIMING.0 + V_TIMING.1 + V_TIMING.2 + V_TIMING.3) as u32;
306 ht * vt * CLOCKS_PER_PIXEL / ENGINE_CLOCK_DIVIDER + 1
307}
308
309pub const fn heat_level(hits: u32) -> usize {
311 (32 - hits.leading_zeros()) as usize
312}
313
314#[derive(Clone, Copy, Debug, PartialEq, Eq)]
316pub struct Cell {
317 pub glyph: u8,
318 pub blink: bool,
319 pub fg: u8,
320 pub bg: u8,
321}
322
323impl Cell {
324 pub fn new(glyph: u8, fg: u8, bg: u8) -> Self {
325 Cell { glyph: glyph & 0x7F, blink: false, fg: fg & 0x0F, bg: bg & 0x0F }
326 }
327
328 pub fn bytes(self) -> [u8; 2] {
330 [(self.bg << 4) | self.fg, ((self.blink as u8) << 7) | (self.glyph & 0x7F)]
331 }
332
333 pub fn from_bytes(attr: u8, glyph: u8) -> Self {
334 Cell { glyph: glyph & 0x7F, blink: glyph & 0x80 != 0, fg: attr & 0x0F, bg: attr >> 4 }
335 }
336}
337
338pub fn cell_address(row: u8, col: u8) -> u16 {
340 ((row as u16) << 7) | col as u16
341}
342
343pub fn slip_encode(frame: &[u8]) -> Vec<u8> {
345 let mut out = Vec::with_capacity(frame.len() + 2);
346 for &b in frame {
347 match b {
348 SLIP_END => out.extend_from_slice(&[SLIP_ESC, SLIP_ESC_END]),
349 SLIP_ESC => out.extend_from_slice(&[SLIP_ESC, SLIP_ESC_ESC]),
350 _ => out.push(b),
351 }
352 }
353 out.push(SLIP_END);
354 out
355}
356
357#[derive(Default, Debug)]
360pub struct SlipDecoder {
361 frame: Vec<u8>,
362 escaped: bool,
363 damaged: bool,
364}
365
366impl SlipDecoder {
367 pub fn new() -> Self {
368 Self::default()
369 }
370
371 pub fn push(&mut self, b: u8) -> Option<Vec<u8>> {
373 if b == SLIP_END {
374 let frame = std::mem::take(&mut self.frame);
375 let damaged = std::mem::replace(&mut self.damaged, false);
376 self.escaped = false;
377 return (!frame.is_empty() && !damaged).then_some(frame);
378 }
379 if self.escaped {
380 self.escaped = false;
381 match b {
382 SLIP_ESC_END => self.frame.push(SLIP_END),
383 SLIP_ESC_ESC => self.frame.push(SLIP_ESC),
384 _ => self.damaged = true,
385 }
386 } else if b == SLIP_ESC {
387 self.escaped = true;
388 } else {
389 self.frame.push(b);
390 }
391 None
392 }
393
394 pub fn decode_all(&mut self, bytes: &[u8]) -> Vec<Vec<u8>> {
396 bytes.iter().filter_map(|&b| self.push(b)).collect()
397 }
398}
399
400pub fn clocks_per_bit() -> u32 {
402 BOARD_HZ / BAUD
403}
404
405const VERILOG_BYTES: [(&str, u8); 33] = [
406 ("SLIP_END", SLIP_END),
407 ("SLIP_ESC", SLIP_ESC),
408 ("SLIP_ESC_END", SLIP_ESC_END),
409 ("SLIP_ESC_ESC", SLIP_ESC_ESC),
410 ("H_PUT", host::PUT),
411 ("H_FILL", host::FILL),
412 ("H_CURSOR", host::CURSOR),
413 ("H_POINTER", host::POINTER),
414 ("H_WINDOW", host::WINDOW),
415 ("H_ENGINE", host::ENGINE),
416 ("H_ENGINE_RESET", host::ENGINE_RESET),
417 ("H_RESULT", host::RESULT),
418 ("H_ACTIVITY", host::ACTIVITY),
419 ("H_TOTALS", host::TOTALS),
420 ("H_PS2_SEND", host::PS2_SEND),
421 ("H_READ", host::READ),
422 ("H_PING", host::PING),
423 ("B_PS2", board::PS2),
424 ("B_PS2_SENT", board::PS2_SENT),
425 ("B_ENGINE", board::ENGINE),
426 ("B_ENGINE_TAKEN", board::ENGINE_TAKEN),
427 ("B_ACTIVITY", board::ACTIVITY),
428 ("B_TOTALS", board::TOTALS),
429 ("B_CELLS", board::CELLS),
430 ("B_PONG", board::PONG),
431 ("VERSION", VERSION),
432 ("PS2_PARITY_ERROR", PS2_PARITY_ERROR),
433 ("PS2_FRAMING_ERROR", PS2_FRAMING_ERROR),
434 ("PS2_NO_ACK", PS2_NO_ACK),
435 ("PANEL_ROWS", PANEL_ROWS),
436 ("FEATURE_ENGINE", FEATURE_ENGINE),
437 ("FEATURE_SMALL", FEATURE_SMALL),
438 ("FEATURE_TOTALS", FEATURE_TOTALS),
439];
440
441const VERILOG_COLOURS: [(&str, u16); 5] = [
442 ("PANEL_OUTLINE", PANEL_OUTLINE),
443 ("PANEL_DIGIT", PANEL_DIGIT),
444 ("PANEL_STACK_LIT", PANEL_STACK_LIT),
445 ("PANEL_HEAP_LIT", PANEL_HEAP_LIT),
446 ("PANEL_GAUGE_UNLIT", PANEL_GAUGE_UNLIT),
447];
448
449pub fn verilog_numbers() -> Vec<(&'static str, u32)> {
451 vec![
452 ("COLS", COLS as u32),
453 ("ROWS", ROWS as u32),
454 ("SMALL_FIRST_ROW", SMALL_FIRST_ROW as u32),
455 ("SMALL_ROWS", SMALL_ROWS as u32),
456 ("SMALL_COLS", SMALL_COLS as u32),
457 ("PANEL_FIRST_ROW", (ROWS - PANEL_ROWS) as u32),
458 ("CLKS_PER_BIT", clocks_per_bit()),
459 ("ENGINE_CLKS_PER_BIT", ENGINE_CLKS_PER_BIT),
460 ("ENGINE_CLOCK_DIVIDER", ENGINE_CLOCK_DIVIDER),
461 ("ENGINE_LINK_CLKS_PER_BIT", ENGINE_CLKS_PER_BIT * ENGINE_CLOCK_DIVIDER),
462 ("ENGINE_FRAME_MAX", ENGINE_FRAME_MAX as u32),
463 ("CLOCKS_PER_PIXEL", CLOCKS_PER_PIXEL),
464 ("H_VISIBLE", H_TIMING.0 as u32),
465 ("H_FRONT", H_TIMING.1 as u32),
466 ("H_SYNC", H_TIMING.2 as u32),
467 ("H_BACK", H_TIMING.3 as u32),
468 ("V_VISIBLE", V_TIMING.0 as u32),
469 ("V_FRONT", V_TIMING.1 as u32),
470 ("V_SYNC", V_TIMING.2 as u32),
471 ("V_BACK", V_TIMING.3 as u32),
472 ("PANEL_TITLE_ROW", PANEL_TITLE_ROW as u32),
473 ("PANEL_LABEL_ROW", PANEL_LABEL_ROW as u32),
474 ("PANEL_BLOCK_ROW", PANEL_BLOCK_ROW as u32),
475 ("PANEL_CELL_ROW", PANEL_CELL_ROW as u32),
476 ("PANEL_CYCLES_COL", PANEL_CYCLES_COL as u32),
477 ("PANEL_BLOCK_COL", PANEL_BLOCK_COL as u32),
478 ("PANEL_BLOCK_TILE", PANEL_BLOCK_TILE as u32),
479 ("PANEL_CELL_COL", PANEL_CELL_COL as u32),
480 ("PANEL_STACK_COL", PANEL_STACK_COL as u32),
481 ("PANEL_HEAP_COL", PANEL_HEAP_COL as u32),
482 ("MAXBLK", super::wprog_engine_rtl::WPE_HW_MAXBLK as u32),
483 ("MAXCELL", super::wprog_engine_rtl::WPE_HW_MAXCELL as u32),
484 ("DEPTH", super::wprog_engine_rtl::WPE_HW_DEPTH as u32),
485 ("HEAP", super::wprog_engine_rtl::WPE_HW_HEAP as u32),
486 ]
487}
488
489pub fn fill_verilog(template: &str) -> String {
495 let mut out = template.to_string();
496 for (name, byte) in VERILOG_BYTES {
497 out = out.replace(&format!("__V_{name}__"), &format!("8'h{byte:02X}"));
498 out = out.replace(&format!("__X_{name}__"), &format!("0x{byte:02X}"));
499 }
500 for (name, n) in verilog_numbers() {
501 out = out.replace(&format!("__N_{name}__"), &n.to_string());
502 }
503 for (name, rgb) in VERILOG_COLOURS {
504 out = out.replace(&format!("__C_{name}__"), &format!("12'h{rgb:03X}"));
505 }
506 let palette: Vec<String> =
507 PALETTE.iter().enumerate().map(|(i, rgb)| format!(" 4'd{i}: palette = 12'h{rgb:03X};")).collect();
508 out = out.replace("__PALETTE_CASE__", &palette.join("\n"));
509 let heat: Vec<String> =
510 HEAT.iter().enumerate().map(|(i, rgb)| format!(" 4'd{i}: heat_rgb = 12'h{rgb:03X};")).collect();
511 out = out.replace("__HEAT_CASE__", &heat.join("\n"));
512 if out.contains("__DIGIT_ROM_CASE__") {
513 let font = super::terminal_rtl::font();
514 let mut arms = Vec::new();
515 for d in 0..10u8 {
516 for row in 0..GLYPH_ROWS {
517 let bits = font[(b'0' + d) as usize * GLYPH_ROWS + row];
518 arms.push(format!(" 8'h{d:X}{row:X}: digit_bits = 8'h{bits:02X};"));
519 }
520 }
521 out = out.replace("__DIGIT_ROM_CASE__", &arms.join("\n"));
522 }
523 for marker in ["__V_", "__X_", "__N_", "__C_", "__PALETTE", "__HEAT", "__DIGIT"] {
524 if let Some(at) = out.find(marker) {
525 let name: String = out[at..].chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_').collect();
526 panic!("a terminal template names a value terminal_protocol does not define: {name}");
527 }
528 }
529 out
530}
531
532pub fn table_json() -> String {
534 let kinds = |all: &[(&str, u8)]| -> String {
535 all.iter().map(|(n, v)| format!("\"{n}\": {v}")).collect::<Vec<_>>().join(", ")
536 };
537 let palette: Vec<String> = PALETTE.iter().map(|c| c.to_string()).collect();
538 format!(
539 "{{\"version\": {VERSION}, \
540 \"slip\": {{\"end\": {SLIP_END}, \"esc\": {SLIP_ESC}, \"esc_end\": {SLIP_ESC_END}, \"esc_esc\": {SLIP_ESC_ESC}}}, \
541 \"host\": {{{}}}, \"board\": {{{}}}, \
542 \"ps2\": {{\"parity_error\": {PS2_PARITY_ERROR}, \"framing_error\": {PS2_FRAMING_ERROR}, \"no_ack\": {PS2_NO_ACK}}}, \
543 \"screen\": {{\"cols\": {COLS}, \"rows\": {ROWS}, \"panel_rows\": {PANEL_ROWS}, \"glyphs\": {GLYPHS}, \
544 \"small_first_row\": {SMALL_FIRST_ROW}, \"small_rows\": {SMALL_ROWS}, \"small_cols\": {SMALL_COLS}, \
545 \"glyph_rows\": {GLYPH_ROWS}, \"ui_glyphs\": \"{UI_GLYPHS}\", \"del_glyph\": \"{DEL_GLYPH}\", \
546 \"palette\": [{}]}}, \
547 \"panel\": {{\"title_row\": {PANEL_TITLE_ROW}, \"label_row\": {PANEL_LABEL_ROW}, \"block_row\": {PANEL_BLOCK_ROW}, \
548 \"cell_row\": {PANEL_CELL_ROW}, \"cycles_col\": {PANEL_CYCLES_COL}, \"block_col\": {PANEL_BLOCK_COL}, \
549 \"block_tile\": {PANEL_BLOCK_TILE}, \"cell_col\": {PANEL_CELL_COL}, \"stack_col\": {PANEL_STACK_COL}, \
550 \"heap_col\": {PANEL_HEAP_COL}, \"blocks\": {}, \"cells\": {}, \"depth\": {}, \"heap\": {}}}, \
551 \"features\": {{\"engine\": {FEATURE_ENGINE}, \"small\": {FEATURE_SMALL}, \"totals\": {FEATURE_TOTALS}}}, \"pong\": [{}], \"activity\": [{}], \"totals\": [{}], \"engine_frame_max\": {ENGINE_FRAME_MAX}, \"baud\": {BAUD}}}",
552 kinds(&host::ALL),
553 kinds(&board::ALL),
554 palette.join(", "),
555 super::wprog_engine_rtl::WPE_HW_MAXBLK,
556 super::wprog_engine_rtl::WPE_HW_MAXCELL,
557 super::wprog_engine_rtl::WPE_HW_DEPTH,
558 super::wprog_engine_rtl::WPE_HW_HEAP,
559 PONG_FIELDS.iter().map(|f| format!("\"{f}\"")).collect::<Vec<_>>().join(", "),
560 layout(&ACTIVITY_FIELDS),
561 layout(&TOTALS_FIELDS),
562 )
563}
564
565fn layout(fields: &[(&str, usize, usize)]) -> String {
566 fields
567 .iter()
568 .map(|(name, bytes, count)| format!("{{\"name\": \"{name}\", \"bytes\": {bytes}, \"count\": {count}}}"))
569 .collect::<Vec<_>>()
570 .join(", ")
571}