A frame is exactly 1024 accepted input pairs: in_valid = 1 at a rising edge accepts one pair, with no backpressure. Bubbles are ignored. The product is registered at that edge and accumulated at the next edge. out_valid pulses only when the final product is accumulated, alongside the full 42-bit frame sum; the accumulator clears on that same edge so the next frame can follow without an idle cycle. Reset discards any partial frame. Rounding and saturation consume this full-precision output later.
Continuous input, accepted at edge E0 | Product stage after edge | Accumulator / output after edge |
|---|---|---|
E0: accept pair 0 | Product 0 valid | No old product to add |
E1: accept pair 1 | Product 1 valid | Add product 0 |
E1023: accept pair 1023 | Final product valid | Sum contains products 0..1022 |
E1024: next frame may start | Next frame's first product, if offered | Publish all 1024 products; clear accumulator/count |
E1025 | Continue next frame | A clocked consumer can sample the preceding frame's valid result before this update |
Each slot is one clock cycle, sampled just after the edge that opens it: slot 0 follows E1021. Every pair is a = b = 1, so a full frame sums to 1024, hex 400.
Slot 2: the frame's last pair was accepted at E1023, but its product is still in the register (v1_q is 1), so count_q reads 3ff (1023).
Slot 3: at E1024 that product is added. out_valid pulses for one cycle with frame_sum = 400, and count_q clears on the same edge.
Slots 3 to 5: in_valid drops for one cycle. One edge later v1_q drops, and in slot 5 count_q holds at 1 instead of advancing.
Rule: the count follows accepted products, one register behind in_valid, not clock cycles.
module frame_mac (
input logic clk, rst_n, in_valid,
input logic signed [15:0] a_i, b_i,
output logic out_valid,
output logic signed [41:0] frame_sum
);
logic v1_q;
logic signed [31:0] product_q;
logic signed [41:0] acc_q, next_sum;
logic [9:0] count_q;
assign next_sum = acc_q + product_q; // signed product extends to 42 bits
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
v1_q <= 1'b0;
out_valid <= 1'b0;
acc_q <= '0;
count_q <= '0;
frame_sum <= '0;
end else begin
v1_q <= in_valid;
out_valid <= 1'b0;
if (in_valid) product_q <= a_i * b_i;
if (v1_q) begin
if (count_q == 10'd1023) begin
frame_sum <= next_sum;
out_valid <= 1'b1;
acc_q <= '0;
count_q <= '0;
end else begin
acc_q <= next_sum;
count_q <= count_q + 1'b1;
end
end
end
end
endmodule
This is the implementation spine of the running sensor design: Q1.15 inputs produce a Q2.30 product, and the 42-bit accumulator keeps the same 30 fractional bits. The test first resets a 17-sample partial frame with a pending product, discards that reference epoch, then checks three fresh complete frames, including consecutive frames and bubbles. Every output-valid cycle must match the fresh model; no old partial completion is allowed.
Reset validity and frame state, not necessarily every wide data register.
Count accepted products, including the registered product latency, rather than clock cycles.
For variable-length frames, delay frame markers and tags with the product and define restart or abort explicitly.
Break feedback timing with partial sums or interleaved accumulators when one-cycle recurrence is too slow.
The accumulator has a loop-carried dependency: each add needs the previous sum. Its adder and register-to-register path set the recurrence timing.