1//
2// Copyright 2018 Ettus Research, A National Instruments Company
3//
4// SPDX-License-Identifier: LGPL-3.0-or-later
5//
6// Module: kv_map
7
8module kv_map #(
9  parameter KEY_WIDTH = 16,
10  parameter VAL_WIDTH = 32,
11  parameter SIZE      = 6
12) (
13  // Clock and reset
14  input  wire                 clk,
15  input  wire                 reset,
16  // Insert port
17  input  wire                 insert_stb,
18  input  wire [KEY_WIDTH-1:0] insert_key,
19  input  wire [VAL_WIDTH-1:0] insert_val,
20  output wire                 insert_busy,
21  // Find port
22  input  wire                 find_key_stb,
23  input  wire [KEY_WIDTH-1:0] find_key,
24  output wire                 find_res_stb,
25  output wire                 find_res_match,
26  output wire [VAL_WIDTH-1:0] find_res_val,
27  // Count
28  output reg  [SIZE-1:0]      count = {SIZE{1'b0}}
29);
30
31  //-------------------------------------------------
32  // Instantiate a CAM and a RAM
33  //-------------------------------------------------
34  // The CAM serves as a "set" and the RAM serves as a
35  // random addressable "array". Using thse two data structures
36  // we can build a map. The role of the CAM is to compress
37  // the key to an address that can be used to lookup data
38  // stored in the RAM
39
40  wire                 cam_wr_en, cam_wr_busy, cam_rd_match;
41  wire [SIZE-1:0]      cam_wr_addr, cam_rd_addr;
42  wire [KEY_WIDTH-1:0] cam_wr_data, cam_rd_key;
43
44  wire                 ram_wr_en;
45  wire [SIZE-1:0]      ram_wr_addr;
46  reg  [SIZE-1:0]      ram_rd_addr;
47  wire [VAL_WIDTH-1:0] ram_wr_data, ram_rd_data;
48
49  cam #(
50    .DATA_WIDTH  (KEY_WIDTH),
51    .ADDR_WIDTH  (SIZE),
52    .CAM_STYLE   (SIZE > 8 ? "BRAM" : "SRL"),
53    .SLICE_WIDTH (SIZE > 8 ? 9 : 5)
54  ) cam_i (
55    .clk         (clk),
56    .rst         (reset),
57    .write_addr  (cam_wr_addr),
58    .write_data  (cam_wr_data),
59    .write_delete(1'b0),
60    .write_enable(cam_wr_en),
61    .write_busy  (cam_wr_busy),
62    .compare_data(cam_rd_key),
63    .match_addr  (cam_rd_addr),
64    .match       (cam_rd_match),
65    .match_many  (),
66    .match_single()
67  );
68
69  ram_2port #(
70    .DWIDTH(VAL_WIDTH),
71    .AWIDTH(SIZE)
72  ) mem_i (
73    .clka  (clk),
74    .ena   (ram_wr_en),
75    .wea   (1'b1),
76    .addra (ram_wr_addr),
77    .dia   (ram_wr_data),
78    .doa   (/* Write port only */),
79    .clkb  (clk),
80    .enb   (1'b1),
81    .web   (1'b0),
82    .addrb (ram_rd_addr),
83    .dib   (/* Read port only */),
84    .dob   (ram_rd_data)
85  );
86
87  // Pipeline read address into RAM
88  always @(posedge clk)
89    ram_rd_addr <= cam_rd_addr;
90
91  //-------------------------------------------------
92  // Find state machine
93  //-------------------------------------------------
94  // The lookup process has three cycles of latency
95  // - CAM lookup has a 1 cycle latency
96  // - The lookup address into the RAM is delayed by 1 cycle for timing
97  // - The RAM takes 1 cycle to produce data
98
99  localparam FIND_CYC = 3;
100
101  reg [FIND_CYC-1:0] find_key_stb_shreg = {FIND_CYC{1'b0}};
102  reg [FIND_CYC-2:0] find_match_shreg   = {(FIND_CYC-1){1'b0}};
103  reg                find_pending       = 1'b0;
104
105  wire find_busy = find_pending | find_key_stb;
106
107  // Delay the find valid signal to account for the latency
108  // of the CAM and RAM
109  always @(posedge clk) begin
110    find_key_stb_shreg <= reset ? {FIND_CYC{1'b0}} :
111      {find_key_stb_shreg[FIND_CYC-2:0], find_key_stb};
112  end
113  assign find_res_stb = find_key_stb_shreg[FIND_CYC-1];
114
115  // Latch the find signal to compute pending
116  always @(posedge clk) begin
117    if (find_key_stb)
118      find_pending <= 1'b1;
119    else if (find_pending)
120      find_pending <= ~find_res_stb;
121  end
122
123  // Delay the match signal to account for the latency of the RAM
124  always @(posedge clk) begin
125    find_match_shreg <= reset ? {(FIND_CYC-1){1'b0}} :
126      {find_match_shreg[FIND_CYC-3:0], cam_rd_match};
127  end
128  assign find_res_match = find_match_shreg[FIND_CYC-2];
129
130
131  //-------------------------------------------------
132  // Insert state machine
133  //-------------------------------------------------
134
135  localparam [2:0] ST_IDLE            = 3'd0;
136  localparam [2:0] ST_WAIT_FIND       = 3'd1;
137  localparam [2:0] ST_CAM_READ        = 3'd2;
138  localparam [2:0] ST_CAM_CHECK_MATCH = 3'd3;
139  localparam [2:0] ST_CAM_RAM_WRITE   = 3'd4;
140  localparam [2:0] ST_CAM_WRITE_WAIT  = 3'd5;
141  localparam [2:0] ST_RAM_WRITE       = 3'd6;
142
143  reg [2:0] ins_state = ST_IDLE;
144
145  reg [KEY_WIDTH-1:0] ins_key_cached;
146  reg [VAL_WIDTH-1:0] ins_val_cached;
147  reg [SIZE-1:0]      write_addr = {SIZE{1'b0}};
148  reg [SIZE-1:0]      next_addr  = {SIZE{1'b0}};
149
150
151  always @(posedge clk) begin
152    if (reset) begin
153      ins_state <= ST_IDLE;
154      next_addr <= {SIZE{1'b0}};
155    end else begin
156      case (ins_state)
157
158        // Idle and waiting for an insert transaction
159        //
160        ST_IDLE: begin
161          // Cache insertion parameters
162          if (insert_stb) begin
163            ins_key_cached <= insert_key;
164            ins_val_cached <= insert_val;
165            // Wait for find to finish
166            ins_state <= find_busy ? ST_WAIT_FIND : ST_CAM_READ;
167          end
168        end
169
170        // Wait for a find transaction to finish
171        //
172        ST_WAIT_FIND: begin
173          // Wait for find to finish
174          if (~find_busy)
175            ins_state <= ST_CAM_READ;
176        end
177
178        // Read the CAM to check if the key to insert already exists
179        //
180        ST_CAM_READ: begin
181          // Ensure that find always has priority
182          if (~find_key_stb)
183            ins_state <= ST_CAM_CHECK_MATCH;
184        end
185
186        // Look at the CAM match signal to evaluate if we skip writing the CAM
187        //
188        ST_CAM_CHECK_MATCH: begin
189          // If the CAM already has this key, then overwrite it
190          if (cam_rd_match) begin
191            ins_state <= ST_RAM_WRITE;
192            write_addr <= cam_rd_addr;
193          end else if (~cam_wr_busy) begin
194            ins_state <= ST_CAM_RAM_WRITE;
195            write_addr <= next_addr;
196            next_addr <= next_addr + 1'b1;
197          end
198        end
199
200        // Write the specified key to the CAM and value to the RAM
201        //
202        ST_CAM_RAM_WRITE: begin
203          ins_state <= ST_CAM_WRITE_WAIT;
204        end
205
206        // Wait for CAM write to finish
207        //
208        ST_CAM_WRITE_WAIT: begin
209          if (~cam_wr_busy) begin
210            ins_state <= ST_IDLE;
211            count <= next_addr;
212          end
213        end
214
215        // Write the specified value to the RAM
216        //
217        ST_RAM_WRITE: begin
218          ins_state <= ST_IDLE;
219          count <= next_addr;
220        end
221
222        default: begin
223          // We should not get here
224          ins_state <= ST_IDLE;
225        end
226      endcase
227    end
228  end
229
230  // CAM Read Port:
231  // - Find has priority so it can interrupt an insert
232  assign cam_rd_key =
233    (ins_state != ST_CAM_READ || find_key_stb) ? find_key : ins_key_cached;
234
235  // RAM Write Port:
236  // - The RAM write enable is held high for 1 cycle
237  // - The address may come from a CAM lookup or could generated
238  assign ram_wr_en    = (ins_state == ST_RAM_WRITE || ins_state == ST_CAM_RAM_WRITE);
239  assign ram_wr_addr  = write_addr;
240  assign ram_wr_data  = ins_val_cached;
241
242  // CAM Write Port:
243  // - The CAM write enable is held high for 1 cycle
244  // - The address may come from a CAM lookup or could generated (same as RAM)
245  assign cam_wr_en    = (ins_state == ST_CAM_RAM_WRITE);
246  assign cam_wr_addr  = write_addr;
247  assign cam_wr_data  = ins_key_cached;
248
249  // Outputs
250  assign insert_busy  = (ins_state != ST_IDLE);
251  assign find_res_val = ram_rd_data;
252
253endmodule
254