|
1 | 1 | // ============================================================================= |
2 | 2 | // Project: Open Cognitive Core Project (OCCP) |
3 | | -// File: hdc_ngram_encoder_v3.sv |
4 | | -// Description: N-gram encoder for Hyperdimensional Computing (HDC) |
5 | | -// - Configurable n-gram size (2-4) |
6 | | -// - LFSR-based token-to-hypervector mapping |
7 | | -// - Circular shift permutation for positional encoding |
8 | | -// - AND-binding for n-gram composition |
9 | | -// - clear_context support for sequence boundaries |
10 | | -// - ngram_valid output for downstream handshake |
| 3 | +// File: hdc_ngram_encoder_v4_pipelined.sv |
| 4 | +// Description: Pipelined N-gram encoder with 4-stage token_to_hv |
| 5 | +// - Breaks 1024-bit LFSR hash into 4 x 256-bit stages |
| 6 | +// - Reduces critical path by 4x |
| 7 | +// - Maintains identical output to v3 (bit-accurate) |
11 | 8 | // |
12 | | -// Architecture: |
13 | | -// token_in -> token_to_hv() -> shift_reg[] -> permute_cw() -> AND-bind -> ngram_hv |
| 9 | +// Pipeline: |
| 10 | +// Stage 0: Accept token, compute HV bits [0:255] |
| 11 | +// Stage 1: Compute HV bits [256:511] |
| 12 | +// Stage 2: Compute HV bits [512:767] |
| 13 | +// Stage 3: Compute HV bits [768:1023] + shift register update |
| 14 | +// Stage 4: Permutation + AND-binding + output |
14 | 15 | // |
15 | | -// Latency: 1 clock cycle (combinational binding after registered shift) |
16 | | -// Interface: Simple valid/enable handshake |
| 16 | +// Total Latency: 5 clock cycles |
| 17 | +// Throughput: 1 token/cycle (after pipeline fill) |
17 | 18 | // |
18 | 19 | // Author: OCCP Contributors |
19 | | -// Version: 1.1.0 (bugfix release) |
| 20 | +// Version: 2.0.0 (pipelined architecture) |
20 | 21 | // License: CERN-OHL-W v2 |
21 | 22 | // ============================================================================= |
22 | 23 |
|
23 | 24 | `ifndef SYNTHESIS |
24 | 25 | `timescale 1ns/1ps |
25 | 26 | `endif |
26 | 27 |
|
27 | | -module hdc_ngram_encoder_v3 #( |
28 | | - parameter HV_DIM = 1024, // Hypervector dimensionality |
29 | | - parameter NGRAM_SIZE = 3, // N-gram window size (2, 3, or 4) |
30 | | - parameter TOKEN_WIDTH = 16 // Input token bit-width |
| 28 | +module hdc_ngram_encoder_v4_pipelined #( |
| 29 | + parameter HV_DIM = 1024, |
| 30 | + parameter NGRAM_SIZE = 3, |
| 31 | + parameter TOKEN_WIDTH = 16, |
| 32 | + parameter PIPE_STAGES = 4 // Number of HV computation stages |
31 | 33 | )( |
32 | 34 | input logic clk, |
33 | 35 | input logic rst_n, |
34 | | - input logic en, // Encoding enable |
35 | | - input logic clear_context, // Reset sequence state |
36 | | - input logic [TOKEN_WIDTH-1:0] token_in, // Current input token |
37 | | - output logic [HV_DIM-1:0] ngram_hv, // Encoded n-gram hypervector |
38 | | - output logic ngram_valid // Valid when full n-gram available |
| 36 | + input logic en, |
| 37 | + input logic clear_context, |
| 38 | + input logic [TOKEN_WIDTH-1:0] token_in, |
| 39 | + output logic [HV_DIM-1:0] ngram_hv, |
| 40 | + output logic ngram_valid |
39 | 41 | ); |
40 | 42 |
|
| 43 | + // ========================================================================= |
| 44 | + // Local Parameters |
| 45 | + // ========================================================================= |
| 46 | + localparam NUM_SHIFTS = NGRAM_SIZE - 1; |
| 47 | + localparam BITS_PER_STG = HV_DIM / PIPE_STAGES; // 256 bits per stage |
| 48 | + localparam LFSR_SEED = 32'hDEADBEEF; |
| 49 | + |
41 | 50 | // ========================================================================= |
42 | 51 | // Parameter Validation |
43 | 52 | // ========================================================================= |
44 | 53 | initial begin |
| 54 | + if (HV_DIM % PIPE_STAGES != 0) |
| 55 | + $fatal(1, "HV_DIM must be divisible by PIPE_STAGES"); |
45 | 56 | if (NGRAM_SIZE < 2 || NGRAM_SIZE > 4) |
46 | | - $fatal(1, "NGRAM_SIZE must be 2, 3, or 4. Got: %0d", NGRAM_SIZE); |
47 | | - if (HV_DIM < 64 || HV_DIM % 64 != 0) |
48 | | - $fatal(1, "HV_DIM must be a multiple of 64 and >= 64. Got: %0d", HV_DIM); |
| 57 | + $fatal(1, "NGRAM_SIZE must be 2, 3, or 4"); |
49 | 58 | end |
50 | 59 |
|
51 | 60 | // ========================================================================= |
52 | | - // Local Parameters |
| 61 | + // Function: LFSR Step (single iteration) |
| 62 | + // Polynomial: x^32 + x^22 + x^2 + x + 1 |
53 | 63 | // ========================================================================= |
54 | | - localparam NUM_SHIFTS = NGRAM_SIZE - 1; // Number of history slots |
| 64 | + function automatic logic [31:0] lfsr_step(input logic [31:0] state); |
| 65 | + logic feedback; |
| 66 | + feedback = state[31] ^ state[21] ^ state[1] ^ state[0]; |
| 67 | + lfsr_step = {state[30:0], feedback}; |
| 68 | + endfunction |
55 | 69 |
|
56 | 70 | // ========================================================================= |
57 | | - // Internal Signals |
| 71 | + // Function: Compute HV chunk (BITS_PER_STG bits) |
| 72 | + // Advances LFSR by 'start_bit' iterations, then generates chunk |
58 | 73 | // ========================================================================= |
59 | | - logic [TOKEN_WIDTH-1:0] token_shift [0:NUM_SHIFTS-1]; // Token history |
60 | | - logic [HV_DIM-1:0] shift_reg [0:NUM_SHIFTS-1]; // HV history |
61 | | - logic valid_shift [0:NUM_SHIFTS-1]; // Validity tracking |
| 74 | + function automatic logic [BITS_PER_STG-1:0] compute_chunk( |
| 75 | + input logic [TOKEN_WIDTH-1:0] token, |
| 76 | + input int unsigned start_bit |
| 77 | + ); |
| 78 | + logic [31:0] lfsr; |
| 79 | + logic [BITS_PER_STG-1:0] chunk; |
| 80 | + |
| 81 | + // Initialize LFSR |
| 82 | + lfsr = {16'b0, token} ^ LFSR_SEED; |
| 83 | + |
| 84 | + // Skip to start position |
| 85 | + for (int i = 0; i < start_bit; i++) |
| 86 | + lfsr = lfsr_step(lfsr); |
| 87 | + |
| 88 | + // Generate chunk bits |
| 89 | + for (int i = 0; i < BITS_PER_STG; i++) begin |
| 90 | + lfsr = lfsr_step(lfsr); |
| 91 | + chunk[i] = lfsr[15] ^ lfsr[7] ^ lfsr[0]; |
| 92 | + end |
| 93 | + |
| 94 | + return chunk; |
| 95 | + endfunction |
62 | 96 |
|
63 | 97 | // ========================================================================= |
64 | | - // Function: permute_cw |
65 | | - // Circular left-shift permutation for positional encoding. |
66 | | - // Uses modulo to prevent undefined behavior when shift >= HV_DIM. |
| 98 | + // Pipeline Stage 0: Accept token, compute bits [0:255] |
67 | 99 | // ========================================================================= |
68 | | - function automatic logic [HV_DIM-1:0] permute_cw( |
69 | | - input logic [HV_DIM-1:0] hv, |
70 | | - input int unsigned shift_amount |
71 | | - ); |
72 | | - int unsigned safe_shift; |
73 | | - safe_shift = shift_amount % HV_DIM; |
74 | | - permute_cw = (hv << safe_shift) | (hv >> (HV_DIM - safe_shift)); |
75 | | - endfunction |
| 100 | + logic [TOKEN_WIDTH-1:0] pipe_token [0:PIPE_STAGES]; |
| 101 | + logic [BITS_PER_STG-1:0] pipe_chunk [0:PIPE_STAGES-1]; |
| 102 | + logic pipe_valid [0:PIPE_STAGES]; |
| 103 | + logic pipe_clear [0:PIPE_STAGES]; |
| 104 | + |
| 105 | + always_ff @(posedge clk or negedge rst_n) begin |
| 106 | + if (!rst_n) begin |
| 107 | + pipe_token[0] <= '0; |
| 108 | + pipe_valid[0] <= 1'b0; |
| 109 | + pipe_clear[0] <= 1'b0; |
| 110 | + end else begin |
| 111 | + pipe_token[0] <= token_in; |
| 112 | + pipe_valid[0] <= en & ~clear_context; |
| 113 | + pipe_clear[0] <= clear_context; |
| 114 | + end |
| 115 | + end |
| 116 | + |
| 117 | + // Compute chunk 0: bits [0:255] |
| 118 | + always_ff @(posedge clk or negedge rst_n) begin |
| 119 | + if (!rst_n) |
| 120 | + pipe_chunk[0] <= '0; |
| 121 | + else if (en) |
| 122 | + pipe_chunk[0] <= compute_chunk(token_in, 0); |
| 123 | + end |
76 | 124 |
|
77 | 125 | // ========================================================================= |
78 | | - // Function: token_to_hv |
79 | | - // Maps a token to a pseudo-random hypervector using a 32-bit LFSR. |
80 | | - // Each output bit depends on both the token value AND its position (i), |
81 | | - // ensuring non-degenerate, approximately balanced output (~50% ones). |
82 | | - // |
83 | | - // LFSR polynomial: x^32 + x^22 + x^2 + x + 1 (maximal-length) |
| 126 | + // Pipeline Stages 1-3: Compute remaining chunks |
84 | 127 | // ========================================================================= |
85 | | - function automatic logic [HV_DIM-1:0] token_to_hv( |
86 | | - input logic [TOKEN_WIDTH-1:0] token |
87 | | - ); |
88 | | - logic [HV_DIM-1:0] result; |
89 | | - logic [31:0] lfsr; |
90 | | - logic feedback; |
| 128 | + genvar s; |
| 129 | + generate |
| 130 | + for (s = 1; s < PIPE_STAGES; s++) begin : gen_pipe_stages |
91 | 131 |
|
92 | | - // Seed LFSR with token-derived value (XOR with constant for diffusion) |
93 | | - lfsr = {16'b0, token} ^ 32'hDEADBEEF; |
| 132 | + always_ff @(posedge clk or negedge rst_n) begin |
| 133 | + if (!rst_n) begin |
| 134 | + pipe_token[s] <= '0; |
| 135 | + pipe_valid[s] <= 1'b0; |
| 136 | + pipe_clear[s] <= 1'b0; |
| 137 | + end else begin |
| 138 | + pipe_token[s] <= pipe_token[s-1]; |
| 139 | + pipe_valid[s] <= pipe_valid[s-1]; |
| 140 | + pipe_clear[s] <= pipe_clear[s-1]; |
| 141 | + end |
| 142 | + end |
94 | 143 |
|
95 | | - for (int i = 0; i < HV_DIM; i++) begin |
96 | | - // LFSR feedback: taps at bits 31, 21, 1, 0 |
97 | | - feedback = lfsr[31] ^ lfsr[21] ^ lfsr[1] ^ lfsr[0]; |
98 | | - lfsr = {lfsr[30:0], feedback}; |
| 144 | + always_ff @(posedge clk or negedge rst_n) begin |
| 145 | + if (!rst_n) |
| 146 | + pipe_chunk[s] <= '0; |
| 147 | + else if (pipe_valid[s-1]) |
| 148 | + pipe_chunk[s] <= compute_chunk( |
| 149 | + pipe_token[s-1], |
| 150 | + s * BITS_PER_STG |
| 151 | + ); |
| 152 | + end |
99 | 153 |
|
100 | | - // Output bit: XOR of multiple LFSR taps for better distribution |
101 | | - result[i] = lfsr[15] ^ lfsr[7] ^ lfsr[0]; |
102 | 154 | end |
| 155 | + endgenerate |
103 | 156 |
|
104 | | - return result; |
105 | | - endfunction |
| 157 | + // ========================================================================= |
| 158 | + // Assemble Full HV from Pipeline Chunks |
| 159 | + // ========================================================================= |
| 160 | + logic [HV_DIM-1:0] current_hv; |
| 161 | + |
| 162 | + always_comb begin |
| 163 | + for (int i = 0; i < PIPE_STAGES; i++) begin |
| 164 | + current_hv[i*BITS_PER_STG +: BITS_PER_STG] = pipe_chunk[i]; |
| 165 | + end |
| 166 | + end |
106 | 167 |
|
107 | 168 | // ========================================================================= |
108 | | - // Shift Register Update (Sequential) |
109 | | - // On each valid clock: shift history and insert new token/HV. |
110 | | - // clear_context resets all history for sequence boundary handling. |
| 169 | + // Shift Register (updated at pipeline output) |
111 | 170 | // ========================================================================= |
| 171 | + logic [HV_DIM-1:0] shift_reg [0:NUM_SHIFTS-1]; |
| 172 | + logic valid_shift [0:NUM_SHIFTS-1]; |
| 173 | + logic pipe_out_valid; |
| 174 | + logic pipe_out_clear; |
| 175 | + |
| 176 | + assign pipe_out_valid = pipe_valid[PIPE_STAGES-1]; |
| 177 | + assign pipe_out_clear = pipe_clear[PIPE_STAGES-1]; |
| 178 | + |
112 | 179 | always_ff @(posedge clk or negedge rst_n) begin |
113 | | - if (!rst_n || clear_context) begin |
| 180 | + if (!rst_n || pipe_out_clear) begin |
114 | 181 | for (int i = 0; i < NUM_SHIFTS; i++) begin |
115 | | - token_shift[i] <= '0; |
116 | 182 | shift_reg[i] <= '0; |
117 | 183 | valid_shift[i] <= 1'b0; |
118 | 184 | end |
119 | | - end else if (en) begin |
120 | | - // Shift history: slot[i] <- slot[i-1] |
| 185 | + end else if (pipe_out_valid) begin |
121 | 186 | for (int i = NUM_SHIFTS-1; i > 0; i--) begin |
122 | | - token_shift[i] <= token_shift[i-1]; |
123 | 187 | shift_reg[i] <= shift_reg[i-1]; |
124 | 188 | valid_shift[i] <= valid_shift[i-1]; |
125 | 189 | end |
126 | | - // Insert current token at slot[0] |
127 | | - token_shift[0] <= token_in; |
128 | | - shift_reg[0] <= token_to_hv(token_in); |
| 190 | + shift_reg[0] <= current_hv; |
129 | 191 | valid_shift[0] <= 1'b1; |
130 | 192 | end |
131 | 193 | end |
132 | 194 |
|
133 | 195 | // ========================================================================= |
134 | | - // N-gram Binding (Combinational) |
135 | | - // Binds current token HV with permuted history HVs using AND operation. |
136 | | - // Position i is permuted by (i+1)*17 bits for orthogonality. |
137 | | - // Only valid history entries participate in binding. |
| 196 | + // Permutation + AND-Binding (Combinational) |
138 | 197 | // ========================================================================= |
| 198 | + function automatic logic [HV_DIM-1:0] permute_cw( |
| 199 | + input logic [HV_DIM-1:0] hv, |
| 200 | + input int unsigned shift_amount |
| 201 | + ); |
| 202 | + int unsigned safe_shift; |
| 203 | + safe_shift = shift_amount % HV_DIM; |
| 204 | + permute_cw = (hv << safe_shift) | (hv >> (HV_DIM - safe_shift)); |
| 205 | + endfunction |
| 206 | + |
139 | 207 | logic [HV_DIM-1:0] permuted_hv [0:NUM_SHIFTS-1]; |
140 | 208 | logic [HV_DIM-1:0] bound_hv; |
141 | 209 |
|
142 | | - // Compute permuted hypervectors for each history position |
143 | 210 | always_comb begin |
144 | | - for (int i = 0; i < NUM_SHIFTS; i++) begin |
| 211 | + for (int i = 0; i < NUM_SHIFTS; i++) |
145 | 212 | permuted_hv[i] = permute_cw(shift_reg[i], (i + 1) * 17); |
146 | | - end |
147 | 213 | end |
148 | 214 |
|
149 | | - // AND-binding: current HV & permuted_history[0] & permuted_history[1] & ... |
150 | 215 | always_comb begin |
151 | | - bound_hv = token_to_hv(token_in); |
| 216 | + bound_hv = current_hv; |
152 | 217 | for (int i = 0; i < NUM_SHIFTS; i++) begin |
153 | | - if (valid_shift[i]) begin |
| 218 | + if (valid_shift[i]) |
154 | 219 | bound_hv = bound_hv & permuted_hv[i]; |
155 | | - end |
156 | 220 | end |
157 | 221 | end |
158 | 222 |
|
159 | 223 | assign ngram_hv = bound_hv; |
160 | 224 |
|
161 | 225 | // ========================================================================= |
162 | | - // Output Valid Generation |
163 | | - // ngram_valid is asserted only when: |
164 | | - // 1. en is active (current token is valid) |
165 | | - // 2. All history slots are filled (full n-gram window available) |
| 226 | + // Output Valid |
166 | 227 | // ========================================================================= |
167 | 228 | always_comb begin |
168 | | - ngram_valid = en; |
169 | | - for (int i = 0; i < NUM_SHIFTS; i++) begin |
| 229 | + ngram_valid = pipe_out_valid; |
| 230 | + for (int i = 0; i < NUM_SHIFTS; i++) |
170 | 231 | ngram_valid = ngram_valid & valid_shift[i]; |
171 | | - end |
172 | 232 | end |
173 | 233 |
|
174 | 234 | // ========================================================================= |
175 | | - // Simulation-Only Assertions |
| 235 | + // Assertions |
176 | 236 | // ========================================================================= |
177 | 237 | `ifndef SYNTHESIS |
178 | | - |
179 | | - // Assert: output HV must not be degenerate (all-zeros or all-ones) |
180 | 238 | assert property (@(posedge clk) disable iff (!rst_n) |
181 | | - (en && ngram_valid) |-> (ngram_hv != '0) && (ngram_hv != '1) |
182 | | - ) else $warning("HDC_NGRAM: Degenerate hypervector detected!"); |
| 239 | + (ngram_valid) |-> (ngram_hv != '0) && (ngram_hv != '1) |
| 240 | + ) else $warning("HDC_NGRAM_V4: Degenerate hypervector!"); |
183 | 241 |
|
184 | | - // Assert: ngram_valid must deassert after clear_context |
185 | 242 | assert property (@(posedge clk) disable iff (!rst_n) |
186 | 243 | clear_context |=> !ngram_valid |
187 | | - ) else $error("HDC_NGRAM: ngram_valid not cleared after clear_context!"); |
188 | | - |
189 | | - // Assert: valid_shift propagation consistency |
190 | | - assert property (@(posedge clk) disable iff (!rst_n) |
191 | | - (clear_context) |=> (valid_shift[0] == 1'b0) |
192 | | - ) else $error("HDC_NGRAM: valid_shift[0] not reset!"); |
193 | | - |
| 244 | + ) else $error("HDC_NGRAM_V4: valid not cleared!"); |
194 | 245 | `endif |
195 | 246 |
|
196 | 247 | endmodule |
0 commit comments