Added equihash check for index ordering

This commit is contained in:
bsdevlin 2019-02-26 16:57:18 -05:00
parent 39892b7732
commit 73c20415f8
6 changed files with 338 additions and 100 deletions

View File

@ -23,7 +23,8 @@ module axi_stream_fifo #(
parameter SIZE,
parameter DAT_BITS,
parameter MOD_BITS = $clog2(DAT_BITS/8),
parameter CTL_BITS
parameter CTL_BITS,
parameter USE_BRAM = 0 // If using BRAM there is an extra cycle delay between reads
) (
input i_clk, i_rst,
if_axi_stream.sink i_axi,
@ -33,26 +34,74 @@ module axi_stream_fifo #(
);
logic [$clog2(SIZE)-1:0] rd_ptr, wr_ptr;
localparam RAM_WIDTH = DAT_BITS + CTL_BITS + MOD_BITS + 3;
logic [RAM_WIDTH-1:0] data_out;
logic [SIZE-1:0][DAT_BITS + CTL_BITS + MOD_BITS + 3 -1:0] ram;
generate
if (USE_BRAM == 0) begin: BRAM_GEN
logic [SIZE-1:0][DAT_BITS + CTL_BITS + MOD_BITS + 3 -1:0] ram;
always_ff @ (posedge i_clk) begin
if (i_axi.val && i_axi.rdy) begin
ram [wr_ptr] <= {i_axi.err, i_axi.eop, i_axi.sop, i_axi.mod, i_axi.ctl, i_axi.dat};
end
end
always_comb begin
data_out = ram [rd_ptr];
o_axi.val = ~o_emp;
end
end else begin
if_ram #(.RAM_WIDTH(RAM_WIDTH), .RAM_DEPTH(SIZE)) bram_if_rd (i_clk, i_rst);
if_ram #(.RAM_WIDTH(RAM_WIDTH), .RAM_DEPTH(SIZE)) bram_if_wr (i_clk, i_rst);
bram #(
.RAM_WIDTH ( RAM_WIDTH ),
.RAM_DEPTH ( SIZE ),
.RAM_PERFORMANCE ( "LOW_LATENCY" )
) bram_i (
.a ( bram_if_rd ),
.b ( bram_if_wr )
);
always_ff @ (posedge i_clk) begin
o_axi.val <= 0;
if (~o_emp) o_axi.val <= 1;
if (o_axi.val && o_axi.rdy) o_axi.val <= 0;
end
always_comb begin
bram_if_rd.re = 1;
bram_if_rd.a = rd_ptr;
bram_if_rd.d = 0;
bram_if_rd.we = 0;
bram_if_rd.en = 1;
bram_if_wr.re = 0;
bram_if_wr.a = wr_ptr;
bram_if_wr.d = {i_axi.err, i_axi.eop, i_axi.sop, i_axi.mod, i_axi.ctl, i_axi.dat};
bram_if_wr.we = i_axi.val && i_axi.rdy;
bram_if_wr.en = 1;
data_out = bram_if_rd.q;
end
end
endgenerate
// Control for full and empty, and assigning outputs from the ram
always_comb begin
i_axi.rdy = ~o_full;
o_axi.dat = ram[rd_ptr][0 +: DAT_BITS];
o_axi.ctl = ram[rd_ptr][DAT_BITS +: CTL_BITS];
o_axi.mod = ram[rd_ptr][CTL_BITS+DAT_BITS +: MOD_BITS];
o_axi.sop = ram[rd_ptr][CTL_BITS+DAT_BITS+MOD_BITS +: 1];
o_axi.eop = ram[rd_ptr][CTL_BITS+DAT_BITS+MOD_BITS+1 +: 1];
o_axi.err = ram[rd_ptr][CTL_BITS+DAT_BITS+MOD_BITS+2 +: 1];
o_axi.val = ~o_emp;
end
// Logic for writing and reading from ram without reset
always_ff @ (posedge i_clk) begin
if (i_axi.val && i_axi.rdy) begin
ram [wr_ptr] <= {i_axi.err, i_axi.eop, i_axi.sop, i_axi.mod, i_axi.ctl, i_axi.dat};
end
o_axi.dat = data_out[0 +: DAT_BITS];
o_axi.ctl = data_out[DAT_BITS +: CTL_BITS];
o_axi.mod = data_out[CTL_BITS+DAT_BITS +: MOD_BITS];
o_axi.sop = data_out[CTL_BITS+DAT_BITS+MOD_BITS +: 1];
o_axi.eop = data_out[CTL_BITS+DAT_BITS+MOD_BITS+1 +: 1];
o_axi.err = data_out[CTL_BITS+DAT_BITS+MOD_BITS+2 +: 1];
end
// Control logic which requires a reset

View File

@ -38,6 +38,7 @@ module hash_map #(
output logic o_fnd, // Will be high if adding a key and it already exists (old data overwritten)
// or for a lookup if key was found.
// Configuration (overrides other signals)
// To clear memory
input i_cfg_clr,
// When linked list memory is full this will be high
@ -162,9 +163,6 @@ always_ff @ (posedge i_clk) begin
o_rdy <= 0;
hash_state <= STATE_LOOPUP_COL;
coll_ram_wait[0] <= 1;
end else if (i_cfg_clr) begin
o_rdy <= 0;
hash_state <= STATE_RESET;
end
end
STATE_LOOPUP_COL: begin
@ -401,6 +399,12 @@ always_ff @ (posedge i_clk) begin
end
endcase
// Clear signal overrides others
if (i_cfg_clr) begin
o_rdy <= 0;
hash_state <= STATE_RESET;
end
end
end
@ -432,7 +436,8 @@ axi_stream_fifo #(
.SIZE ( LL_MEM_SIZE ),
.DAT_BITS ( $clog2(LL_MEM_SIZE) ),
.MOD_BITS ( 1 ),
.CTL_BITS ( 1 )
.CTL_BITS ( 1 ),
.USE_BRAM ( 1 )
)
free_mem_fifo (
.i_clk ( i_clk ),

View File

@ -16,6 +16,14 @@ module bram #(
if_ram.sink b
);
// Check RAM sizes match the interface
initial begin
assert ($bits(a.d) == RAM_WIDTH) else $fatal(1, "%m %t ERROR: bram RAM_WIDTH (%d) does not match interface a (%d)", $time, RAM_WIDTH, $bits(a.d));
assert ($bits(a.a) == $clog2(RAM_DEPTH)) else $fatal(1, "%m %t ERROR: bram $clog2(RAM_DEPTH) (%d) does not match interface a (%d)", $time, $clog2(RAM_DEPTH), $bits(a.a));
assert ($bits(b.d) == RAM_WIDTH) else $fatal(1, "%m %t ERROR: bram RAM_WIDTH (%d) does not match interface b (%d)", $time, RAM_WIDTH, $bits(b.d));
assert ($bits(b.a) == $clog2(RAM_DEPTH)) else $fatal(1, "%m %t ERROR: bram $clog2(RAM_DEPTH) (%d) does not match interface b (%d)", $time, $clog2(RAM_DEPTH), $bits(b.a));
end
xilinx_true_dual_port_no_change_2_clock_ram #(
.RAM_WIDTH(RAM_WIDTH), // Specify RAM data width
.RAM_DEPTH(RAM_DEPTH), // Specify RAM depth (number of entries)

View File

@ -0,0 +1,148 @@
/*
This verifies that a input list stream has no duplicates.
Implemented using a hash table and FIFOs for flow control.
Input FIFO could be implemented with clock crossing to run at higher frequency.
Copyright (C) 2019 Benjamin Devlin and Zcash Foundation
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
module dup_check # (
parameter IN_BITS,
parameter LIST_SIZE
) (
input i_clk, i_rst,
if_axi_stream.sink i_axi, // One index per clock cycle - start on .sop and finishes on .eop
if_axi_stream.source o_axi // Will give a single clock cycle output with dat = 0 (no duplicates) or 1 (duplicates)
);
logic hash_map_out_rdy, hash_map_out_val, hash_map_out_fnd, hash_map_clr, hash_map_full;
logic sol_index_fifo_if_emp;
if_axi_stream #(.DAT_BITS(IN_BITS), .CTL_BITS(1), .MOD_BITS(1)) sol_index_fifo_if_in(i_clk);
if_axi_stream #(.DAT_BITS(IN_BITS), .CTL_BITS(1), .MOD_BITS(1)) sol_index_fifo_if_out(i_clk);
logic eop_l, fnd_l;
enum {IDLE = 0,
SEARCH = 1,
CLEAR = 2} dup_check_state;
always_comb begin
i_axi.rdy = (dup_check_state != CLEAR) && sol_index_fifo_if_in.rdy && ~eop_l;
sol_index_fifo_if_out.rdy = hash_map_out_rdy;
sol_index_fifo_if_in.dat = i_axi.dat;
sol_index_fifo_if_in.eop = 0;
sol_index_fifo_if_in.sop = 0;
sol_index_fifo_if_in.err = 0;
sol_index_fifo_if_in.ctl = 0;
sol_index_fifo_if_in.mod = 0;
sol_index_fifo_if_in.val = i_axi.val;
end
always_ff @ (posedge i_clk) begin
if (i_rst) begin
dup_check_state <= IDLE;
hash_map_clr <= 0;
eop_l <= 0;
fnd_l <= 0;
o_axi.reset_source();
end else begin
eop_l <= eop_l || (i_axi.val && i_axi.rdy && i_axi.eop);
fnd_l <= fnd_l || (hash_map_out_val && hash_map_out_fnd);
o_axi.val <= 0;
o_axi.err <= 0;
o_axi.sop <= 1;
o_axi.eop <= 1;
hash_map_clr <= 0;
case (dup_check_state)
IDLE: begin
if (~sol_index_fifo_if_emp && hash_map_out_rdy) begin
dup_check_state <= SEARCH;
end
end
SEARCH: begin
if (sol_index_fifo_if_emp && eop_l && o_axi.rdy) begin
dup_check_state <= CLEAR;
o_axi.val <= 1;
o_axi.dat <= fnd_l;
hash_map_clr <= 1;
end
end
CLEAR: begin
eop_l <= 0;
fnd_l <= 0;
if (hash_map_out_rdy)
dup_check_state <= IDLE;
end
endcase
if (hash_map_full) begin
o_axi.err <= 1;
o_axi.val <= 1;
hash_map_clr <= 1;
dup_check_state <= CLEAR;
end
end
end
axi_stream_fifo #(
.SIZE ( LIST_SIZE ),
.DAT_BITS ( IN_BITS ),
.MOD_BITS ( 1 ),
.CTL_BITS ( 1 ),
.USE_BRAM ( 1 )
)
index_fifo (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_axi ( sol_index_fifo_if_in ),
.o_axi ( sol_index_fifo_if_out ),
.o_full ( sol_index_fifo_if_full ),
.o_emp ( sol_index_fifo_if_emp )
);
// Hash table used to detect duplicate index, fed from FIFO
// Could potentially be run at much higher clock
hash_map #(
.KEY_BITS ( IN_BITS ),
.DAT_BITS ( 1 ),
.HASH_MEM_SIZE ( 2*LIST_SIZE ),
.LL_MEM_SIZE ( LIST_SIZE )
)
hash_map_i (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_key ( sol_index_fifo_if_out.dat ),
.i_val ( sol_index_fifo_if_out.val ),
.i_dat ( 1'd1 ),
.i_opcode ( 2'd1 ),
.o_rdy ( hash_map_out_rdy ),
.o_dat (),
.o_val ( hash_map_out_val ),
.o_fnd ( hash_map_out_fnd ),
.i_cfg_clr ( hash_map_clr ),
.o_cfg_full ( hash_map_full )
);
endmodule

View File

@ -28,7 +28,8 @@
module zcash_verif_equihash
import zcash_verif_pkg::*;
#(
parameter DAT_BYTS = 8
parameter DAT_BYTS = 8,
parameter CHECK_UNIQUE_INDEX = 1
)(
input i_clk, i_rst,
@ -61,13 +62,18 @@ if_ram #(.RAM_WIDTH(EQUIHASH_SOL_BRAM_WIDTH), .RAM_DEPTH(EQUIHASH_SOL_BRAM_DEPTH
logic [DAT_BITS-1:0] equihash_sol_bram_if_b_l;
logic [2*DAT_BITS-1:0] equihash_sol_bram_if_b_l_comb, equihash_sol_bram_if_b_l_comb_flip;
logic [SOL_BITS-1:0] equihash_sol_index, hash_map_in_dat;
logic [SOL_BITS-1:0] equihash_sol_index;
logic [1:0] equihash_sol_bram_read;
logic hash_map_in_val, hash_map_out_rdy, hash_map_out_val, hash_map_out_fnd, hash_map_clr, hash_map_full;
logic sol_index_fifo_if_emp;
if_axi_stream #(.DAT_BITS(SOL_BITS), .CTL_BITS(1), .MOD_BITS(1)) sol_index_fifo_if_in(i_clk);
if_axi_stream #(.DAT_BITS(SOL_BITS), .CTL_BITS(1), .MOD_BITS(1)) sol_index_fifo_if_out(i_clk);
logic dup_chk_done, order_chk_done;
if_axi_stream #(.DAT_BITS(SOL_BITS), .CTL_BITS(1), .MOD_BITS(1)) dup_check_if_in(i_clk);
if_axi_stream #(.DAT_BITS(1), .CTL_BITS(1), .MOD_BITS(1)) dup_check_if_out(i_clk);
if_axi_stream #(.DAT_BITS(SOL_BITS), .MOD_BITS(1), .CTL_BITS(1)) equihash_order_if(i_clk);
logic equihash_order_val, equihash_order_wrong;
enum {STATE_WR_IDLE = 0,
STATE_WR_DATA = 1,
@ -106,7 +112,7 @@ always_ff @ (posedge i_clk) begin
case (ram_wr_state)
// This state we are waiting for an input block
STATE_WR_IDLE: begin
i_axi.rdy <= hash_map_out_rdy;
i_axi.rdy <= (dup_check_if_in.rdy && equihash_order_if.rdy);
if (i_axi.val && i_axi.rdy) begin
ram_wr_state <= STATE_WR_DATA;
equihash_sol_bram_if_a.a <= 0;
@ -149,6 +155,9 @@ always_ff @ (posedge i_clk) begin
equihash_sol_bram_if_b_l <= 0;
equihash_gen_in <= 0;
equihash_sol_bram_read <= 0;
dup_check_if_in.reset_source();
equihash_order_if.reset_source();
ram_rd_state <= STATE_RD_IDLE;
end else begin
// Defaults
@ -157,6 +166,10 @@ always_ff @ (posedge i_clk) begin
blake2b_in_hash.sop <= 1;
blake2b_in_hash.eop <= 1;
blake2b_in_hash.val <= 0;
dup_check_if_in.val <= 0;
equihash_order_if.val <= 0;
equihash_sol_bram_read <= equihash_sol_bram_read << 1;
if (equihash_sol_bram_read[0])
equihash_sol_bram_if_b_l <= equihash_sol_bram_if_b.q;
@ -192,6 +205,7 @@ always_ff @ (posedge i_clk) begin
equihash_gen_in.index <= (equihash_sol_index)/INDICIES_PER_HASH;
blake2b_in_hash.ctl <= (equihash_sol_index) % INDICIES_PER_HASH;
// Stay 2 clocks behind the RAM write
if ((equihash_sol_bram_if_a.a*DAT_BYTS + DAT_BYTS) >= (equihash_sol_bram_if_b.a + $bits(cblockheader_t)/DAT_BITS) ||
ram_wr_state == STATE_WR_WAIT) begin
@ -205,6 +219,16 @@ always_ff @ (posedge i_clk) begin
blake2b_in_hash.val <= 1;
sol_cnt_in <= sol_cnt_in + 1;
dup_check_if_in.val <= 1;
dup_check_if_in.dat <= equihash_sol_index;
dup_check_if_in.sop <= (sol_cnt_in == 0);
dup_check_if_in.eop <= (sol_cnt_in == SOL_LIST_LEN - 1);
equihash_order_if.val <= 1;
equihash_order_if.dat <= equihash_sol_index;
equihash_order_if.sop <= (sol_cnt_in == 0);
equihash_order_if.eop <= (sol_cnt_in == SOL_LIST_LEN - 1);
// If our input is about to shift we need to adjust pointer by DAT_BITS
sol_pos <= sol_pos + SOL_BITS - (equihash_sol_bram_read[0] ? DAT_BITS : 0);
if (sol_cnt_in == SOL_LIST_LEN - 1)
@ -228,21 +252,34 @@ always_ff @ (posedge i_clk) begin
blake2b_out_hash.rdy <= 0;
sol_cnt_out <= 0;
chk_state <= STATE_CHK_IDLE;
hash_map_clr <= 0;
dup_check_if_out.rdy <= 1;
dup_chk_done <= 0;
order_chk_done <= 0;
end else begin
// Defaults
blake2b_out_hash.rdy <= 1;
hash_map_clr <= 0;
sol_index_fifo_if_in.val <= blake2b_in_hash.val;
sol_index_fifo_if_in.dat <= equihash_sol_index;
// Monitor for result of duplicate check
if (dup_check_if_out.val) begin
if (dup_check_if_out.dat[0] || dup_check_if_out.err) begin
o_mask.DUPLICATE_FND <= 1;
end
dup_chk_done <= 1;
end
if ( (hash_map_out_val && hash_map_out_fnd) || hash_map_full )
o_mask.DUPLICATE_FND <= 1;
// Monitor for result of order check
if (equihash_order_val) begin
if (equihash_order_wrong) begin
o_mask.BAD_IDX_ORDER <= 1;
end
order_chk_done <= 1;
end
case(chk_state)
STATE_CHK_IDLE: begin
sol_cnt_out <= 0;
dup_chk_done <= 0;
order_chk_done <= 0;
o_mask_val <= 0;
o_mask <= 0;
sol_hash_xor <= 0;
@ -275,11 +312,11 @@ always_ff @ (posedge i_clk) begin
if (ram_rd_state == STATE_RD_WAIT &&
ram_wr_state == STATE_WR_WAIT &&
sol_index_fifo_if_emp ) begin
dup_chk_done &&
order_chk_done ) begin
o_mask_val <= 1;
chk_state <= STATE_CHK_DONE;
hash_map_clr <= 1;
end
end
STATE_CHK_DONE: begin
@ -310,10 +347,6 @@ always_comb begin
for (int i = 0; i < SOL_BITS; i++)
equihash_sol_index[i] = equihash_sol_bram_if_b_l_comb_flip[sol_pos + SOL_BITS-1-i];
// Hash map is fed directly from FIFO
hash_map_in_val = sol_index_fifo_if_out.val;
hash_map_in_dat = sol_index_fifo_if_out.dat;
sol_index_fifo_if_out.rdy = hash_map_out_rdy;
end
@ -352,7 +385,7 @@ blake2b_pipe_top_i (
.o_hash ( blake2b_out_hash )
);
// Memory to store the equihash solution as it comes in. We use dual port,
// Memory to store the compressed equihash solution as it comes in. We use dual port,
// one port for writing and one port for reading
bram #(
.RAM_WIDTH ( EQUIHASH_SOL_BRAM_WIDTH ),
@ -363,44 +396,39 @@ bram #(
.b ( equihash_sol_bram_if_b )
);
axi_stream_fifo #(
.SIZE ( SOL_LIST_LEN ),
.DAT_BITS ( SOL_BITS ),
.MOD_BITS ( 1 ),
.CTL_BITS ( 1 )
)
sol_index_fifo (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_axi ( sol_index_fifo_if_in ),
.o_axi ( sol_index_fifo_if_out ),
.o_full (),
.o_emp ( sol_index_fifo_if_emp )
);
// Hash table used to detect duplicate index, fed from FIFO
// Could potentially be run at much higher clock
hash_map #(
.KEY_BITS ( SOL_BITS ),
.DAT_BITS ( 1 ),
.HASH_MEM_SIZE ( 2*SOL_LIST_LEN ),
.LL_MEM_SIZE ( SOL_LIST_LEN )
)
hash_map_i (
zcash_verif_equihash_order
equihash_order (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_key ( hash_map_in_dat ),
.i_val ( hash_map_in_val ),
.i_dat ( 1'd1 ),
.i_opcode ( 2'd1 ),
.o_rdy ( hash_map_out_rdy ),
.o_dat (),
.o_val ( hash_map_out_val ),
.o_fnd ( hash_map_out_fnd ),
.i_cfg_clr ( hash_map_clr ),
.o_cfg_full ( hash_map_full )
.i_axi ( equihash_order_if ),
.o_order_wrong ( equihash_order_wrong ),
.o_val ( equihash_order_val )
);
generate
if (CHECK_UNIQUE_INDEX == 1) begin: GEN_INDEX_CHECK
dup_check #(
.IN_BITS ( SOL_BITS ),
.LIST_SIZE ( SOL_LIST_LEN )
)
dup_check(
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_axi ( dup_check_if_in ),
.o_axi ( dup_check_if_out )
);
end else begin
always_comb begin
dup_check_if_in.rdy = 1;
dup_check_if_out.val = 1;
dup_check_if_out.eop = 1;
dup_check_if_out.sop = 1;
dup_check_if_out.dat = 0;
end
end
endgenerate
// Some checks to make sure our data structures are correct:
initial begin
assert ($bits(equihash_gen_in_t)/8 == 144) else $fatal(1, "%m %t ERROR: equihash_gen_in_t is not 144 bytes in size", $time);

View File

@ -99,7 +99,7 @@ endtask
// Main testbench calls
initial begin
#200ns;
#20us; // Let internal memories reset
test_block_346();