From 547a557a143b2c1f7cdf8b86f788346c9b9d500e Mon Sep 17 00:00:00 2001 From: bsdevlin Date: Wed, 27 Feb 2019 19:45:00 -0500 Subject: [PATCH] SHA256 core First cut of SHA256 core --- ip_cores/sha256/src/rtl/sha256_pkg.sv | 40 +++++ ip_cores/sha256/src/rtl/sha256_top.sv | 224 ++++++++++++++++++++++++ ip_cores/sha256/src/tb/sha256_top_tb.sv | 76 ++++++++ 3 files changed, 340 insertions(+) create mode 100644 ip_cores/sha256/src/rtl/sha256_pkg.sv create mode 100644 ip_cores/sha256/src/rtl/sha256_top.sv create mode 100644 ip_cores/sha256/src/tb/sha256_top_tb.sv diff --git a/ip_cores/sha256/src/rtl/sha256_pkg.sv b/ip_cores/sha256/src/rtl/sha256_pkg.sv new file mode 100644 index 0000000..b3dda68 --- /dev/null +++ b/ip_cores/sha256/src/rtl/sha256_pkg.sv @@ -0,0 +1,40 @@ +/* + The SHA256 package file. + + 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 . +*/ + +package sha256_pkg; + + // K values used during each round + parameter [63:0][31:0] K = { + 32'hc67178f2,32'hbef9a3f7,32'ha4506ceb,32'h90befffa,32'h8cc70208,32'h84c87814,32'h78a5636f,32'h748f82ee, + 32'h682e6ff3,32'h5b9cca4f,32'h4ed8aa4a,32'h391c0cb3,32'h34b0bcb5,32'h2748774c,32'h1e376c08,32'h19a4c116, + 32'h106aa070,32'hf40e3585,32'hd6990624,32'hd192e819,32'hc76c51a3,32'hc24b8b70,32'ha81a664b,32'ha2bfe8a1, + 32'h92722c85,32'h81c2c92e,32'h766a0abb,32'h650a7354,32'h53380d13,32'h4d2c6dfc,32'h2e1b2138,32'h27b70a85, + 32'h14292967,32'h06ca6351,32'hd5a79147,32'hc6e00bf3,32'hbf597fc7,32'hb00327c8,32'ha831c66d,32'h983e5152, + 32'h76f988da,32'h5cb0a9dc,32'h4a7484aa,32'h2de92c6f,32'h240ca1cc,32'h0fc19dc6,32'hefbe4786,32'he49b69c1, + 32'hc19bf174,32'h9bdc06a7,32'h80deb1fe,32'h72be5d74,32'h550c7dc3,32'h243185be,32'h12835b01,32'hd807aa98, + 32'hab1c5ed5,32'h923f82a4,32'h59f111f1,32'h3956c25b,32'he9b5dba5,32'hb5c0fbcf,32'h71374491,32'h428a2f98 + }; + + // Initial values used for H + parameter [7:0][31:0] IV = { + 32'h5be0cd19, 32'h1f83d9ab, 32'h9b05688c, 32'h510e527f, + 32'ha54ff53a, 32'h3c6ef372, 32'hbb67ae85, 32'h6a09e667 + }; + +endpackage \ No newline at end of file diff --git a/ip_cores/sha256/src/rtl/sha256_top.sv b/ip_cores/sha256/src/rtl/sha256_top.sv new file mode 100644 index 0000000..808a953 --- /dev/null +++ b/ip_cores/sha256/src/rtl/sha256_top.sv @@ -0,0 +1,224 @@ +/* + This is an implementation of the SHA256 hash algorithm. + + Takes an AXI stream 512 bit interface. We back pressure the input + if the message size is > 512 bits while we process the 64 rounds. + + 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 . + */ + +module sha256_top + import sha256_pkg::*; +( + input i_clk, i_rst, + + if_axi_stream.sink i_block, // Message stream to be hashed, must be 512 bits + if_axi_stream.source o_hash // Resulting hash digest (32 bytes) +); + +localparam DAT_BYTS = 64; +localparam DAT_BITS = DAT_BYTS*8; // Must be 512 + +logic [7:0][31:0] V; // Internal state, V[0] == A, V[7] == H +logic [31:0] T1, T2; +logic [15:0][31:0] W; +logic [15:0] W_nxt; +logic eop_l, padding_only, final_block; +logic [63:0] bit_len; +logic [$clog2(64)-1:0] rnd_cntr; + +enum {SHA_IDLE = 0, + SHA_ROUNDS = 1, + SHA_UPDATE_HV = 2, + SHA_FINAL = 3} sha_state; + +// Used to make compression function easier to read +localparam A = 0; +localparam B = 1; +localparam C = 2; +localparam D = 3; +localparam E = 4; +localparam F = 5; +localparam G = 6; +localparam H = 7; + + +always_ff @ (posedge i_clk) begin + if (i_rst) begin + sha_state <= SHA_IDLE; + rnd_cntr <= 0; + o_hash.reset_source(); + i_block.rdy <= 0; + bit_len <= 0; + W <= 0; + eop_l <= 0; + padding_only <= 0; + final_block <= 0; + end else begin + o_hash.sop <= 1; + o_hash.eop <= 1; + case(sha_state) + SHA_IDLE: begin + update_HV(1); + rnd_cntr <= 0; + final_block <= 0; + eop_l <= 0; + padding_only <= 0; + i_block.rdy <= 1; + // As soon as we have one write on the input we can start + if (i_block.rdy && i_block.val && i_block.sop) begin + W <= i_block.dat; + bit_len <= DAT_BITS; + i_block.rdy <= 0; + if (i_block.eop) + msg_eop(); + sha_state <= SHA_ROUNDS; + end + end + SHA_ROUNDS: begin + for (int i = 0; i < 16; i++) W[i] <= W[i+i]; + W[15] <= W_nxt; + compress(); + rnd_cntr <= rnd_cntr + 1; + if (rnd_cntr == 62) begin + rnd_cntr <= 0; + i_block.rdy <= ~(final_block || padding_only); + sha_state <= SHA_UPDATE_HV; + end + end + SHA_UPDATE_HV: begin + update_H(0); + if (final_block) begin + sha_state <= SHA_FINAL; + i_block.rdy <= 0; + end else if (padding_only) begin + final_block <= 1; + W <= 0; + W[(64-8)*8 +: 64] <= bit_len; + W[(bit_len/8) % 64 +: 8] <= 1; + end else if (i_block.rdy && i_block.val) begin + W <= i_block.dat; + bit_len <= bit_len + DAT_BITS; + if (i_block.eop) begin + msg_eop(); + i_block.rdy <= 0; + end + sha_state <= SHA_ROUNDS; + end + end + SHA_FINAL: begin + o_hash.val <= 1; + o_hash.dat <= H; + if (o_hash.val && o_hash.rdy) begin + o_hash.val <= 0; + sha_state <= SHA_IDLE; + end + end + endcase + end +end + +// On the msg .eop we need to make sure we add padding if there is space other +// wise set a flag so next time we call we only add the padding block +task msg_eop(); + + eop_l <= 1; + bit_len <= bit_len + (i_block.mod == 0 ? DAT_BITS : i_block.mod*8); + if (i_block.mod == 0 || i_block.mod > 64-9) begin + padding_only <= 1; // Means we need one block extra with only len (and possibly the terminating 0x1) + end else begin + final_block <= 1; // This is the final block and includes padding + end + + for (int i = 0; i < 64; i++) + M[i*8 +: 8] <= (i_block.mod == 0 || i < i_block.mod) ? i_block.dat[i*8 +: 8] : 0; + + if (i_block.mod != 0) + M[i_block.mod*8 +: 8] <= 1; + + if (i_block.mod < 64-9) + M[(64-8)*8 +: 64] <= bit_len + i_block.mod*8; + +endtask + +always_comb begin + W_nxt = little_sig1(W[14]) + W[9] + little_sig0(W[1]) + W[0]; +end +always_comb begin + T1 = V[H] + big_sig1(V[E]) + ch(V[E], V[F], V[G]) + sha256_pkg::K[rnd_cntr] + W[0]; + T2 = big_sig0(V[A]) + maj(V[A], V[B], V[C]); +end + +task compress(); + V[H] <= V[G]; + V[G] <= V[F]; + V[F] <= V[E]; + V[E] <= V[D] + T1; + V[D] <= V[C]; + V[C] <= V[B]; + V[B] <= V[A]; + V[A] <= T1 + T2; +endtask + +task update_HV(logic init); + if (init) begin + for (int i = 0; i < 8; i++) begin + H[i] <= sha256_pkg::IV[i]; + V[i] <= sha256_pkg::IV[i]; + end + end else begin + for (int i = 0; i < 8; i++) begin + H[i] <= H[i] + V[i]; + V[i] <= H[i] + V[i]; + end + end +endtask + +function [31:0] litte_sig0(input logic [31:0] in); + litte_sig0 = {rotr(in, 7)} ^ {rotr(in, 18)} ^ {shr(in, 3)}; +endfunction + +function [31:0] litte_sig1(input logic [31:0] in); + litte_sig1 = {rotr(in, 17)} ^ {rotr(in, 19)} ^ {shr(in, 10)}; +endfunction + +function [31:0] big_sig0(input logic [31:0] in); + big_sig0 = {rotr(in, 2)} ^ {rotr(in, 13)} ^ {rotr(in, 22)}; +endfunction + +function [31:0] big_sig1(input logic [31:0] in); + big_sig1 = {rotr(in, 6)} ^ {rotr(in, 11)} ^ {rotr(in, 25)}; +endfunction + +function [31:0] ch(input logic [31:0] x, y, z); + ch = (x & y) ^ (~x & z); +endfunction + +function [31:0] maj(input logic [31:0] x, y, z); + maj = (x & y) ^ (x & z) ^ (y & z); +endfunction + +function [31:0] rotr(input logic [31:0] in, input int bits); + for (int i = 0; i < 32; i++) rotr[i] = in[(i+bits) % 32]; +endfunction + +function [31:0] shr(input logic [31:0] in, input int bits); + shr = 0; + shr = in >> bits; +endfunction + + +endmodule \ No newline at end of file diff --git a/ip_cores/sha256/src/tb/sha256_top_tb.sv b/ip_cores/sha256/src/tb/sha256_top_tb.sv new file mode 100644 index 0000000..87ff33a --- /dev/null +++ b/ip_cores/sha256/src/tb/sha256_top_tb.sv @@ -0,0 +1,76 @@ +/* + The SHA256 testbench. + + 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 . + */ + +module sha256_top_tb(); + +import common_pkg::*; + +logic clk, rst; + +if_axi_stream #(.DAT_BYTS(64)) i_block(clk); +if_axi_stream #(.DAT_BYTS(32)) out_hash(clk); + +initial begin + rst = 0; + #100ns rst = 1; + #100ns rst = 0; +end + +initial begin + clk = 0; + forever #10ns clk = ~clk; +end + +sha256_top DUT ( + .i_clk ( clk ), + .i_rst ( rst ), + .i_block ( i_block ), + .o_hash ( out_hash ) +); + + +// This test runs the hash which is shown in the RFC, for "abc" +task rfc_test(); + begin + integer signed get_len; + logic [common_pkg::MAX_SIM_BYTS*8-1:0] get_dat; + $display("Running rfc_test...\n"); + expected = 'h239900d4ed8623b95a92f1dba88ad31895cc3345ded552c22d79ab2a39c5877dd1a2ffdb6fbb124bb7c45a68142f214ce9f6129fb697276a0d4d1c983fa580ba; + i_block.put_stream("cba", 3); + out_hash.get_stream(get_dat, get_len); + common_pkg::compare_and_print(get_dat, expected); + $display("rfc_test PASSED"); + end +endtask + + +// Main testbench calls +initial begin + i_block.reset_source(); + out_hash.rdy = 1; + + #200ns; + + rfc_test(); + + #10us $finish(); + +end + +endmodule \ No newline at end of file