diff --git a/NOW.md b/NOW.md index e5e1ba848..adae27733 100644 --- a/NOW.md +++ b/NOW.md @@ -2,6 +2,16 @@ Last updated: 2026-08-08 +## parser: silent statement drop is DEAD -- malformed input hard-errors (Closes #1940) + +- Statement-level and module-level "recovery" silently DROPPED malformed statements/declarations (fn bodies became unimplemented stubs, whole fns vanished); every drop site now returns a hard parse error with fn name + line +- Array-literal element capture depth-counts nested brackets (`[value, array[1], ...]` no longer truncates at the inner `]`) +- The negative-test contract (tests_compiler_rejects + parity test) flipped: malformed input must FAIL to compile, not "drop and keep going" +- bridge.t27's packet dispatch was a `match` STATEMENT the parser never supported -- the WHOLE dispatch was silently missing from the generated Verilog; rewritten as an if-chain and bridge.v regenerated +- SVA fixture spec rewritten in legal t27 (tail expr + bare assert) +- tri-net corpus: hardening surfaced 4 more latent-drop specs (fixed tri-net-side) +- FROZEN_HASH resealed + ## gen-rust: array literals emit elements, not empty vec![] (Closes #1938) - ExprArrayLiteral keeps element text in extra_size with no children; the Rust emitter mapped children only, so every spec array literal compiled to an empty Vec-typed vec![] (E0308 against a [T; N] return). Emits `[a, b, c]` / `[v; n]` from the text now, mirroring the Zig/C fixes diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index c790d2b6c..56dfe2cfc 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -980,6 +980,7 @@ impl Parser { // Skip tokens until we reach a top-level keyword (for keyword-style test/invariant/bench) // Handles nested braces, brackets, and parens so we don't stop inside nested groups + #[allow(dead_code)] fn skip_to_next_top_level(&mut self) { let mut paren_depth: i32 = 0; let mut bracket_depth: i32 = 0; @@ -1185,27 +1186,29 @@ impl Parser { Ok(decl) => { module.children.push(decl); } - Err(_) => { - // On parse error, skip to next top-level declaration and continue - self.skip_to_next_top_level(); + Err(e) => { + // A malformed declaration is a HARD error: the old + // skip-to-next-decl recovery silently DROPPED whole + // fns/tests from the module (t27#1940). + return Err(e); } } } else if self.is_top_level_start() { // Stray top-level keyword (e.g. `module` inside a module, or an - // unhandled `use`). Run it through the top-level parser so we - // error out and advance at least one token; otherwise - // skip_to_next_top_level can stop immediately on the same token - // and loop forever. - if self.parse_top_level_decl().is_err() { - self.skip_to_next_top_level(); + // unhandled `use`): the top-level parser produces the error. + if let Err(e) = self.parse_top_level_decl() { + return Err(e); } } else { match self.parse_body_stmt() { Ok(stmt) => { module.children.push(stmt); } - Err(_) => { - self.skip_to_next_top_level(); + Err(e) => { + return Err(format!( + "parse error at module level near line {}: {}", + self.current.line, e + )); } } } @@ -1888,9 +1891,15 @@ impl Parser { while self.current.kind != TokenKind::RBrace && self.current.kind != TokenKind::Eof { match self.parse_body_stmt() { Ok(stmt) => decl.children.push(stmt), - Err(_) => { - // On parse error, skip to next statement boundary and continue - self.recover_to_stmt_boundary(); + Err(e) => { + // A malformed statement is a HARD error. The old + // statement-level recovery silently DROPPED it and left + // the body unimplemented -- three tri-net specs shipped + // wrong generated code with no diagnostic (t27#1940). + return Err(format!( + "parse error in fn '{}' near line {}: {}", + decl.name, self.current.line, e + )); } } } @@ -1898,6 +1907,7 @@ impl Parser { } /// Skip tokens to recover to next statement boundary (semicolon or closing brace) + #[allow(dead_code)] fn recover_to_stmt_boundary(&mut self) { let mut brace_depth: i32 = 0; loop { @@ -2165,7 +2175,12 @@ impl Parser { while self.current.kind != TokenKind::RBrace && self.current.kind != TokenKind::Eof { match self.parse_body_stmt() { Ok(s) => then_block.children.push(s), - Err(_) => self.recover_to_stmt_boundary(), + Err(e) => { + return Err(format!( + "parse error near line {}: {}", + self.current.line, e + )) + } } } self.expect(TokenKind::RBrace)?; @@ -2197,7 +2212,12 @@ impl Parser { { match self.parse_body_stmt() { Ok(s) => else_block.children.push(s), - Err(_) => self.recover_to_stmt_boundary(), + Err(e) => { + return Err(format!( + "parse error near line {}: {}", + self.current.line, e + )) + } } } self.expect(TokenKind::RBrace)?; @@ -2226,7 +2246,12 @@ impl Parser { while self.current.kind != TokenKind::RBrace && self.current.kind != TokenKind::Eof { match self.parse_body_stmt() { Ok(s) => body_block.children.push(s), - Err(_) => self.recover_to_stmt_boundary(), + Err(e) => { + return Err(format!( + "parse error near line {}: {}", + self.current.line, e + )) + } } } self.expect(TokenKind::RBrace)?; @@ -2292,7 +2317,12 @@ impl Parser { while self.current.kind != TokenKind::RBrace && self.current.kind != TokenKind::Eof { match self.parse_body_stmt() { Ok(s) => body_block.children.push(s), - Err(_) => self.recover_to_stmt_boundary(), + Err(e) => { + return Err(format!( + "parse error near line {}: {}", + self.current.line, e + )) + } } } self.expect(TokenKind::RBrace)?; @@ -2320,7 +2350,12 @@ impl Parser { while self.current.kind != TokenKind::RBrace && self.current.kind != TokenKind::Eof { match self.parse_body_stmt() { Ok(s) => body_block.children.push(s), - Err(_) => self.recover_to_stmt_boundary(), + Err(e) => { + return Err(format!( + "parse error near line {}: {}", + self.current.line, e + )) + } } } self.expect(TokenKind::RBrace)?; @@ -2956,9 +2991,18 @@ impl Parser { self.advance(); } else { let mut bracket_content = String::new(); - while self.current.kind != TokenKind::RBracket + // Depth-count nested brackets: an element like `array[1]` + // used to cut the capture at ITS closing bracket, truncating + // the literal's element text (t27#1940). + let mut depth: i32 = 0; + while !(self.current.kind == TokenKind::RBracket && depth == 0) && self.current.kind != TokenKind::Eof { + match self.current.kind { + TokenKind::LBracket => depth += 1, + TokenKind::RBracket => depth -= 1, + _ => {} + } bracket_content.push_str(&self.current.lexeme); self.advance(); } @@ -25883,35 +25927,14 @@ mod tests_hir_pipeline_parity { #[test] fn test_parser_rejects_unknown_cast_type() { - // Variant E: `parse_cast_target_type` validates the base type so a typo - // like `x as widget` cannot silently lower to a 32-bit default cast. - // The function-body parser uses statement-level error recovery - // (`recover_to_stmt_boundary`), so the offending `return` is dropped - // rather than aborting the whole compile. The observable guarantee is - // therefore: the bogus type never reaches codegen -- no `widget` token - // and no cast assignment leak into the emitted Verilog; the body is left - // unimplemented instead. + // Hardened contract (t27#1940): the bogus cast type is now a + // hard compile error, not a silently-dropped statement. let src = r#"module BadCast { pub fn f(x: u8) -> u8 { return x as widget } }"#; - let v = Compiler::compile_verilog(src).unwrap(); - assert!( - !v.contains("widget"), - "unknown cast type `widget` must never reach codegen, got:\n{}", - v - ); - assert!( - !v.contains("f = "), - "the rejected cast statement must be dropped, not lowered, got:\n{}", - v - ); - assert!( - v.contains("// TODO: implement"), - "the recovered body should be left unimplemented, got:\n{}", - v - ); + assert!(Compiler::compile_verilog(src).is_err(), "bogus cast must fail to compile"); } #[test] @@ -26008,52 +26031,48 @@ mod tests_compiler_rejects { // VALID_CAST_TYPES, so the statement is dropped; `widget` never emitted. #[test] fn rejects_unknown_cast_type() { - let v = emit( + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( r#"module RejUnknownCast { pub fn f(x: u8) -> u8 { return x as widget } }"#, ); - assert_dropped(&v, "f", &["widget"]); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // Case 2: `as` with no type at all. `x as` (followed by `}`) -> the cast // parser hits a non-identifier and errors; statement dropped. #[test] fn rejects_cast_with_no_type() { - let v = emit( + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( r#"module RejNoType { pub fn g(x: u8) -> u8 { return x as } }"#, ); - // Body must be left unimplemented; the operand assignment must not leak. - assert!( - v.contains("// TODO: implement"), - "missing-type cast should drop the body, got:\n{}", - v - ); - assert!( - !v.contains("g = x"), - "a typeless cast must not lower to a bare assignment, got:\n{}", - v - ); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // Case 3: invalid width. `u3` is not a real integer width and is not in // VALID_CAST_TYPES -> rejected; `u3` never reaches codegen. #[test] fn rejects_invalid_width_u3() { - let v = emit( + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( r#"module RejBadWidth { pub fn h(x: u8) -> u8 { return x as u3 } }"#, ); - assert_dropped(&v, "h", &["u3"]); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // Case 4: nested cast where the inner target is invalid. `(x as widget) as @@ -26061,14 +26080,16 @@ mod tests_compiler_rejects { // neither `widget` nor a lowered assignment may appear. #[test] fn rejects_nested_cast_with_inner_error() { - let v = emit( + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( r#"module RejNestedCast { pub fn k(x: u8) -> u8 { return (x as widget) as u8 } }"#, ); - assert_dropped(&v, "k", &["widget"]); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // Case 5 (contrast / guard against over-rejection): a VALID cast still @@ -26122,27 +26143,31 @@ mod tests_compiler_rejects { // (a) Unclosed parenthesis in a return expression -> drop-to-TODO. #[test] fn rejects_unclosed_paren() { - let v = emit( + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( r#"module RejUnclosedParen { pub fn f(x: u8) -> u8 { return (x + 1 } }"#, ); - assert_dropped(&v, "f", &[]); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // (a) Malformed binary expression (`x + * 2`) -> drop-to-TODO. #[test] fn rejects_malformed_binop() { - let v = emit( + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( r#"module RejBadBinop { pub fn g(x: u8) -> u8 { return x + * 2 } }"#, ); - assert_dropped(&v, "g", &[]); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // (b) Unterminated module (missing closing brace) -> HARD compile error. @@ -26182,7 +26207,9 @@ mod tests_compiler_rejects { // closed it is now a rejection assertion (renamed accordingly). #[test] fn rejects_stray_ident() { - let v = emit( + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( r#"module GapStrayIdent { pub fn k(x: u8) -> u8 { frobnicate x @@ -26190,8 +26217,7 @@ mod tests_compiler_rejects { } }"#, ); - // The malformed statement is dropped; the bogus ident never leaks. - assert_dropped(&v, "k", &["frobnicate"]); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // ----------------------------------------------------------------------- // Variant Q: extend the negative-test contract from the statement level @@ -26246,8 +26272,12 @@ mod tests_compiler_rejects { // module still compiles. Neither bogus identifier reaches codegen. #[test] fn rejects_unknown_top_level_token() { - let v = emit(r#"module QUnknownDecl { gibberish foo }"#); - assert_decl_dropped(&v, "QUnknownDecl", &["gibberish", "foo"]); + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( + r#"module QUnknownDecl { gibberish foo }"#, + ); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // (a) DROP-RECOVERY that RESYNCS: a bogus leading token is dropped, then a @@ -26256,13 +26286,12 @@ mod tests_compiler_rejects { // or truncates the rest of the module. #[test] fn recovers_to_next_decl_after_unknown_token() { - let v = emit(r#"module QResync { zzzbogus pub const KEEP : u32 = 99 }"#); - assert_decl_dropped(&v, "QResync", &["zzzbogus"]); - assert!( - v.contains("KEEP = 99"), - "the valid const after the dropped token must still lower, got:\n{}", - v + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( + r#"module QResync { zzzbogus pub const KEEP : u32 = 99 }"#, ); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // (a) A `fn` declaration with no body (`pub fn f(x: u8) -> u8` with no @@ -26270,8 +26299,12 @@ mod tests_compiler_rejects { // reaches codegen and the module skeleton still emits. #[test] fn rejects_fn_declaration_without_body() { - let v = emit(r#"module QNoBody { pub fn weirdfn(x: u8) -> u8 }"#); - assert_decl_dropped(&v, "QNoBody", &["weirdfn"]); + // Hardened contract (t27#1940): a malformed statement/declaration + // is a COMPILE ERROR, not a silent drop. + let r = try_emit( + r#"module QNoBody { pub fn weirdfn(x: u8) -> u8 }"#, + ); + assert!(r.is_err(), "malformed input must fail to compile, got Ok"); } // (b) A stray token that does not lex as a skippable declaration start diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index 4095f9a1c..eef7cddb3 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -44c15f58d25650f0b0a4b3c89db3b844bdbb41b81040b01a8c7cd4b022f94bfd +4d10feb127277a49d610a3f22fd8350466dd7460a6ee4a831b872b6f60e5da25 diff --git a/bootstrap/tests/behavior_sva_v2.rs b/bootstrap/tests/behavior_sva_v2.rs index 565fb3687..514ef2560 100644 --- a/bootstrap/tests/behavior_sva_v2.rs +++ b/bootstrap/tests/behavior_sva_v2.rs @@ -318,14 +318,17 @@ fn write_minimal_t27_spec() -> String { let _ = fs::create_dir_all(&dir); let counter = TEST_COUNTER.fetch_add(1, Ordering::Relaxed); let path = dir.join(format!("spec_{}_{}.t27", std::process::id(), counter)); + // Legal t27 only: the hardened parser (t27#1940) rejects Rust-style + // tail expressions and bare asserts instead of silently dropping them. let spec = r#"module test_module; pub fn add(a: i32, b: i32) -> i32 { - a + b + return a + b; } test test_add { - assert add(1, 2) == 3; + x = add(1, 2); + assert(x == 3, "add"); } "#; fs::write(&path, spec).expect("write spec"); diff --git a/docs/NOW.md b/docs/NOW.md index 480ecdc9c..8d485b927 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -2,6 +2,16 @@ Last updated: 2026-08-08 +## parser: silent statement drop is DEAD -- malformed input hard-errors (Closes #1940) + +- Statement-level and module-level "recovery" silently DROPPED malformed statements/declarations (fn bodies became unimplemented stubs, whole fns vanished); every drop site now returns a hard parse error with fn name + line +- Array-literal element capture depth-counts nested brackets (`[value, array[1], ...]` no longer truncates at the inner `]`) +- The negative-test contract (tests_compiler_rejects + parity test) flipped: malformed input must FAIL to compile, not "drop and keep going" +- bridge.t27's packet dispatch was a `match` STATEMENT the parser never supported -- the WHOLE dispatch was silently missing from the generated Verilog; rewritten as an if-chain and bridge.v regenerated +- SVA fixture spec rewritten in legal t27 (tail expr + bare assert) +- tri-net corpus: hardening surfaced 4 more latent-drop specs (fixed tri-net-side) +- FROZEN_HASH resealed + ## gen-rust: array literals emit elements, not empty vec![] (Closes #1938) - ExprArrayLiteral keeps element text in extra_size with no children; the Rust emitter mapped children only, so every spec array literal compiled to an empty Vec-typed vec![] (E0308 against a [T; N] return). Emits `[a, b, c]` / `[v; n]` from the text now, mirroring the Zig/C fixes diff --git a/specs/fpga/bridge.t27 b/specs/fpga/bridge.t27 index 23f467c59..8b7defc8f 100644 --- a/specs/fpga/bridge.t27 +++ b/specs/fpga/bridge.t27 @@ -178,18 +178,24 @@ module FPGA_Bridge; return false; } - match bridge.packet_type { - PKT_UART_DATA => bridge_handle_uart_data(), - PKT_SPI_XFER => bridge_handle_spi_xfer(), - PKT_MAC_OP => bridge_handle_mac_op(), - PKT_STATUS => bridge_handle_status(), - PKT_CONFIG => bridge_handle_config(), - _ => { - // Unknown packet type - bridge.state = BRIDGE_IDLE; - bridge.rx_tail = bridge.rx_head; - return false; - } + // Dispatch by packet type. Was a `match` STATEMENT, which the parser + // has never supported -- the whole dispatch was silently DROPPED from + // the generated Verilog (t27#1940 hardening surfaced it). + if (bridge.packet_type == PKT_UART_DATA) { + bridge_handle_uart_data(); + } else if (bridge.packet_type == PKT_SPI_XFER) { + bridge_handle_spi_xfer(); + } else if (bridge.packet_type == PKT_MAC_OP) { + bridge_handle_mac_op(); + } else if (bridge.packet_type == PKT_STATUS) { + bridge_handle_status(); + } else if (bridge.packet_type == PKT_CONFIG) { + bridge_handle_config(); + } else { + // Unknown packet type + bridge.state = BRIDGE_IDLE; + bridge.rx_tail = bridge.rx_head; + return false; } bridge.state = BRIDGE_IDLE; diff --git a/specs/fpga/bridge.v b/specs/fpga/bridge.v index edf337c59..b8afb54d3 100644 --- a/specs/fpga/bridge.v +++ b/specs/fpga/bridge.v @@ -18,25 +18,74 @@ module FPGA_Bridge ( // Parameters (from const declarations) // ------------------------------------------------------- localparam [31:0] RX_BUFFER_SIZE = 256; - localparam [31:0] bridge = 0 /* Bridge_Unit {...} */; - localparam [31:0] rx_buffer = /* array [0;RX_BUFFER_SIZE]{} */; - localparam [31:0] tx_buffer = /* array [0;TX_BUFFER_SIZE]{} */; - localparam [7:0] PKT_UART_DATA = 8'h00; + localparam [31:0] TX_BUFFER_SIZE = 256; + localparam [31:0] SPI_BUFFER_SIZE = 64; + localparam [31:0] MAX_PACKET_SIZE = 128; + localparam [31:0] PACKET_TIMEOUT = 10000; + localparam [7:0] OP_MAC_MUL = 0; + localparam [7:0] OP_MAC_MAC = 1; + localparam [7:0] OP_MAC_MACC = 2; + localparam [7:0] OP_MAC_DOT = 3; + localparam [31:0] NUM_MAC_UNITS = 8; + localparam [7:0] BRIDGE_IDLE = 0; + localparam [7:0] BRIDGE_RX = 1; + localparam [7:0] BRIDGE_PARSE = 2; + localparam [7:0] BRIDGE_TX = 3; + localparam [7:0] BRIDGE_SPI = 4; + localparam [7:0] BRIDGE_MAC = 5; + reg [31:0] bridge; + initial begin + bridge = 0 /* Bridge_Unit {...} */; + end + + reg [31:0] rx_buffer; + initial begin + rx_buffer = 0 /* TODO: array literal [0;RX_BUFFER_SIZE] not yet lowered to Verilog */; + end + + reg [31:0] tx_buffer; + initial begin + tx_buffer = 0 /* TODO: array literal [0;TX_BUFFER_SIZE] not yet lowered to Verilog */; + end + + localparam [7:0] PKT_UART_DATA = 0; + localparam [7:0] PKT_SPI_XFER = 16; + localparam [7:0] PKT_MAC_OP = 32; + localparam [7:0] PKT_STATUS = 48; + localparam [7:0] PKT_CONFIG = 64; + + // ------------------------------------------------------- + // R-SI-1: multiplication helper (no `*` operator) + // ------------------------------------------------------- + function [63:0] __mul_noop; // t27#1886: 64-bit, u64 products no longer truncate + input [63:0] a; + input [63:0] b; + integer i; + reg [127:0] acc; + begin + acc = 128'd0; + for (i = 0; i < 64; i = i + 1) begin + if (b[i]) acc = acc + ({64'd0, a} << i); + end + __mul_noop = acc[63:0]; + end + endfunction // ------------------------------------------------------- // Registers (from struct declarations) // ------------------------------------------------------- // struct Bridge_Unit - reg [7:0] bridge_unit_state; // Bridge_Unit.state - reg [31:0] bridge_unit_rx_head; // Bridge_Unit.rx_head - reg [31:0] bridge_unit_rx_tail; // Bridge_Unit.rx_tail - reg [31:0] bridge_unit_tx_head; // Bridge_Unit.tx_head - reg [31:0] bridge_unit_tx_tail; // Bridge_Unit.tx_tail - reg [7:0] bridge_unit_packet_len; // Bridge_Unit.packet_len - reg [7:0] bridge_unit_packet_type; // Bridge_Unit.packet_type - reg [31:0] bridge_unit_timeout_cnt; // Bridge_Unit.timeout_cnt - reg bridge_unit_spi_enabled; // Bridge_Unit.spi_enabled - reg bridge_unit_mac_enabled; // Bridge_Unit.mac_enabled + // UNSUPPORTED_ICARUS: struct Bridge_Unit contains non-lowerable fields + reg [7:0] bridge_state; // Bridge_Unit.state + reg [31:0] bridge_rx_head; // Bridge_Unit.rx_head + reg [31:0] bridge_rx_tail; // Bridge_Unit.rx_tail + reg [31:0] bridge_tx_head; // Bridge_Unit.tx_head + reg [31:0] bridge_tx_tail; // Bridge_Unit.tx_tail + reg [7:0] bridge_packet_len; // Bridge_Unit.packet_len + reg [7:0] bridge_packet_type; // Bridge_Unit.packet_type + reg [31:0] bridge_timeout_cnt; // Bridge_Unit.timeout_cnt + reg bridge_spi_enabled; // Bridge_Unit.spi_enabled + reg bridge_mac_enabled; // Bridge_Unit.mac_enabled assign ready = 1'b1; @@ -50,11 +99,33 @@ module FPGA_Bridge ( input [31:0] size; input [31:0] head; input [7:0] data; - begin + begin : buffer_write_body + reg [31:0] new_head; + new_head = ((head + 1) % size); if (((new_head == 0) && (head == (size - 1)))) begin buffer_write = 1'b0; + end else begin + buf_in[head] = data; + buffer_write = 1'b1; + end + end + endfunction + + // function: buffer_read + function [39:0] buffer_read; // -> (u8, usize) + input [31:0] \buf ; + input [31:0] size; + input [31:0] tail; + begin : buffer_read_body + reg [31:0] data; + reg [31:0] new_tail; + if ((tail == size)) begin + buffer_read = {0, 0}; + end else begin + data = \buf [tail]; + new_tail = ((tail + 1) % size); + buffer_read = {new_tail, data}; end - buffer_write = 1'b1; end endfunction @@ -63,7 +134,7 @@ module FPGA_Bridge ( input [31:0] head; input [31:0] tail; input [31:0] size; - begin + begin : buffer_count_body if ((head >= tail)) begin buffer_count = (head - tail); end else begin @@ -74,109 +145,187 @@ module FPGA_Bridge ( // function: bridge_rx_available function [31:0] bridge_rx_available; // -> usize - begin + input _unused; + begin : bridge_rx_available_body bridge_rx_available = buffer_count(bridge_rx_head, bridge_rx_tail, RX_BUFFER_SIZE); end endfunction // function: bridge_tx_space function [31:0] bridge_tx_space; // -> usize - begin + input _unused; + begin : bridge_tx_space_body bridge_tx_space = (TX_BUFFER_SIZE - buffer_count(bridge_tx_head, bridge_tx_tail, TX_BUFFER_SIZE)); end endfunction // function: bridge_parse_header function bridge_parse_header; // -> bool - begin - if ((bridge_rx_available() < 2)) begin - bridge_parse_header = 1'b0; - end - reg [31:0] ptype = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); - reg [31:0] plen = buffer_read(rx_buffer, RX_BUFFER_SIZE, ptype); - if ((plen > MAX_PACKET_SIZE)) begin + input _unused; + begin : bridge_parse_header_body + reg [31:0] ptype; + reg [31:0] plen; + if ((bridge_rx_available(1'b0) < 2)) begin bridge_parse_header = 1'b0; + end else begin + ptype = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + plen = buffer_read(rx_buffer, RX_BUFFER_SIZE, ptype); + bridge_rx_tail = plen; + bridge_packet_type = ptype; + bridge_packet_len = plen; + if ((plen > MAX_PACKET_SIZE)) begin + bridge_parse_header = 1'b0; + end else begin + bridge_state = BRIDGE_PARSE; + bridge_timeout_cnt = 0; + bridge_parse_header = 1'b1; + end end - bridge_parse_header = 1'b1; end endfunction // function: bridge_process_payload function bridge_process_payload; // -> bool - begin - bridge_process_payload = 1'b1; + input _unused; + begin : bridge_process_payload_body + if ((bridge_rx_available(1'b0) < (bridge_packet_len & {32{1'b1}}))) begin + bridge_timeout_cnt = (bridge_timeout_cnt + 1); + if ((bridge_timeout_cnt > PACKET_TIMEOUT)) begin + bridge_state = BRIDGE_IDLE; + bridge_rx_tail = bridge_rx_head; + end + bridge_process_payload = 1'b0; + end else begin + if ((bridge_packet_type == PKT_UART_DATA)) begin + bridge_handle_uart_data(1'b0); + end else if ((bridge_packet_type == PKT_SPI_XFER)) begin + bridge_handle_spi_xfer(1'b0); + end else if ((bridge_packet_type == PKT_MAC_OP)) begin + bridge_handle_mac_op(1'b0); + end else if ((bridge_packet_type == PKT_STATUS)) begin + bridge_handle_status(1'b0); + end else if ((bridge_packet_type == PKT_CONFIG)) begin + bridge_handle_config(1'b0); + end else begin + bridge_state = BRIDGE_IDLE; + bridge_rx_tail = bridge_rx_head; + bridge_process_payload = 1'b0; + end + bridge_state = BRIDGE_IDLE; + bridge_process_payload = 1'b1; + end end endfunction // function: bridge_handle_uart_data task bridge_handle_uart_data; - // TODO: implement + begin : bridge_handle_uart_data_body + reg [31:0] i; + reg [31:0] result_read; + reg [31:0] data; + reg [31:0] ok; + reg [31:0] new_head; + i = 0; + while ((i < (bridge_packet_len & {32{1'b1}}))) begin + result_read = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + data = result_read; + bridge_rx_tail = result_read; + if ((bridge_tx_space(1'b0) > 0)) begin + ok = 1'b1; + new_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); + tx_buffer[bridge_tx_head] = data; + bridge_tx_head = new_head; + end + i = (i + 1); + end + end endtask // function: bridge_handle_spi_xfer task bridge_handle_spi_xfer; - begin - if ((!bridge_spi_enabled || spi_is_busy())) begin - end - reg [31:0] cs_sel = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); - reg [31:0] data_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, cs_sel); - reg [31:0] data_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, data_l); - if (spi_transfer(data)) begin + begin : bridge_handle_spi_xfer_body + reg [31:0] cs_sel; + reg [31:0] data_l; + reg [31:0] data_h; + reg [31:0] data; + if ((!bridge_spi_enabled || spi_is_busy(1'b0))) begin + end else begin + cs_sel = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + data_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, cs_sel); + data_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, data_l); + bridge_rx_tail = data_h; + data = (((data_h & {32{1'b1}}) << 8) | (data_l & {32{1'b1}})); + if (spi_transfer(data)) begin + end end end endtask // function: bridge_handle_mac_op task bridge_handle_mac_op; - begin + begin : bridge_handle_mac_op_body + reg [31:0] op_byte; + reg [31:0] unit_byte; + reg [31:0] a_l; + reg [31:0] a_h; + reg [31:0] b_l; + reg [31:0] b_h; + reg [31:0] operand_a; + reg [31:0] operand_b; + reg [31:0] acc; if (!bridge_mac_enabled) begin - end - if ((bridge_rx_available() < 6)) begin - end - reg [31:0] op_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); - reg [31:0] unit_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); - reg [31:0] a_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); - reg [31:0] a_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); - reg [31:0] b_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); - reg [31:0] b_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); - if ((unit_byte >= NUM_MAC_UNITS)) begin - end - if ((op_byte == OP_MAC_MUL)) begin - mac_multiply(operand_a, operand_b, unit_byte); - end else if ((op_byte == OP_MAC_MAC)) begin - mac_cycle(operand_a, operand_b, unit_byte, mac_get_accumulator(unit_byte)); - end else if ((op_byte == OP_MAC_DOT)) begin - mac_dot_product(/* array [operand_a]{} */, /* array [operand_b]{} */, 1, unit_byte); - end - if ((bridge_tx_space() >= 4)) begin - reg [31:0] acc = mac_get_accumulator(unit_byte); - as; - u32; - tx_buffer[bridge_tx_head] = (acc && 8'hFF); - as; - u8; - bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); - tx_buffer[bridge_tx_head] = ((acc >> 8) && 8'hFF); - as; - u8; - bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); - tx_buffer[bridge_tx_head] = ((acc >> 16) && 8'hFF); - as; - u8; - bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); - tx_buffer[bridge_tx_head] = ((acc >> 24) && 8'hFF); - as; - u8; - bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); + end else begin + if ((bridge_rx_available(1'b0) < 6)) begin + end else begin + op_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + bridge_rx_tail = op_byte; + unit_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + bridge_rx_tail = unit_byte; + a_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + bridge_rx_tail = a_l; + a_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + bridge_rx_tail = a_h; + b_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + bridge_rx_tail = b_l; + b_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + bridge_rx_tail = b_h; + if ((unit_byte >= NUM_MAC_UNITS)) begin + end else begin + operand_a = (((a_h & {16{1'b1}}) << 8) | (a_l & {16{1'b1}})); + operand_b = (((b_h & {16{1'b1}}) << 8) | (b_l & {16{1'b1}})); + if ((op_byte == OP_MAC_MUL)) begin + mac_multiply(operand_a, operand_b, unit_byte); + end else if ((op_byte == OP_MAC_MAC)) begin + mac_cycle(operand_a, operand_b, unit_byte, mac_get_accumulator(unit_byte)); + end else if ((op_byte == OP_MAC_DOT)) begin + mac_dot_product(0 /* TODO: array literal [operand_a] not yet lowered to Verilog */, 0 /* TODO: array literal [operand_b] not yet lowered to Verilog */, 1, unit_byte); + end + if ((bridge_tx_space(1'b0) >= 4)) begin + acc = (mac_get_accumulator(unit_byte) & {32{1'b1}}); + tx_buffer[bridge_tx_head] = ((acc & 255) & {8{1'b1}}); + bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); + tx_buffer[bridge_tx_head] = (((acc >> 8) & 255) & {8{1'b1}}); + bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); + tx_buffer[bridge_tx_head] = (((acc >> 16) & 255) & {8{1'b1}}); + bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); + tx_buffer[bridge_tx_head] = (((acc >> 24) & 255) & {8{1'b1}}); + bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); + end + end + end end end endtask // function: bridge_handle_status task bridge_handle_status; - begin + begin : bridge_handle_status_body + reg [31:0] status; + reg [31:0] i; + status = 0 /* TODO: array literal [if(bridge.spi_enabled){1}else{0},if(bridge.mac_enabled){1}else{0},0,0,] not yet lowered to Verilog */; + i = 0; while ((i < 4)) begin - if ((bridge_tx_space() > 0)) begin + if ((bridge_tx_space(1'b0) > 0)) begin tx_buffer[bridge_tx_head] = status[i]; bridge_tx_head = ((bridge_tx_head + 1) % TX_BUFFER_SIZE); end @@ -187,14 +336,18 @@ module FPGA_Bridge ( // function: bridge_handle_config task bridge_handle_config; - begin - reg [31:0] cfg_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + begin : bridge_handle_config_body + reg [31:0] cfg_byte; + cfg_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge_rx_tail); + bridge_rx_tail = cfg_byte; + bridge_spi_enabled = ((cfg_byte & 1) != 0); + bridge_mac_enabled = ((cfg_byte & 2) != 0); end endtask // ------------------------------------------------------- // Test assertions (from test blocks) // ------------------------------------------------------- - // synthesis translate_off + `ifndef SIMULATION // test: bridge_initially_idle initial begin : bridge_initially_idle_test $display("[TEST] bridge_initially_idle : starting"); @@ -300,7 +453,7 @@ module FPGA_Bridge ( $display("[TEST] bridge_mac_handler_rejects_invalid_unit : starting"); $display("[TEST] bridge_mac_handler_rejects_invalid_unit : PASSED"); end - // synthesis translate_on + `endif // ------------------------------------------------------- // Invariant checks (compile-time assertions) @@ -319,30 +472,44 @@ module FPGA_Bridge ( // ------------------------------------------------------- // Benchmark blocks (simulation only) // ------------------------------------------------------- - initial begin : bridge_rx_write_latency_bench // synthesis translate_off + `ifndef SIMULATION + integer _bench_bridge_rx_write_latency_cycles = 0; + integer _bench_bridge_tx_read_latency_cycles = 0; + integer _bench_bridge_parse_header_latency_cycles = 0; + integer _bench_bridge_packet_processing_latency_cycles = 0; + `endif + `ifndef SIMULATION + initial begin : bridge_rx_write_latency_bench $display("[BENCH] bridge_rx_write_latency : starting"); - integer _bench_cycles = 0; - $display("[BENCH] bridge_rx_write_latency : %%0d cycles", _bench_cycles); - $display("[BENCH] bridge_rx_write_latency : DONE"); - end // synthesis translate_on - initial begin : bridge_tx_read_latency_bench // synthesis translate_off + _bench_bridge_rx_write_latency_cycles = 0; + $display("[BENCH] bridge_rx_write_latency : %%0d cycles", _bench_bridge_rx_write_latency_cycles); + $display("[BENCH] bridge_rx_write_latency : PASSED"); + end + `endif + `ifndef SIMULATION + initial begin : bridge_tx_read_latency_bench $display("[BENCH] bridge_tx_read_latency : starting"); - integer _bench_cycles = 0; - $display("[BENCH] bridge_tx_read_latency : %%0d cycles", _bench_cycles); - $display("[BENCH] bridge_tx_read_latency : DONE"); - end // synthesis translate_on - initial begin : bridge_parse_header_latency_bench // synthesis translate_off + _bench_bridge_tx_read_latency_cycles = 0; + $display("[BENCH] bridge_tx_read_latency : %%0d cycles", _bench_bridge_tx_read_latency_cycles); + $display("[BENCH] bridge_tx_read_latency : PASSED"); + end + `endif + `ifndef SIMULATION + initial begin : bridge_parse_header_latency_bench $display("[BENCH] bridge_parse_header_latency : starting"); - integer _bench_cycles = 0; - $display("[BENCH] bridge_parse_header_latency : %%0d cycles", _bench_cycles); - $display("[BENCH] bridge_parse_header_latency : DONE"); - end // synthesis translate_on - initial begin : bridge_packet_processing_latency_bench // synthesis translate_off + _bench_bridge_parse_header_latency_cycles = 0; + $display("[BENCH] bridge_parse_header_latency : %%0d cycles", _bench_bridge_parse_header_latency_cycles); + $display("[BENCH] bridge_parse_header_latency : PASSED"); + end + `endif + `ifndef SIMULATION + initial begin : bridge_packet_processing_latency_bench $display("[BENCH] bridge_packet_processing_latency : starting"); - integer _bench_cycles = 0; - $display("[BENCH] bridge_packet_processing_latency : %%0d cycles", _bench_cycles); - $display("[BENCH] bridge_packet_processing_latency : DONE"); - end // synthesis translate_on + _bench_bridge_packet_processing_latency_cycles = 0; + $display("[BENCH] bridge_packet_processing_latency : %%0d cycles", _bench_bridge_packet_processing_latency_cycles); + $display("[BENCH] bridge_packet_processing_latency : PASSED"); + end + `endif endmodule