aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/control/map/kv_map.v
blob: dfb0bca43c651880ec65a551c278c7819644b612 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
//
// Copyright 2018 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: kv_map

module kv_map #(
  parameter KEY_WIDTH = 16,
  parameter VAL_WIDTH = 32,
  parameter SIZE      = 6
) (
  // Clock and reset
  input  wire                 clk,
  input  wire                 reset,
  // Insert port
  input  wire                 insert_stb,
  input  wire [KEY_WIDTH-1:0] insert_key,
  input  wire [VAL_WIDTH-1:0] insert_val,
  output wire                 insert_busy,
  // Find port
  input  wire                 find_key_stb,
  input  wire [KEY_WIDTH-1:0] find_key,
  output wire                 find_res_stb,
  output wire                 find_res_match,
  output wire [VAL_WIDTH-1:0] find_res_val,
  // Count
  output reg  [SIZE-1:0]      count = {SIZE{1'b0}}
);

  //-------------------------------------------------
  // Instantiate a CAM and a RAM
  //-------------------------------------------------
  // The CAM serves as a "set" and the RAM serves as a
  // random addressable "array". Using thse two data structures
  // we can build a map. The role of the CAM is to compress
  // the key to an address that can be used to lookup data
  // stored in the RAM

  wire                 cam_wr_en, cam_wr_busy, cam_rd_match;
  wire [SIZE-1:0]      cam_wr_addr, cam_rd_addr;
  wire [KEY_WIDTH-1:0] cam_wr_data, cam_rd_key;

  wire                 ram_wr_en;
  wire [SIZE-1:0]      ram_wr_addr;
  reg  [SIZE-1:0]      ram_rd_addr;
  wire [VAL_WIDTH-1:0] ram_wr_data, ram_rd_data;

  cam #(
    .DATA_WIDTH  (KEY_WIDTH),
    .ADDR_WIDTH  (SIZE),
    .CAM_STYLE   (SIZE > 8 ? "BRAM" : "SRL"),
    .SLICE_WIDTH (SIZE > 8 ? 9 : 5)
  ) cam_i (
    .clk         (clk),
    .rst         (reset),
    .write_addr  (cam_wr_addr),
    .write_data  (cam_wr_data),
    .write_delete(1'b0),
    .write_enable(cam_wr_en),
    .write_busy  (cam_wr_busy),
    .compare_data(cam_rd_key),
    .match_addr  (cam_rd_addr),
    .match       (cam_rd_match),
    .match_many  (),
    .match_single()
  );

  ram_2port #(
    .DWIDTH(VAL_WIDTH),
    .AWIDTH(SIZE)
  ) mem_i (
    .clka  (clk),
    .ena   (ram_wr_en),
    .wea   (1'b1),
    .addra (ram_wr_addr),
    .dia   (ram_wr_data),
    .doa   (/* Write port only */),
    .clkb  (clk),
    .enb   (1'b1),
    .web   (1'b0),
    .addrb (ram_rd_addr),
    .dib   (/* Read port only */),
    .dob   (ram_rd_data)
  );

  // Pipeline read address into RAM
  always @(posedge clk)
    ram_rd_addr <= cam_rd_addr;

  //-------------------------------------------------
  // Find state machine
  //-------------------------------------------------
  // The lookup process has three cycles of latency
  // - CAM lookup has a 1 cycle latency
  // - The lookup address into the RAM is delayed by 1 cycle for timing
  // - The RAM takes 1 cycle to produce data

  localparam FIND_CYC = 3;

  reg [FIND_CYC-1:0] find_key_stb_shreg = {FIND_CYC{1'b0}};
  reg [FIND_CYC-2:0] find_match_shreg   = {(FIND_CYC-1){1'b0}};
  reg                find_pending       = 1'b0;

  wire find_busy = find_pending | find_key_stb;

  // Delay the find valid signal to account for the latency 
  // of the CAM and RAM
  always @(posedge clk) begin
    find_key_stb_shreg <= reset ? {FIND_CYC{1'b0}} :
      {find_key_stb_shreg[FIND_CYC-2:0], find_key_stb};
  end
  assign find_res_stb = find_key_stb_shreg[FIND_CYC-1];

  // Latch the find signal to compute pending
  always @(posedge clk) begin
    if (find_key_stb)
      find_pending <= 1'b1;
    else if (find_pending)
      find_pending <= ~find_res_stb;
  end

  // Delay the match signal to account for the latency of the RAM
  always @(posedge clk) begin
    find_match_shreg <= reset ? {(FIND_CYC-1){1'b0}} :
      {find_match_shreg[FIND_CYC-3:0], cam_rd_match};
  end
  assign find_res_match = find_match_shreg[FIND_CYC-2];


  //-------------------------------------------------
  // Insert state machine
  //-------------------------------------------------

  localparam [2:0] ST_IDLE            = 3'd0;
  localparam [2:0] ST_WAIT_FIND       = 3'd1;
  localparam [2:0] ST_CAM_READ        = 3'd2;
  localparam [2:0] ST_CAM_CHECK_MATCH = 3'd3;
  localparam [2:0] ST_CAM_RAM_WRITE   = 3'd4;
  localparam [2:0] ST_CAM_WRITE_WAIT  = 3'd5;
  localparam [2:0] ST_RAM_WRITE       = 3'd6;

  reg [2:0] ins_state = ST_IDLE;

  reg [KEY_WIDTH-1:0] ins_key_cached;
  reg [VAL_WIDTH-1:0] ins_val_cached;
  reg [SIZE-1:0]      write_addr = {SIZE{1'b0}};
  reg [SIZE-1:0]      next_addr  = {SIZE{1'b0}};


  always @(posedge clk) begin
    if (reset) begin
      ins_state <= ST_IDLE;
      next_addr <= {SIZE{1'b0}};
    end else begin
      case (ins_state)

        // Idle and waiting for an insert transaction 
        //
        ST_IDLE: begin
          // Cache insertion parameters
          if (insert_stb) begin
            ins_key_cached <= insert_key;
            ins_val_cached <= insert_val;
            // Wait for find to finish
            ins_state <= find_busy ? ST_WAIT_FIND : ST_CAM_READ;
          end
        end

        // Wait for a find transaction to finish
        //
        ST_WAIT_FIND: begin
          // Wait for find to finish
          if (~find_busy)
            ins_state <= ST_CAM_READ;
        end

        // Read the CAM to check if the key to insert already exists
        //
        ST_CAM_READ: begin
          // Ensure that find always has priority
          if (~find_key_stb)
            ins_state <= ST_CAM_CHECK_MATCH;
        end

        // Look at the CAM match signal to evaluate if we skip writing the CAM
        //
        ST_CAM_CHECK_MATCH: begin
          // If the CAM already has this key, then overwrite it
          if (cam_rd_match) begin
            ins_state <= ST_RAM_WRITE;
            write_addr <= cam_rd_addr;
          end else if (~cam_wr_busy) begin
            ins_state <= ST_CAM_RAM_WRITE;
            write_addr <= next_addr;
            next_addr <= next_addr + 1'b1;
          end
        end

        // Write the specified key to the CAM and value to the RAM
        //
        ST_CAM_RAM_WRITE: begin
          ins_state <= ST_CAM_WRITE_WAIT;
        end

        // Wait for CAM write to finish
        //
        ST_CAM_WRITE_WAIT: begin
          if (~cam_wr_busy) begin
            ins_state <= ST_IDLE;
            count <= next_addr;
          end
        end

        // Write the specified value to the RAM
        //
        ST_RAM_WRITE: begin
          ins_state <= ST_IDLE;
          count <= next_addr;
        end

        default: begin
          // We should not get here
          ins_state <= ST_IDLE;
        end
      endcase
    end
  end

  // CAM Read Port:
  // - Find has priority so it can interrupt an insert
  assign cam_rd_key = 
    (ins_state != ST_CAM_READ || find_key_stb) ? find_key : ins_key_cached;

  // RAM Write Port:
  // - The RAM write enable is held high for 1 cycle
  // - The address may come from a CAM lookup or could generated
  assign ram_wr_en    = (ins_state == ST_RAM_WRITE || ins_state == ST_CAM_RAM_WRITE);
  assign ram_wr_addr  = write_addr;
  assign ram_wr_data  = ins_val_cached;

  // CAM Write Port:
  // - The CAM write enable is held high for 1 cycle
  // - The address may come from a CAM lookup or could generated (same as RAM)
  assign cam_wr_en    = (ins_state == ST_CAM_RAM_WRITE);
  assign cam_wr_addr  = write_addr;
  assign cam_wr_data  = ins_key_cached;

  // Outputs
  assign insert_busy  = (ins_state != ST_IDLE);
  assign find_res_val = ram_rd_data;

endmodule