//////////////////////////////////////////////////////////////////////////////// // // Filename: prefetch.v // {{{ // Project: 10Gb Ethernet switch // // Purpose: This is a very simple instruction fetch approach. It gets // one instruction at a time. Future versions should pipeline // fetches and perhaps even cache results--this doesn't do that. It // should, however, be simple enough to get things running. // // The interface is fascinating. The 'i_pc' input wire is just a // suggestion of what to load. Other wires may be loaded instead. i_pc // is what must be output, not necessarily input. // // 20150919 -- Added support for the WB error signal. When reading an // instruction results in this signal being raised, the pipefetch module // will set an illegal instruction flag to be returned to the CPU together // with the instruction. Hence, the ZipCPU can trap on it if necessary. // // 20171020 -- Added a formal proof to prove that the module works. This // also involved adding a req_addr register, and the logic associated // with it. // // 20171113 -- Removed the req_addr register, replacing it with a bus abort // capability. // // Creator: Dan Gisselquist, Ph.D. // Gisselquist Technology, LLC // //////////////////////////////////////////////////////////////////////////////// // }}} // Copyright (C) 2023, Gisselquist Technology, LLC // {{{ // This file is part of the ETH10G project. // // The ETH10G project contains free software and gateware, licensed under the // Apache License, Version 2.0 (the "License"). You may not use this project, // or this file, except in compliance with the License. You may obtain a copy // of the License at // }}} // http://www.apache.org/licenses/LICENSE-2.0 // {{{ // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // //////////////////////////////////////////////////////////////////////////////// // `default_nettype none // }}} module prefetch #( // {{{ parameter ADDRESS_WIDTH=30, // Byte addr wid INSN_WIDTH=32, DATA_WIDTH=INSN_WIDTH, localparam AW=ADDRESS_WIDTH, DW=DATA_WIDTH, parameter [0:0] OPT_ALIGNED = 1'b0, parameter [0:0] OPT_LITTLE_ENDIAN = 1'b1 // }}} ) ( // {{{ input wire i_clk, i_reset, // CPU interaction wires input wire i_new_pc, i_clear_cache, i_ready, // We ignore i_pc unless i_new_pc is true as well input wire [AW-1:0] i_pc, output reg o_valid, // If output is valid output reg o_illegal, // bus err result output reg [INSN_WIDTH-1:0] o_insn, // Insn read from WB output reg [AW-1:0] o_pc, // Byt addr of that insn // Wishbone outputs output reg o_wb_cyc, o_wb_stb, // verilator coverage_off output wire o_wb_we, // == const 0 // verilator coverage_on output reg [AW-$clog2(DW/8)-1:0] o_wb_addr, // verilator coverage_off output wire [DW-1:0] o_wb_data, // == const 0 // verilator coverage_on // And return inputs input wire i_wb_stall, i_wb_ack, i_wb_err, input wire [DW-1:0] i_wb_data // }}} ); // Declare local variables // {{{ reg invalid; wire r_valid; wire [DATA_WIDTH-1:0] r_insn, i_wb_shifted; // }}} // These are kind of obligatory outputs when dealing with a bus, that // we'll set them here. Nothing's going to pay attention to these, // though, this is primarily for form. assign o_wb_we = 1'b0; assign o_wb_data = {(DATA_WIDTH){1'b0}}; // o_wb_cyc, o_wb_stb // {{{ // Let's build it simple and upgrade later: For each instruction // we do one bus cycle to get the instruction. Later we should // pipeline this, but for now let's just do one at a time. initial o_wb_cyc = 1'b0; initial o_wb_stb = 1'b0; always @(posedge i_clk) if ((i_reset || i_clear_cache)||(o_wb_cyc &&(i_wb_ack||i_wb_err))) begin // {{{ // End any bus cycle on a reset, or a return ACK // or error. o_wb_cyc <= 1'b0; o_wb_stb <= 1'b0; // }}} end else if (!o_wb_cyc &&( // Start if the last instruction output was // accepted, *and* it wasn't a bus error // response (i_ready && !o_illegal && !r_valid) // Start if the last bus result ended up // invalid ||(invalid) // Start on any request for a new address ||i_new_pc)) begin // {{{ // Initiate a bus transaction o_wb_cyc <= 1'b1; o_wb_stb <= 1'b1; // }}} end else if (o_wb_cyc) begin // {{{ // If our request has been accepted, then drop the // strobe line if (!i_wb_stall) o_wb_stb <= 1'b0; // Abort on new-pc // ... clear_cache is identical, save that it will // immediately be followed by a new PC, so we don't // need to worry about that other than to drop // CYC and STB here. if (i_new_pc) begin o_wb_cyc <= 1'b0; o_wb_stb <= 1'b0; end // }}} end // }}} // invalid // {{{ // If during the current bus request, a command came in from the CPU // that will invalidate the results of that request, then we need to // keep track of an "invalid" flag to remember that and so squash // the result. // initial invalid = 1'b0; always @(posedge i_clk) if (i_reset || !o_wb_cyc) invalid <= 1'b0; else if (i_new_pc) invalid <= 1'b1; // }}} // The wishbone request address, o_wb_addr // {{{ // The rule regarding this address is that it can *only* be changed // when no bus request is active. Further, since the CPU is depending // upon this value to know what "PC" is associated with the instruction // it is processing, we can't change until either the CPU has accepted // our result, or it is requesting a new PC (and hence not using the // output). // initial o_wb_addr= 0; always @(posedge i_clk) if (i_new_pc) o_wb_addr <= i_pc[AW-1:$clog2(DATA_WIDTH/8)]; else if (o_valid && i_ready && !r_valid) o_wb_addr <= o_wb_addr + 1'b1; // }}} //////////////////////////////////////////////////////////////////////// // // (Optionally) shift the output word into place // {{{ //////////////////////////////////////////////////////////////////////// // // // This only applies when the bus size doesn't match the instruction // word size. Here, we only support bus sizes greater than the // instruction word size. `ifdef FORMAL wire [DATA_WIDTH-1:0] f_bus_word; `endif generate if (DATA_WIDTH > INSN_WIDTH) begin : GEN_SUBSHIFT // {{{ localparam NSHIFT = $clog2(DATA_WIDTH/INSN_WIDTH); reg rg_valid; reg [DATA_WIDTH-1:0] rg_insn; reg [NSHIFT:0] r_count; reg [NSHIFT-1:0] r_shift; // rg_valid // {{{ always @(posedge i_clk) if (i_reset || i_new_pc || i_clear_cache) rg_valid <= 1'b0; else if (r_count <= ((o_valid && i_ready) ? 1:0)) begin rg_valid <= 1'b0; if (o_wb_cyc && i_wb_ack && !(&r_shift)) rg_valid <= 1'b1; end // }}} // rg_insn // {{{ always @(posedge i_clk) if (i_wb_ack && (r_count <= ((o_valid && i_ready) ? 1:0))) begin rg_insn <= i_wb_data; if (OPT_LITTLE_ENDIAN) begin rg_insn <= i_wb_shifted >> INSN_WIDTH; end else begin rg_insn <= i_wb_shifted << INSN_WIDTH; end end else if (o_valid && i_ready) begin if (OPT_LITTLE_ENDIAN) rg_insn <= rg_insn >> INSN_WIDTH; else rg_insn <= rg_insn << INSN_WIDTH; end // }}} // r_count // {{{ always @(posedge i_clk) if (i_reset || i_new_pc || i_clear_cache) r_count <= 0; // Verilator lint_off CMPCONST else if (o_valid && i_ready && r_valid) // Verilator lint_on CMPCONST r_count <= r_count - 1; else if (o_wb_cyc && i_wb_ack) begin // if (OPT_LITTLE_ENDIAN) r_count <= { 1'b0, ~r_shift }; end `ifdef FORMAL always @(*) if (!i_reset) begin assert(r_valid == (r_count > 0)); assert(r_count <= (1< 0) assert(!o_valid && !r_valid); `endif // }}} assign r_valid = rg_valid; ; assign r_insn = rg_insn; if (OPT_LITTLE_ENDIAN) begin : GEN_LIL_ENDIAN_SHIFT assign i_wb_shifted = i_wb_data >> (r_shift * INSN_WIDTH); end else begin : GEN_BIG_ENDIAN_SHIFT assign i_wb_shifted = i_wb_data << (r_shift * INSN_WIDTH); end // Keep Verilator happy // {{{ // Verilator coverage_off // Verilator lint_off UNUSED wire unused_shift; assign unused_shift = &{ 1'b0, r_insn[DATA_WIDTH-1:INSN_WIDTH], i_wb_shifted[DATA_WIDTH-1:INSN_WIDTH] }; // Verilator lint_on UNUSED // Verilator coverage_on // }}} `ifdef FORMAL assign f_bus_word = rg_insn << ((r_count-1)* INSN_WIDTH); always @(*) if (!i_reset && r_valid) begin assert(o_valid); assert(r_shift == 0); // assert((r_count + o_pc[NSHIFT-1:0]) == ((1< INSN_WIDTH) begin : F_CHECK_SHIFTED_WORD // {{{ wire [DW-1:0] f_shifted_insn; localparam IW = INSN_WIDTH; if (OPT_LITTLE_ENDIAN) begin assign f_shifted_insn = f_const_bus_word >> (f_const_addr[$clog2(DW/8)-1:$clog2(IW/8)] * IW); always @(*) assume(f_shifted_insn[IW-1:0] == f_const_insn); end else begin assign f_shifted_insn = f_const_bus_word << (f_const_addr[$clog2(DW/8)-1:$clog2(IW/8)] * IW); always @(*) assume(f_shifted_insn[DW-1:DW-IW] == f_const_insn); end // }}} end else begin // {{{ always @(*) assume(f_const_bus_word == f_const_insn); // }}} end endgenerate // f_addr_pending // {{{ initial f_addr_pending = 1'b0; always @(posedge i_clk) if (i_reset) f_addr_pending <= 1'b0; else if (!o_wb_cyc) f_addr_pending <= 1'b0; else if ((o_wb_stb)&&(f_this_addr)) begin if ((!i_wb_ack)&&(!i_wb_err)) f_addr_pending <= 1'b1; end // }}} // 1. Assume the correct response for the given address // {{{ always @(*) if ((o_wb_stb)&&(f_this_addr)&&(!i_wb_stall)) begin if (!f_const_illegal) begin assume(!i_wb_err); end else assume(!i_wb_ack); if (i_wb_ack) assume(f_this_data); end else if ((o_wb_cyc)&&(f_addr_pending)) begin if (!f_const_illegal) begin assume(!i_wb_err); end else assume(!i_wb_ack); if (i_wb_ack) assume(f_this_data); end // }}} // f_insn_pending // {{{ initial f_insn_pending = 1'b0; always @(posedge i_clk) if (i_reset) f_insn_pending <= 1'b0; else if (i_clear_cache) f_insn_pending <= 1'b0; else if (i_new_pc && f_this_req) f_insn_pending <= 1'b1; else if ((o_valid)||(i_new_pc)) f_insn_pending <= 1'b0; // }}} always @(posedge i_clk) if ((f_past_valid)&&($past(o_wb_cyc))&&(o_wb_cyc)&&(f_insn_pending)) assert(f_this_pc); always @(posedge i_clk) if (((f_past_valid)&&($past(o_wb_cyc))&&($past(f_insn_pending))) &&(!$past(i_reset))&&(!$past(i_clear_cache)) &&(!$past(i_new_pc))) begin if(!o_wb_cyc) assert(o_valid && f_this_pc); end always @(posedge i_clk) if ((f_past_valid)&&(!$past(o_wb_cyc))&&(!o_wb_cyc)) assert(!f_insn_pending); always @(posedge i_clk) if ((f_past_valid)&&($past(o_wb_cyc))&&(o_wb_cyc)&&(f_this_addr)) assert(f_addr_pending); always @(posedge i_clk) if ((f_past_valid)&&($past(o_wb_cyc))&&(f_insn_pending)) assert(f_this_addr); // }}} // Make Verilator happy // {{{ // Verilator lint_off UNUSED wire unused_formal; assign unused_formal = &{ 1'b0, f_nreqs, f_nacks, f_outstanding, f_const_addr[1:0] }; // Verilator lint_on UNUSED // }}} `endif // }}} endmodule // // Usage: (this) (mid) (past) // Cells 167 230 175 // FDRE 67 97 69 // LUT1 1 1 1 // LUT2 1 3 3 // LUT3 31 63 33 // LUT4 5 3 3 // LUT5 1 3 3 // LUT6 2 1 3 // MUXCY 29 29 31 // XORCY 30 30 32