diff --git a/docs/info.md b/docs/info.md index ce1f04c..30ea064 100644 --- a/docs/info.md +++ b/docs/info.md @@ -9,12 +9,12 @@ You can also include images in this folder and reference them in the markdown. E ## How it works -Explain how your project works +it does not work ## How to test -Explain how to use your project +by inspection ## External hardware -List external hardware used in your project (e.g. PMOD, LED display, etc), if any +ATP diff --git a/info.yaml b/info.yaml index 50bb751..5e7a6e0 100644 --- a/info.yaml +++ b/info.yaml @@ -1,9 +1,9 @@ # Tiny Tapeout project information project: - title: "" # Project title - author: "" # Your name - discord: "" # Your discord username, for communication and automatically assigning you a Tapeout role (optional) - description: "" # One line description of what your project does + title: "Interconnect Data Bus" # Project title + author: "1" # Your name + discord: "2" # Your discord username, for communication and automatically assigning you a Tapeout role (optional) + description: "Trasnfer data between modules" # One line description of what your project does language: "Verilog" # other examples include SystemVerilog, Amaranth, VHDL, etc clock_hz: 0 # Clock frequency in Hz (or 0 if not applicable) @@ -18,36 +18,42 @@ project: # Don't forget to also update `PROJECT_SOURCES` in test/Makefile. source_files: - "project.v" + - "data_bus_ctrl.v" + - "data_bus_module_interface.v" + - "ack_bus_arbiter.v" + - "ack_bus_module_interface.v" + - "global_arbiter.v" + - "interconnect.v" # The pinout of your project. Leave unused pins blank. DO NOT delete or add any pins. pinout: # Inputs - ui[0]: "" - ui[1]: "" - ui[2]: "" - ui[3]: "" - ui[4]: "" - ui[5]: "" - ui[6]: "" - ui[7]: "" + ui[0]: "ui_in0" # corresponds to ui_in[0] in project.v + ui[1]: "ui_in1" # ui_in[1] + ui[2]: "ui_in2" # ui_in[2] + ui[3]: "ui_in3" # ui_in[3] + ui[4]: "ui_in4" # ui_in[4] + ui[5]: "ui_in5" # ui_in[5] + ui[6]: "ui_in6" # ui_in[6] + ui[7]: "ui_in7" # ui_in[7] # Outputs - uo[0]: "" - uo[1]: "" - uo[2]: "" - uo[3]: "" - uo[4]: "" - uo[5]: "" - uo[6]: "" - uo[7]: "" + uo[0]: "uo_out0" # corresponds to uo_out[0] + uo[1]: "uo_out1" + uo[2]: "uo_out2" + uo[3]: "uo_out3" + uo[4]: "uo_out4" + uo[5]: "uo_out5" + uo[6]: "uo_out6" + uo[7]: "uo_out7" # Bidirectional pins - uio[0]: "" - uio[1]: "" - uio[2]: "" - uio[3]: "" - uio[4]: "" - uio[5]: "" + uio[0]: "" # uio_out[0] + uio[1]: "" # uio_out[1] + uio[2]: "" # uio_out[2] + uio[3]: "" # uio_out[3] + uio[4]: "" # uio_out[4] + uio[5]: "" # uio_out[5] uio[6]: "" uio[7]: "" diff --git a/src/ack_bus_arbiter.v b/src/ack_bus_arbiter.v index 923d03e..ea4081e 100644 --- a/src/ack_bus_arbiter.v +++ b/src/ack_bus_arbiter.v @@ -32,28 +32,28 @@ always @(*) begin ack_valid_n = 1'b1; winner_source_id = 2'b11; //CTRL has default ownship of the bus - if (ack_ready_to_ctrl == 1'b1) begin + if (ack_valid_from_ctrl == 1'b1) begin //CTRL module is asserting ack {ack_ready_to_ctrl, ack_ready_to_aes, ack_ready_to_sha, ack_ready_to_mem} = 4'b1000; ack_valid_n = 1'b0; winner_source_id = 2'b11; end - else if (ack_ready_to_aes == 1'b1) begin + else if (ack_valid_from_aes == 1'b1) begin //AES module is asserting ack {ack_ready_to_ctrl, ack_ready_to_aes, ack_ready_to_sha, ack_ready_to_mem} = 4'b0100; ack_valid_n = 1'b0; winner_source_id = 2'b10; end - else if (ack_ready_to_sha == 1'b1) begin + else if (ack_valid_from_sha == 1'b1) begin //SHA module is asserting ack {ack_ready_to_ctrl, ack_ready_to_aes, ack_ready_to_sha, ack_ready_to_mem} = 4'b0010; ack_valid_n = 1'b0; winner_source_id = 2'b01; end - else if (ack_ready_to_mem == 1'b1) begin + else if (ack_valid_from_mem == 1'b1) begin //MEM module is asserting ack {ack_ready_to_ctrl, ack_ready_to_aes, ack_ready_to_sha, ack_ready_to_mem} = 4'b0001; ack_valid_n = 1'b0; diff --git a/src/ack_bus_module_interface.v b/src/ack_bus_module_interface.v index 6ec81f3..34858b5 100644 --- a/src/ack_bus_module_interface.v +++ b/src/ack_bus_module_interface.v @@ -1,18 +1,18 @@ module ack_bus_module_interface ( input wire ACK_READY, output wire ACK_READY_TO_MODULE, - input wire MODULE_SIDE_ACK_VAILD, + input wire MODULE_SIDE_ACK_VALID, output wire ACK_VALID, input wire [1:0] MODULE_SIDE_MODULE_SOURCE_ID, output wire [1:0] MODULE_SOURCE_ID ); //READY -//MODULE_SIDE_ACK_VALID->ACK_VAILD +//MODULE_SIDE_ACK_VALID->ACK_VALID - assign ACK_READY=ACK_READY_TO_MODULE; - assign ACK_VAILD=MODULE_SIDE_ACK_VAILD; - assign MODULE_SOURCE_ID=MODULE_SIDE_MODULE_SOURCE_ID; + assign ACK_READY_TO_MODULE = ACK_READY; + assign ACK_VALID = MODULE_SIDE_ACK_VALID; + assign MODULE_SOURCE_ID = MODULE_SIDE_MODULE_SOURCE_ID; //This is an example of what the interfacing should look like, realistically the central ack_bus module will take care of all the interfacing //We can refer back to this in the future @@ -30,4 +30,4 @@ How to use: ACK Bus: The Ack bus receive the signals and -*/ +*/ \ No newline at end of file diff --git a/src/data_bus_ctrl.v b/src/data_bus_ctrl.v new file mode 100644 index 0000000..9046c51 --- /dev/null +++ b/src/data_bus_ctrl.v @@ -0,0 +1,243 @@ +`default_nettype none + +module data_bus_ctrl ( + input wire clk, + input wire rst_n, + // data bus handshake + input wire [7:0] data_on_bus, + input wire valid_on_bus, + + // ready signal from module + input wire rdy_mem, + input wire rdy_aes, + input wire rdy_sha, + + // ack bus handshake + input wire[1:0] id_on_ack, + input wire ready_on_ack, + input wire valid_on_ack, + + // 1bit per signal + output reg [3:0] rdy_rd_grant, // ready read grant + output reg [3:0] dv_rd_grant, // data/valid read grant + // encoded + output reg [1:0] data_sel, + + // ready output + output reg rdy_to_owner + // output reg [1:0] rdy_sel +); + //src/dest ids + localparam [1:0] ctrl_id = 2'b11, mem_id = 2'b00, aes_id = 2'b10, sha_id = 2'b01; + // ? tbd + localparam [3:0] mem_1b = (4'b0001 << mem_id); + localparam [3:0] sha_1b = (4'b0001 << sha_id); + localparam [3:0] aes_1b = (4'b0001 << aes_id); + localparam [3:0] ctrl_1b = (4'b0001 << ctrl_id); + // default control ? after opcode byte handshaked goes to tansmission, back when ack bus handshake + localparam [1:0] idle = 2'd0, hash_op_wait_ready = 2'd1, addr = 2'd2, module_transmission = 2'd3; + + // mux sel + reg [1:0] n_data_sel; + // rd grant + reg [3:0] n_rdy_rd_grant, n_dv_rd_grant; + // state of the bus + reg [1:0] state, n_state; + // counter if mem include + reg [1:0] counter, n_counter; + + // slice opcode + wire [1:0] dest, src, opcode; + // latch src and dest + reg[1:0] dest_latch, src_latch, n_dest_latch, n_src_latch; + assign dest = data_on_bus[5:4], src = data_on_bus[3:2], opcode = data_on_bus[1:0]; + + // handshakes on data/ack bus + wire ack_bus_fire; + assign ack_bus_fire = valid_on_ack && ready_on_ack && (id_on_ack == src_latch); + + // opcode format matching + wire rd_key_fire, rd_txt_fire, wr_txt_fire, hash_fire; + assign rd_key_fire = (opcode == 2'b00) && (dest == aes_id) && (src == mem_id); + assign rd_txt_fire = (opcode == 2'b01) && ((dest == aes_id) || (dest == sha_id)) && (src == mem_id); + assign wr_txt_fire = (opcode == 2'b10) && (dest == mem_id) && ((src == aes_id) || (src == sha_id)); + assign hash_fire = (opcode == 2'b11) && ((dest == aes_id) || (dest == sha_id)); + + // src ready and dest ready only for opcode + reg src_rdy, dest_rdy; + + + always @(*) begin + if (state == idle) begin + src_rdy = (src == mem_id) ? rdy_mem : (src == aes_id) ? rdy_aes : (src == sha_id) ? rdy_sha : 0; + dest_rdy = (dest == mem_id) ? rdy_mem : (dest == aes_id) ? rdy_aes : (dest == sha_id) ? rdy_sha : 0; + end else begin + src_rdy = (src_latch == mem_id) ? rdy_mem : (src_latch == aes_id) ? rdy_aes : (src_latch == sha_id) ? rdy_sha : 0; + dest_rdy = (dest_latch == mem_id) ? rdy_mem : (dest_latch == aes_id) ? rdy_aes : (dest_latch == sha_id) ? rdy_sha : 0; + end + end + + // sequential + always @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + state <= 0; + counter <= 0; + + data_sel <= 0; + rdy_rd_grant <= 0; + dv_rd_grant <= 0; + + dest_latch <= 0; + src_latch <= 0; + + // rdy_to_owner <= 0; + end else begin + state <= n_state; + counter <= n_counter; + + data_sel <= n_data_sel; + rdy_rd_grant <= n_rdy_rd_grant; + dv_rd_grant <= n_dv_rd_grant; + + dest_latch <= n_dest_latch; + src_latch <= n_src_latch; + + // rdy_to_owner <= n_rdy_to_owner; + end + end + + // next state/sel computing + always @(*) begin + // comb default + n_state = state; + n_counter = counter; + + n_data_sel = data_sel; + n_rdy_rd_grant = rdy_rd_grant; + n_dv_rd_grant = dv_rd_grant; + + n_dest_latch = dest_latch; + n_src_latch = src_latch; + + // n_rdy_to_owner = rdy_to_owner; + + case (state) + // when ctrl owns the bus by default + idle: begin + n_counter = 0; + n_dest_latch = 0; + n_src_latch = 0; + // control owns the bus by default + n_data_sel = ctrl_id; + // default + rdy_to_owner = 1; + // CTRL RD + n_rdy_rd_grant = ctrl_1b; + n_dv_rd_grant = ctrl_1b; + + if(valid_on_bus) begin + // keep same + if (hash_fire) begin + // id to 1hot decode + n_dv_rd_grant = (dest == aes_id) ? aes_1b : (dest == sha_id) ? sha_1b : ctrl_1b; + + n_dest_latch = dest; // remember AES or SHA + rdy_to_owner = 0; //stall for 1 cycle + n_state = hash_op_wait_ready; + end else if(rd_key_fire || rd_txt_fire || wr_txt_fire) begin + // handshake + rdy_to_owner = src_rdy & dest_rdy; + n_state = (src_rdy & dest_rdy) ? addr : idle; + // let source module see opcode, if not mem only 1byte for handshake + n_src_latch = (src_rdy & dest_rdy) ? src : 0; + n_dest_latch = (src_rdy & dest_rdy) ? dest : 0; + // set src + n_dv_rd_grant = set(n_dv_rd_grant, src); + n_dv_rd_grant = set(n_dv_rd_grant, dest); + + end else begin + // ggs + end + end + end + // when the opcode is xx xx xx 11 hashing operation, wait for fire (ideally 1 cycle but could more than that if sha/aes is not ready) + hash_op_wait_ready: begin + rdy_to_owner = dest_rdy; //should never be 0 otherwise control is cooked + if (valid_on_bus && dest_rdy) begin + n_state = idle; + n_rdy_rd_grant = ctrl_1b; + n_dv_rd_grant = ctrl_1b; + + n_dest_latch = 0; //reset latch + end + end + // when src/dest contains mem, count 3 handshake update ownership + addr: begin + // only mem needs handshake + rdy_to_owner = rdy_mem; + + // count the handshake + n_counter = (valid_on_bus && rdy_mem) ? counter + 1: counter; + // when 2 beat handshaked and the third handshake + if (counter == 2 && valid_on_bus && rdy_mem) begin + n_state = module_transmission; + n_data_sel = src_latch; + // set ready read grant to src module + n_rdy_rd_grant = set(4'b0000, src_latch); + // reset counter + n_counter = 0; + // rd grant reset + n_dv_rd_grant = clr(n_dv_rd_grant, src_latch); + end + end + // src module now owns the bus and ownership will be return upon ack handshake on ack bus + module_transmission: begin + n_counter = 0; + n_data_sel = src_latch; + rdy_to_owner = dest_rdy; + + if (ack_bus_fire) begin + n_data_sel = ctrl_id; + rdy_to_owner = 1; + n_dv_rd_grant = clr(n_dv_rd_grant, dest_latch); + n_state = idle; + n_src_latch = 0; + n_dest_latch = 0; + end + end + + default:; + endcase + + end + // set decode based on id + function [3:0] set; + input [3:0] grant; + input [1:0] id; + begin + set = grant; + case (id) + mem_id: set = set | mem_1b; + ctrl_id: set = set | ctrl_1b; + aes_id: set = set | aes_1b; + sha_id: set = set | sha_1b; + default: ; + endcase + end + endfunction + //clr decode based on id + function [3:0] clr; + input [3:0] grant; + input [1:0] id; + begin + clr = grant; + case (id) + mem_id: clr = clr & (~mem_1b); + ctrl_id: clr = clr & (~ctrl_1b); + aes_id: clr = clr & (~aes_1b); + sha_id: clr = clr & (~sha_1b); + default: ; + endcase + end + endfunction +endmodule \ No newline at end of file diff --git a/src/data_bus_module_interface.v b/src/data_bus_module_interface.v new file mode 100644 index 0000000..9fb5b67 --- /dev/null +++ b/src/data_bus_module_interface.v @@ -0,0 +1,33 @@ +module data_bus_module_interface ( + // input from global arbiter + input wire rdy_rd_grant, + input wire dv_rd_grant, + + // global bus into this local interface + input wire [9:0] data_in, // [9]:ready, [8]:valid, [7:0]:data + + // local module drives this into arbiter + input wire [9:0] data_from_module, + + // this interface drives into global arbiter + output reg [9:0] data_out, + + // this interface drives into module + output reg [9:0] data_to_module +); + +always @(*) begin + // module's outgoing data/valid/ready always forwarded to global side + data_out = data_from_module; + + // ready bit is controlled by ready-read grant + data_to_module[9] = rdy_rd_grant ? data_in[9] : 1'b0; + + // valid bit is controlled by data/valid-read grant + data_to_module[8] = dv_rd_grant ? data_in[8] : 1'b0; + + // data can be passed through or zeroed; valid=0 means ignore it anyway + data_to_module[7:0] = data_in[7:0]; +end + +endmodule \ No newline at end of file diff --git a/src/global_arbiter.v b/src/global_arbiter.v new file mode 100644 index 0000000..6652dd6 --- /dev/null +++ b/src/global_arbiter.v @@ -0,0 +1,139 @@ +`default_nettype none + +module global_arbiter ( + input wire clk, + input wire rst_n, + + // data local interfaces -> global arbiter + // packed format: + // [9] ready + // [8] valid + // [7:0] data + input wire [9:0] data_from_mem, + input wire [9:0] data_from_sha, + input wire [9:0] data_from_aes, + input wire [9:0] data_from_ctrl, + + // global arbiter -> all data local interfaces + output wire [9:0] data_to_locals, + + // read grants to data local interfaces + // bit mapping: + // [0] mem + // [1] sha + // [2] aes + // [3] ctrl + output wire [3:0] rdy_rd_grant, + output wire [3:0] dv_rd_grant, + + // ack local interfaces -> global arbiter + input wire ack_valid_from_mem, + input wire ack_valid_from_sha, + input wire ack_valid_from_aes, + input wire ack_valid_from_ctrl, + + // global arbiter -> ack local interfaces + output wire ack_ready_to_mem, + output wire ack_ready_to_sha, + output wire ack_ready_to_aes, + output wire ack_ready_to_ctrl, + // ack bus exposed to ctrl + output wire [2:0] ack_bus_to_ctrl +); + + // local IDs + // bit mapping: + // mem = 00 -> bit 0 + // sha = 01 -> bit 1 + // aes = 10 -> bit 2 + // ctrl = 11 -> bit 3 + + localparam [1:0] MEM_ID = 2'b00; + localparam [1:0] SHA_ID = 2'b01; + localparam [1:0] AES_ID = 2'b10; + localparam [1:0] CTRL_ID = 2'b11; + + // internal data bus wires + + wire [7:0] data_on_bus; + wire valid_on_bus; + wire rdy_to_owner; + wire [1:0] data_sel; + + // data_sel chooses who is currently driving the shared data bus + assign data_on_bus = (data_sel == MEM_ID) ? data_from_mem[7:0] : (data_sel == SHA_ID) ? data_from_sha[7:0] : + (data_sel == AES_ID) ? data_from_aes[7:0] : (data_sel == CTRL_ID) ? data_from_ctrl[7:0] : 8'h00; + + assign valid_on_bus = (data_sel == MEM_ID) ? data_from_mem[8] : (data_sel == SHA_ID) ? data_from_sha[8] : + (data_sel == AES_ID) ? data_from_aes[8] : (data_sel == CTRL_ID) ? data_from_ctrl[8] : 1'b0; + + // broadcast shared data bus back to all DATA_LOCAL interfaces + assign data_to_locals = {rdy_to_owner, valid_on_bus, data_on_bus}; + + // internal ack bus wires + // ack_bus_arbiter uses active-low valid + // data_bus_ctrl expects active-high valid + wire ack_valid_n; + wire valid_on_ack; + wire ready_on_ack; + wire [1:0] id_on_ack; + wire [1:0] winner_source_id; + + assign valid_on_ack = ~ack_valid_n; + assign ready_on_ack = 1; // forced to 1 since just need to check ack and winner for databus ctrl + assign id_on_ack = winner_source_id; + + // broadcast ack bus to ctrl + assign ack_bus_to_ctrl = {ack_valid_n, winner_source_id}; + // data bus controller + // controls data_sel, read grants, and ready-to-current-owner + + data_bus_ctrl u_data_bus_ctrl ( + .clk (clk), + .rst_n (rst_n), + + // current shared data bus + .data_on_bus (data_on_bus), + .valid_on_bus (valid_on_bus), + + // ready from modules through DATA_LOCAL interfaces + .rdy_mem (data_from_mem[9]), + .rdy_aes (data_from_aes[9]), + .rdy_sha (data_from_sha[9]), + + // ack bus handshake + .id_on_ack (id_on_ack), + .ready_on_ack (ready_on_ack), + .valid_on_ack (valid_on_ack), + + // grants and bus owner select + .rdy_rd_grant (rdy_rd_grant), + .dv_rd_grant (dv_rd_grant), + .data_sel (data_sel), + + // ready back to current data bus owner + .rdy_to_owner (rdy_to_owner) + ); + + // ack bus arbiter + // chooses one ack source and sends winner ID to data_bus_ctrl + + ack_bus_arbiter u_ack_bus_arbiter ( + // ack valid requests from ACK_LOCAL interfaces + .ack_valid_from_ctrl (ack_valid_from_ctrl), + .ack_valid_from_aes (ack_valid_from_aes), + .ack_valid_from_sha (ack_valid_from_sha), + .ack_valid_from_mem (ack_valid_from_mem), + + // ready back to ACK_LOCAL interfaces + .ack_ready_to_ctrl (ack_ready_to_ctrl), + .ack_ready_to_aes (ack_ready_to_aes), + .ack_ready_to_sha (ack_ready_to_sha), + .ack_ready_to_mem (ack_ready_to_mem), + + // shared ack bus result + .ack_valid_n (ack_valid_n), + .winner_source_id (winner_source_id) + ); + +endmodule \ No newline at end of file diff --git a/src/interconnect.v b/src/interconnect.v new file mode 100644 index 0000000..54e876c --- /dev/null +++ b/src/interconnect.v @@ -0,0 +1,329 @@ +`default_nettype none + +module interconnect_top ( + input wire clk, + input wire rst_n, + + // mem + + // mem -> data bus + input wire [7:0] data_in_mem, + input wire valid_in_mem, + output wire ready_out_mem, + + // data bus -> mem + output wire [7:0] data_out_mem, + output wire valid_out_mem, + input wire ready_in_mem, + + // debug / visible grants to mem DATA_LOCAL + output wire rdy_rd_grant_mem, + output wire dv_rd_grant_mem, + + // mem -> ack bus + input wire [1:0] ack_id_in_mem, + input wire ack_valid_in_mem, + output wire ack_ready_out_mem, + + // shs + + // sha -> data bus + input wire [7:0] data_in_sha, + input wire valid_in_sha, + output wire ready_out_sha, + + // data bus -> sha + output wire [7:0] data_out_sha, + output wire valid_out_sha, + input wire ready_in_sha, + + // debug / visible grants to sha DATA_LOCAL + output wire rdy_rd_grant_sha, + output wire dv_rd_grant_sha, + + // sha -> ack bus + input wire [1:0] ack_id_in_sha, + input wire ack_valid_in_sha, + output wire ack_ready_out_sha, + + // aes + + // aes -> data bus + input wire [7:0] data_in_aes, + input wire valid_in_aes, + output wire ready_out_aes, + + // data bus -> aes + output wire [7:0] data_out_aes, + output wire valid_out_aes, + input wire ready_in_aes, + + // debug / visible grants to aes DATA_LOCAL + output wire rdy_rd_grant_aes, + output wire dv_rd_grant_aes, + + // aes -> ack bus + input wire [1:0] ack_id_in_aes, + input wire ack_valid_in_aes, + output wire ack_ready_out_aes, + + // ctrl + + // ctrl -> data bus + // ctrl owns the bus by default and sends opcode/address bytes + input wire [7:0] data_in_ctrl, + input wire valid_in_ctrl, + output wire ready_out_ctrl, + + // ctrl only needs ready read grant + output wire rdy_rd_grant_ctrl, + + // ctrl -> ack bus + input wire [1:0] ack_id_in_ctrl, + input wire ack_valid_in_ctrl, + output wire ack_ready_out_ctrl, + // ack bus exposed to ctrl + output wire [2:0] ack_out_ctrl +); + + // packed data bus format: + // [9] ready + // [8] valid + // [7:0] data + wire [9:0] data_to_locals; + + wire [9:0] data_from_mem_local; + wire [9:0] data_from_sha_local; + wire [9:0] data_from_aes_local; + wire [9:0] data_from_ctrl_local; + + wire [9:0] data_to_mem_local; + wire [9:0] data_to_sha_local; + wire [9:0] data_to_aes_local; + wire [9:0] data_to_ctrl_local; + + + // grant wires from global arbiter core + // bit mapping: + // [0] mem + // [1] sha + // [2] aes + // [3] ctrl + wire [3:0] rdy_rd_grant; + wire [3:0] dv_rd_grant; + + assign rdy_rd_grant_mem = rdy_rd_grant[0]; + assign rdy_rd_grant_sha = rdy_rd_grant[1]; + assign rdy_rd_grant_aes = rdy_rd_grant[2]; + assign rdy_rd_grant_ctrl = rdy_rd_grant[3]; + + assign dv_rd_grant_mem = dv_rd_grant[0]; + assign dv_rd_grant_sha = dv_rd_grant[1]; + assign dv_rd_grant_aes = dv_rd_grant[2]; + + + // ctrl does not expose dv_rd_grant_ctrl because ctrl does not read data/valid + + // mem DATA_LOCAL + data_bus_module_interface u_mem_data_local ( + .rdy_rd_grant (rdy_rd_grant[0]), + .dv_rd_grant (dv_rd_grant[0]), + + // from GLOBAL_ARB to local interface + .data_in (data_to_locals), + + // from MEM module to local interface + .data_from_module ({ready_in_mem, valid_in_mem, data_in_mem}), + + // from local interface to GLOBAL_ARB + .data_out (data_from_mem_local), + + // from local interface to MEM module + .data_to_module (data_to_mem_local) + ); + + assign ready_out_mem = data_to_mem_local[9]; + assign valid_out_mem = data_to_mem_local[8]; + assign data_out_mem = data_to_mem_local[7:0]; + + // sha DATA_LOCAL + data_bus_module_interface u_sha_data_local ( + .rdy_rd_grant (rdy_rd_grant[1]), + .dv_rd_grant (dv_rd_grant[1]), + + // from GLOBAL_ARB to local interface + .data_in (data_to_locals), + + // from SHA module to local interface + .data_from_module ({ready_in_sha, valid_in_sha, data_in_sha}), + + // from local interface to GLOBAL_ARB + .data_out (data_from_sha_local), + + // from local interface to SHA module + .data_to_module (data_to_sha_local) + ); + + assign ready_out_sha = data_to_sha_local[9]; + assign valid_out_sha = data_to_sha_local[8]; + assign data_out_sha = data_to_sha_local[7:0]; + + // aes DATA_LOCAL + data_bus_module_interface u_aes_data_local ( + .rdy_rd_grant (rdy_rd_grant[2]), + .dv_rd_grant (dv_rd_grant[2]), + + // from GLOBAL_ARB to local interface + .data_in (data_to_locals), + + // from AES module to local interface + .data_from_module ({ready_in_aes, valid_in_aes, data_in_aes}), + + // from local interface to GLOBAL_ARB + .data_out (data_from_aes_local), + + // from local interface to AES module + .data_to_module (data_to_aes_local) + ); + + assign ready_out_aes = data_to_aes_local[9]; + assign valid_out_aes = data_to_aes_local[8]; + assign data_out_aes = data_to_aes_local[7:0]; + + + // ctrl DATA_LOCAL + // ctrl sends opcode/address bytes and only reads ready + data_bus_module_interface u_ctrl_data_local ( + .rdy_rd_grant (rdy_rd_grant[3]), + .dv_rd_grant (dv_rd_grant[3]), + + // from GLOBAL_ARB to local interface + .data_in (data_to_locals), + + // ctrl does not provide a ready input as a receiver + // ctrl only drives valid/data and reads ready_out_ctrl + .data_from_module ({1'b0, valid_in_ctrl, data_in_ctrl}), + + // from local interface to GLOBAL_ARB + .data_out (data_from_ctrl_local), + + // from local interface to CTRL module + .data_to_module (data_to_ctrl_local) + ); + + assign ready_out_ctrl = data_to_ctrl_local[9]; + + + // ack local interface wires + wire ack_valid_from_mem_local; + wire ack_valid_from_sha_local; + wire ack_valid_from_aes_local; + wire ack_valid_from_ctrl_local; + + wire ack_ready_to_mem_local; + wire ack_ready_to_sha_local; + wire ack_ready_to_aes_local; + wire ack_ready_to_ctrl_local; + + wire [1:0] ack_id_from_mem_local; + wire [1:0] ack_id_from_sha_local; + wire [1:0] ack_id_from_aes_local; + wire [1:0] ack_id_from_ctrl_local; + + wire [2:0] ack_bus_to_ctrl; + assign ack_out_ctrl = ack_bus_to_ctrl; + + // mem ACK_LOCAL + ack_bus_module_interface u_mem_ack_local ( + .ACK_READY (ack_ready_to_mem_local), + .ACK_READY_TO_MODULE (ack_ready_out_mem), + + .MODULE_SIDE_ACK_VALID (ack_valid_in_mem), + .ACK_VALID (ack_valid_from_mem_local), + + .MODULE_SIDE_MODULE_SOURCE_ID (ack_id_in_mem), + .MODULE_SOURCE_ID (ack_id_from_mem_local) + ); + + // sha ACK_LOCAL + ack_bus_module_interface u_sha_ack_local ( + .ACK_READY (ack_ready_to_sha_local), + .ACK_READY_TO_MODULE (ack_ready_out_sha), + + .MODULE_SIDE_ACK_VALID (ack_valid_in_sha), + .ACK_VALID (ack_valid_from_sha_local), + + .MODULE_SIDE_MODULE_SOURCE_ID (ack_id_in_sha), + .MODULE_SOURCE_ID (ack_id_from_sha_local) + ); + + + // aes ACK + ack_bus_module_interface u_aes_ack_local ( + .ACK_READY (ack_ready_to_aes_local), + .ACK_READY_TO_MODULE (ack_ready_out_aes), + + .MODULE_SIDE_ACK_VALID (ack_valid_in_aes), + .ACK_VALID (ack_valid_from_aes_local), + + .MODULE_SIDE_MODULE_SOURCE_ID (ack_id_in_aes), + .MODULE_SOURCE_ID (ack_id_from_aes_local) + ); + + + // ctrl ACK + ack_bus_module_interface u_ctrl_ack_local ( + .ACK_READY (ack_ready_to_ctrl_local), + .ACK_READY_TO_MODULE (ack_ready_out_ctrl), + + .MODULE_SIDE_ACK_VALID (ack_valid_in_ctrl), + .ACK_VALID (ack_valid_from_ctrl_local), + + .MODULE_SIDE_MODULE_SOURCE_ID (ack_id_in_ctrl), + .MODULE_SOURCE_ID (ack_id_from_ctrl_local) + ); + + // contains data_bus_ctrl + ack_bus_arbiter + global_arbiter u_global_arbiter ( + .clk (clk), + .rst_n (rst_n), + + // DATA_LOCALs -> GLOBAL_ARB + .data_from_mem (data_from_mem_local), + .data_from_sha (data_from_sha_local), + .data_from_aes (data_from_aes_local), + .data_from_ctrl (data_from_ctrl_local), + + // GLOBAL_ARB -> DATA_LOCALs + .data_to_locals (data_to_locals), + + // grants to DATA_LOCALs + .rdy_rd_grant (rdy_rd_grant), + .dv_rd_grant (dv_rd_grant), + + // ACK_LOCALs -> GLOBAL_ARB + .ack_valid_from_mem (ack_valid_from_mem_local), + .ack_valid_from_sha (ack_valid_from_sha_local), + .ack_valid_from_aes (ack_valid_from_aes_local), + .ack_valid_from_ctrl (ack_valid_from_ctrl_local), + + // GLOBAL_ARB -> ACK_LOCALs + .ack_ready_to_mem (ack_ready_to_mem_local), + .ack_ready_to_sha (ack_ready_to_sha_local), + .ack_ready_to_aes (ack_ready_to_aes_local), + .ack_ready_to_ctrl (ack_ready_to_ctrl_local), + + .ack_bus_to_ctrl (ack_bus_to_ctrl) + ); + + + // unused ack source id wires + wire _unused_ack_ids; + assign _unused_ack_ids = ^{ + ack_id_from_mem_local, + ack_id_from_sha_local, + ack_id_from_aes_local, + ack_id_from_ctrl_local + }; + +endmodule \ No newline at end of file diff --git a/src/project.v b/src/project.v index cd6f740..8cf7bce 100644 --- a/src/project.v +++ b/src/project.v @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ + +// wire by ChatGPT `default_nettype none module tt_um_example ( @@ -10,18 +12,243 @@ module tt_um_example ( output wire [7:0] uo_out, // Dedicated outputs input wire [7:0] uio_in, // IOs: Input path output wire [7:0] uio_out, // IOs: Output path - output wire [7:0] uio_oe, // IOs: Enable path (active high: 0=input, 1=output) - input wire ena, // always 1 when the design is powered, so you can ignore it - input wire clk, // clock - input wire rst_n // reset_n - low to reset + output wire [7:0] uio_oe, // IOs: Enable path + input wire ena, + input wire clk, + input wire rst_n ); - // All output pins must be assigned. If not used, assign to 0. - assign uo_out = ui_in + uio_in; // Example: ou_out is the sum of ui_in and uio_in - assign uio_out = 0; - assign uio_oe = 0; + // ============================================================ + // TinyTapeout quick smoke-test pin mapping + // ============================================================ + // + // ui_in[7:0] = test byte driven by CTRL and MEM + // + // uio_in[0] = valid_in_ctrl + // uio_in[1] = valid_in_mem + // uio_in[2] = ready_in_mem + // uio_in[3] = ready_in_aes + // uio_in[4] = ready_in_sha + // uio_in[5] = ack_valid_in_mem + // uio_in[6] = ack_valid_in_aes + // uio_in[7] = ack_valid_in_sha + // + // uo_out[0] = ready_out_ctrl + // uo_out[1] = ready_out_mem + // uo_out[2] = ready_out_aes + // uo_out[3] = ready_out_sha + // uo_out[4] = dv_rd_grant_mem + // uo_out[5] = dv_rd_grant_aes + // uo_out[6] = dv_rd_grant_sha + // uo_out[7] = ack_ready_out_mem + // ============================================================ + + wire [7:0] test_data_byte; + assign test_data_byte = ui_in; + + wire valid_in_ctrl; + wire valid_in_mem; + + wire ready_in_mem; + wire ready_in_aes; + wire ready_in_sha; + + wire ack_valid_in_mem; + wire ack_valid_in_aes; + wire ack_valid_in_sha; + + assign valid_in_ctrl = uio_in[0]; + assign valid_in_mem = uio_in[1]; + + assign ready_in_mem = uio_in[2]; + assign ready_in_aes = uio_in[3]; + assign ready_in_sha = uio_in[4]; + + assign ack_valid_in_mem = uio_in[5]; + assign ack_valid_in_aes = uio_in[6]; + assign ack_valid_in_sha = uio_in[7]; + + // uio pins are inputs only for this smoke-test wrapper + assign uio_oe = 8'b0000_0000; + assign uio_out = 8'b0000_0000; + + + // ============================================================ + // Wires from interconnect + // ============================================================ + + wire ready_out_mem; + wire ready_out_aes; + wire ready_out_sha; + wire ready_out_ctrl; + + wire [7:0] data_out_mem; + wire [7:0] data_out_aes; + wire [7:0] data_out_sha; + + wire valid_out_mem; + wire valid_out_aes; + wire valid_out_sha; + + wire rdy_rd_grant_mem; + wire rdy_rd_grant_aes; + wire rdy_rd_grant_sha; + wire rdy_rd_grant_ctrl; + + wire dv_rd_grant_mem; + wire dv_rd_grant_aes; + wire dv_rd_grant_sha; + + wire ack_ready_out_mem; + wire ack_ready_out_aes; + wire ack_ready_out_sha; + wire ack_ready_out_ctrl; + + wire[2:0] ack_bus_out_ctrl; + + // ============================================================ + // Debug outputs + // ============================================================ + + assign uo_out[0] = ready_out_ctrl; + assign uo_out[1] = ready_out_mem; + assign uo_out[2] = ready_out_aes; + assign uo_out[3] = ready_out_sha; + + assign uo_out[4] = dv_rd_grant_mem; + assign uo_out[5] = dv_rd_grant_aes; + assign uo_out[6] = dv_rd_grant_sha; + + assign uo_out[7] = ack_ready_out_mem; + + + // ============================================================ + // Interconnect + // ============================================================ + + interconnect_top u_interconnect ( + .clk (clk), + .rst_n (rst_n), + + // ======================================================== + // MEM + // ======================================================== + + // mem -> data bus + .data_in_mem (test_data_byte), + .valid_in_mem (valid_in_mem), + .ready_out_mem (ready_out_mem), + + // data bus -> mem + .data_out_mem (data_out_mem), + .valid_out_mem (valid_out_mem), + .ready_in_mem (ready_in_mem), + + // grants + .rdy_rd_grant_mem (rdy_rd_grant_mem), + .dv_rd_grant_mem (dv_rd_grant_mem), + + // mem -> ack bus + .ack_id_in_mem (2'b00), + .ack_valid_in_mem (ack_valid_in_mem), + .ack_ready_out_mem (ack_ready_out_mem), + + + // ======================================================== + // AES + // ======================================================== + + // aes -> data bus + // AES does not drive data in this lazy smoke test + .data_in_aes (8'h00), + .valid_in_aes (1'b0), + .ready_out_aes (ready_out_aes), + + // data bus -> aes + .data_out_aes (data_out_aes), + .valid_out_aes (valid_out_aes), + .ready_in_aes (ready_in_aes), + + // grants + .rdy_rd_grant_aes (rdy_rd_grant_aes), + .dv_rd_grant_aes (dv_rd_grant_aes), + + // aes -> ack bus + .ack_id_in_aes (2'b10), + .ack_valid_in_aes (ack_valid_in_aes), + .ack_ready_out_aes (ack_ready_out_aes), + + + // ======================================================== + // SHA + // ======================================================== + + // sha -> data bus + // SHA does not drive data in this lazy smoke test + .data_in_sha (8'h00), + .valid_in_sha (1'b0), + .ready_out_sha (ready_out_sha), + + // data bus -> sha + .data_out_sha (data_out_sha), + .valid_out_sha (valid_out_sha), + .ready_in_sha (ready_in_sha), + + // grants + .rdy_rd_grant_sha (rdy_rd_grant_sha), + .dv_rd_grant_sha (dv_rd_grant_sha), + + // sha -> ack bus + .ack_id_in_sha (2'b01), + .ack_valid_in_sha (ack_valid_in_sha), + .ack_ready_out_sha (ack_ready_out_sha), + + + // ======================================================== + // CTRL + // ======================================================== + + // ctrl -> data bus + .data_in_ctrl (test_data_byte), + .valid_in_ctrl (valid_in_ctrl), + .ready_out_ctrl (ready_out_ctrl), + + // ctrl ready-read grant + .rdy_rd_grant_ctrl (rdy_rd_grant_ctrl), + + // ctrl does not write ack in this smoke test + .ack_id_in_ctrl (2'b11), + .ack_valid_in_ctrl (1'b0), + .ack_ready_out_ctrl (ack_ready_out_ctrl), + + .ack_out_ctrl(ack_bus_out_ctrl) + ); + + + // ============================================================ + // Unused signals + // ============================================================ + + wire _unused; + assign _unused = ^{ + ena, + + data_out_mem, + data_out_aes, + data_out_sha, + + valid_out_mem, + valid_out_aes, + valid_out_sha, + + rdy_rd_grant_mem, + rdy_rd_grant_aes, + rdy_rd_grant_sha, + rdy_rd_grant_ctrl, - // List all unused inputs to prevent warnings - wire _unused = &{ena, clk, rst_n, 1'b0}; + ack_ready_out_aes, + ack_ready_out_sha, + ack_ready_out_ctrl + }; -endmodule +endmodule \ No newline at end of file diff --git a/test/Makefile b/test/Makefile index bb64345..cd5a8fc 100644 --- a/test/Makefile +++ b/test/Makefile @@ -5,7 +5,14 @@ SIM ?= icarus TOPLEVEL_LANG ?= verilog SRC_DIR = $(PWD)/../src -PROJECT_SOURCES = project.v +PROJECT_SOURCES = \ +project.v \ +interconnect.v \ +global_arbiter.v \ +ack_bus_module_interface.v \ +data_bus_ctrl.v \ +data_bus_module_interface.v \ +ack_bus_arbiter.v ifneq ($(GATES),yes) diff --git a/test/test.py b/test/test.py index fa7f92c..8c3d7d9 100644 --- a/test/test.py +++ b/test/test.py @@ -1,40 +1,248 @@ -# SPDX-FileCopyrightText: © 2024 Tiny Tapeout -# SPDX-License-Identifier: Apache-2.0 - import cocotb from cocotb.clock import Clock -from cocotb.triggers import ClockCycles +from cocotb.triggers import ClockCycles, Timer, ReadOnly, RisingEdge, FallingEdge + +# uio_in bit mapping +VALID_CTRL = 1 << 0 +VALID_MEM = 1 << 1 +READY_MEM = 1 << 2 +READY_AES = 1 << 3 +READY_SHA = 1 << 4 +ACK_MEM = 1 << 5 +ACK_AES = 1 << 6 +ACK_SHA = 1 << 7 + +# IDs +MEM = 0b00 +SHA = 0b01 +AES = 0b10 +CTRL = 0b11 + +# opcodes +OP_RD_KEY = 0b00 +OP_RD_TXT = 0b01 +OP_WR_TXT = 0b10 +OP_HASH = 0b11 + + +def enc_opcode(dest, src, op): + return ((dest & 0b11) << 4) | ((src & 0b11) << 2) | (op & 0b11) + + +def get_bit(sig, idx): + return (int(sig.value) >> idx) & 1 -@cocotb.test() -async def test_project(dut): - dut._log.info("Start") +async def settle(): + await Timer(1, "ns") + await ReadOnly() - # Set the clock period to 10 us (100 KHz) - clock = Clock(dut.clk, 10, units="us") - cocotb.start_soon(clock.start()) - # Reset - dut._log.info("Reset") - dut.ena.value = 1 +async def reset_dut(dut): + dut.rst_n.value = 0 dut.ui_in.value = 0 dut.uio_in.value = 0 - dut.rst_n.value = 0 - await ClockCycles(dut.clk, 10) + + await ClockCycles(dut.clk, 5) + dut.rst_n.value = 1 + await ClockCycles(dut.clk, 2) + # IMPORTANT: do not call settle() here + # because settle() ends in ReadOnly phase + + +@cocotb.test(timeout_time=100, timeout_unit="us") +async def interconnect_smoke_test(dut): + cocotb.start_soon(Clock(dut.clk, 10, "ns").start()) + + dut._log.info("Reset start") + await reset_dut(dut) + dut._log.info("Reset done") + + # uo_out mapping: + # [0] ready_out_ctrl + # [1] ready_out_mem + # [2] ready_out_aes + # [3] ready_out_sha + # [4] dv_rd_grant_mem + # [5] dv_rd_grant_aes + # [6] dv_rd_grant_sha + # [7] ack_ready_out_mem + + dut._log.info("Check reset: ctrl should be ready") + await settle() + assert get_bit(dut.uo_out, 0) == 1, "After reset, ready_out_ctrl should be 1" + + # leave ReadOnly phase before driving + await RisingEdge(dut.clk) + + dut._log.info("Test invalid opcode") + + # invalid hash: dest=MEM is invalid for hash + invalid_op = enc_opcode(MEM, MEM, OP_HASH) + + dut.ui_in.value = invalid_op + dut.uio_in.value = VALID_CTRL + await settle() + + assert get_bit(dut.uo_out, 0) == 1, "Invalid opcode should keep ctrl ready high" + assert get_bit(dut.uo_out, 4) == 0, "Invalid opcode should not grant MEM dv" + assert get_bit(dut.uo_out, 5) == 0, "Invalid opcode should not grant AES dv" + assert get_bit(dut.uo_out, 6) == 0, "Invalid opcode should not grant SHA dv" + + # leave ReadOnly phase before driving + await RisingEdge(dut.clk) + + dut.uio_in.value = 0 + dut.ui_in.value = 0 + await ClockCycles(dut.clk, 2) + + dut._log.info("Test hash opcode to AES") + + # hash to AES: dest=AES, src=MEM/don't-care, op=HASH + hash_aes = enc_opcode(AES, MEM, OP_HASH) + + # AES not ready first; ctrl valid high + dut.ui_in.value = hash_aes + dut.uio_in.value = VALID_CTRL + await settle() + + assert get_bit(dut.uo_out, 0) == 0, "Hash should stall ctrl for grant setup" + + # leave ReadOnly phase before clocking/checking next registered grant + await RisingEdge(dut.clk) + await settle() + + assert get_bit(dut.uo_out, 5) == 1, "Hash should grant AES dv after stall cycle" + assert get_bit(dut.uo_out, 0) == 0, "Ctrl should stay stalled while AES not ready" - dut._log.info("Test project behavior") + # leave ReadOnly phase before driving AES ready + await RisingEdge(dut.clk) - # Set the input values you want to test - dut.ui_in.value = 20 - dut.uio_in.value = 30 + # Now AES becomes ready, ctrl should be allowed to handshake + dut.uio_in.value = VALID_CTRL | READY_AES + await settle() - # Wait for one clock cycle to see the output values + assert get_bit(dut.uo_out, 0) == 1, "Ctrl ready should rise when AES ready" + + # leave ReadOnly phase and take handshake edge + await RisingEdge(dut.clk) + + # ctrl drops valid + dut.uio_in.value = READY_AES await ClockCycles(dut.clk, 1) + await settle() + + assert get_bit(dut.uo_out, 5) == 0, "AES dv grant should clear after hash handshake" + + dut._log.info("PASS: reset + invalid opcode + hash opcode smoke test") + +@cocotb.test(timeout_time=100, timeout_unit="us") +async def mem_to_aes_smoke_test(dut): + cocotb.start_soon(Clock(dut.clk, 10, "ns").start()) + + dut._log.info("Reset start") + await reset_dut(dut) + dut._log.info("Reset done") + + # uo_out mapping: + # [0] ready_out_ctrl + # [1] ready_out_mem + # [2] ready_out_aes + # [3] ready_out_sha + # [4] dv_rd_grant_mem + # [5] dv_rd_grant_aes + # [6] dv_rd_grant_sha + # [7] ack_ready_out_mem + + await settle() + assert get_bit(dut.uo_out, 0) == 1, "After reset, ctrl should be ready" + + dut._log.info("Test rd_txt MEM -> AES") + + rd_txt_mem_to_aes = enc_opcode(AES, MEM, OP_RD_TXT) # 0x21 + + # Opcode phase + await FallingEdge(dut.clk) + dut.ui_in.value = rd_txt_mem_to_aes + dut.uio_in.value = VALID_CTRL | READY_MEM | READY_AES + + await settle() + assert get_bit(dut.uo_out, 0) == 1, "Opcode phase: ctrl should see ready high" + + # Opcode handshake + await RisingEdge(dut.clk) + + # Address byte 0 + await FallingEdge(dut.clk) + dut.ui_in.value = 0xAA + dut.uio_in.value = VALID_CTRL | READY_MEM | READY_AES + + await settle() + assert get_bit(dut.uo_out, 0) == 1, "Addr byte 0: ctrl should be ready" + assert get_bit(dut.uo_out, 4) == 1, "After opcode: MEM dv grant should be high" + assert get_bit(dut.uo_out, 5) == 1, "After opcode: AES dv grant should be high" + + # Address byte 0 handshakes. + await RisingEdge(dut.clk) + # Address byte 1 + await FallingEdge(dut.clk) + dut.ui_in.value = 0xBB + dut.uio_in.value = VALID_CTRL | READY_MEM | READY_AES + + await settle() + assert get_bit(dut.uo_out, 0) == 1, "Addr byte 1: ctrl should be ready" + + # Address byte 1 handshakes. + await RisingEdge(dut.clk) + # Address byte 2 + await FallingEdge(dut.clk) + dut.ui_in.value = 0xCC + dut.uio_in.value = VALID_CTRL | READY_MEM | READY_AES + + await settle() + assert get_bit(dut.uo_out, 0) == 1, "Addr byte 2: ctrl should be ready" + + # Address byte 2 handshakes. + await RisingEdge(dut.clk) + + # Module transmission phase + await settle() + + assert get_bit(dut.uo_out, 1) == 1, "Module transmission: MEM should see ready" + assert get_bit(dut.uo_out, 4) == 0, "Module transmission: MEM dv grant should be cleared" + assert get_bit(dut.uo_out, 5) == 1, "Module transmission: AES dv grant should stay high" + + dut._log.info("MEM drives data, then sends ack") + + # MEM drives one data byte + await FallingEdge(dut.clk) + dut.ui_in.value = 0x5A + dut.uio_in.value = VALID_MEM | READY_AES + + await settle() + assert get_bit(dut.uo_out, 1) == 1, "MEM transmission: MEM should see ready from AES" + + # MEM asserts ack + await FallingEdge(dut.clk) + dut.ui_in.value = 0x5A + dut.uio_in.value = VALID_MEM | READY_AES | ACK_MEM + + await settle() + assert get_bit(dut.uo_out, 7) == 1, "MEM ack: ack_ready_out_mem should assert" + + # Ack is observed on this rising edge. + await RisingEdge(dut.clk) + + # Drop MEM valid/ack + await FallingEdge(dut.clk) + dut.uio_in.value = READY_AES + dut.ui_in.value = 0 + + await RisingEdge(dut.clk) + await settle() - # The following assersion is just an example of how to check the output values. - # Change it to match the actual expected output of your module: - assert dut.uo_out.value == 50 + assert get_bit(dut.uo_out, 0) == 1, "After MEM ack: ctrl should be ready again" + assert get_bit(dut.uo_out, 5) == 0, "After MEM ack: AES dv grant should clear" - # Keep testing the module by changing the input values, waiting for - # one or more clock cycles, and asserting the expected output values. + dut._log.info("PASS: rd_txt MEM -> AES smoke test") \ No newline at end of file