//////////////////////////////////////////////////////////////////////////////// // // Filename: memops.v // {{{ // Project: 10Gb Ethernet switch // // Purpose: A memory unit to support a CPU. // // In the interests of code simplicity, this memory operator is // susceptible to unknown results should a new command be sent to it // before it completes the last one. Unpredictable results might then // occurr. // // BIG ENDIAN // Note that this core assumes a big endian bus, with the MSB // of the bus word being the least bus address // // 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 memops #( // {{{ parameter ADDRESS_WIDTH=28, parameter DATA_WIDTH=32, // CPU's register width parameter BUS_WIDTH=32, parameter [0:0] OPT_LOCK=1'b1, WITH_LOCAL_BUS=1'b1, OPT_ALIGNMENT_ERR=1'b1, OPT_LOWPOWER=1'b0, OPT_LITTLE_ENDIAN = 1'b0, localparam AW=ADDRESS_WIDTH `ifdef FORMAL , parameter F_LGDEPTH = 2 `endif // }}} ) ( // {{{ input wire i_clk, i_reset, // CPU interface // {{{ input wire i_stb, i_lock, input wire [2:0] i_op, input wire [31:0] i_addr, input wire [DATA_WIDTH-1:0] i_data, input wire [4:0] i_oreg, // CPU outputs output wire o_busy, output reg o_rdbusy, output reg o_valid, output reg o_err, output reg [4:0] o_wreg, output reg [DATA_WIDTH-1:0] o_result, // }}} // Wishbone // {{{ output wire o_wb_cyc_gbl, output wire o_wb_cyc_lcl, output reg o_wb_stb_gbl, output reg o_wb_stb_lcl, output reg o_wb_we, output reg [AW-1:0] o_wb_addr, output reg [BUS_WIDTH-1:0] o_wb_data, output reg [BUS_WIDTH/8-1:0] o_wb_sel, // Wishbone inputs input wire i_wb_stall, i_wb_ack, i_wb_err, input wire [BUS_WIDTH-1:0] i_wb_data // }}} // }}} ); // Declarations // {{{ localparam WBLSB = $clog2(BUS_WIDTH/8); `ifdef FORMAL wire [(F_LGDEPTH-1):0] f_nreqs, f_nacks, f_outstanding; `endif wire misaligned; reg r_wb_cyc_gbl, r_wb_cyc_lcl; reg [2+WBLSB-1:0] r_op; wire lock_gbl, lock_lcl; wire lcl_bus, gbl_stb, lcl_stb; reg [BUS_WIDTH/8-1:0] oword_sel; wire [BUS_WIDTH/8-1:0] pre_sel; wire [BUS_WIDTH-1:0] pre_result; wire [1:0] oshift2; wire [WBLSB-1:0] oshift; // }}} // misaligned // {{{ generate if (OPT_ALIGNMENT_ERR) begin : GENERATE_ALIGNMENT_ERR reg r_misaligned; always @(*) casez({ i_op[2:1], i_addr[1:0] }) 4'b01?1: r_misaligned = i_stb; // Words must be halfword aligned 4'b0110: r_misaligned = i_stb; // Words must be word aligned 4'b10?1: r_misaligned = i_stb; // Halfwords must be aligned // 4'b11??: r_misaligned <= 1'b0; Byte access are never misaligned default: r_misaligned = 1'b0; endcase assign misaligned = r_misaligned; end else begin : NO_MISALIGNMENT_ERR assign misaligned = 1'b0; end endgenerate // }}} // lcl_stb, gbl_stb // {{{ assign lcl_bus = (WITH_LOCAL_BUS)&&(i_addr[31:24]==8'hff); assign lcl_stb = (i_stb)&&( lcl_bus)&&(!misaligned); assign gbl_stb = (i_stb)&&(!lcl_bus)&&(!misaligned); // }}} // r_wb_cyc_gbl, r_wb_cyc_lcl // {{{ initial r_wb_cyc_gbl = 1'b0; initial r_wb_cyc_lcl = 1'b0; always @(posedge i_clk) if (i_reset) begin r_wb_cyc_gbl <= 1'b0; r_wb_cyc_lcl <= 1'b0; end else if ((r_wb_cyc_gbl)||(r_wb_cyc_lcl)) begin if ((i_wb_ack)||(i_wb_err)) begin r_wb_cyc_gbl <= 1'b0; r_wb_cyc_lcl <= 1'b0; end end else begin // New memory operation // Grab the wishbone r_wb_cyc_lcl <= (lcl_stb); r_wb_cyc_gbl <= (gbl_stb); end // }}} // o_wb_stb_gbl // {{{ initial o_wb_stb_gbl = 1'b0; always @(posedge i_clk) if (i_reset) o_wb_stb_gbl <= 1'b0; else if ((i_wb_err)&&(r_wb_cyc_gbl)) o_wb_stb_gbl <= 1'b0; else if (gbl_stb) o_wb_stb_gbl <= 1'b1; else if (o_wb_cyc_gbl) o_wb_stb_gbl <= (o_wb_stb_gbl)&&(i_wb_stall); // }}} // o_wb_stb_lcl // {{{ initial o_wb_stb_lcl = 1'b0; always @(posedge i_clk) if (i_reset) o_wb_stb_lcl <= 1'b0; else if ((i_wb_err)&&(r_wb_cyc_lcl)) o_wb_stb_lcl <= 1'b0; else if (lcl_stb) o_wb_stb_lcl <= 1'b1; else if (o_wb_cyc_lcl) o_wb_stb_lcl <= (o_wb_stb_lcl)&&(i_wb_stall); // }}} // o_wb_we, o_wb_data, o_wb_sel // {{{ always @(*) begin oword_sel = 0; casez({ OPT_LITTLE_ENDIAN, i_op[2:1], i_addr[1:0] }) 5'b00???: oword_sel[3:0] = 4'b1111; 5'b0100?: oword_sel[3:0] = 4'b1100; 5'b0101?: oword_sel[3:0] = 4'b0011; 5'b01100: oword_sel[3:0] = 4'b1000; 5'b01101: oword_sel[3:0] = 4'b0100; 5'b01110: oword_sel[3:0] = 4'b0010; 5'b01111: oword_sel[3:0] = 4'b0001; // // verilator coverage_off 5'b10???: oword_sel[3:0] = 4'b1111; 5'b1100?: oword_sel[3:0] = 4'b0011; 5'b1101?: oword_sel[3:0] = 4'b1100; 5'b11100: oword_sel[3:0] = 4'b0001; 5'b11101: oword_sel[3:0] = 4'b0010; 5'b11110: oword_sel[3:0] = 4'b0100; 5'b11111: oword_sel[3:0] = 4'b1000; // verilator coverage_on // default: oword_sel[3:0] = 4'b1111; endcase end // pre_sel // {{{ generate if (BUS_WIDTH == 32) begin : COPY_PRESEL assign pre_sel = oword_sel; end else if (OPT_LITTLE_ENDIAN) begin : GEN_LILPRESEL wire [WBLSB-3:0] shift; assign shift = i_addr[WBLSB-1:2]; assign pre_sel = oword_sel << (4 * i_addr[WBLSB-1:2]); end else begin : GEN_PRESEL wire [WBLSB-3:0] shift; assign shift = {(WBLSB-2){1'b1}} ^ i_addr[WBLSB-1:2]; assign pre_sel = oword_sel << (4 * shift); end endgenerate // }}} assign oshift = i_addr[WBLSB-1:0]; assign oshift2 = i_addr[1:0]; initial o_wb_we = 1'b0; initial o_wb_data = 0; initial o_wb_sel = 0; always @(posedge i_clk) if (i_stb) begin o_wb_we <= i_op[0]; if (OPT_LOWPOWER) begin if (lcl_bus) begin // {{{ o_wb_data <= 0; casez({ OPT_LITTLE_ENDIAN, i_op[2:1] }) 3'b010: o_wb_data[31:0] <= { i_data[15:0], {(16){1'b0}} } >> (8*oshift2); 3'b011: o_wb_data[31:0] <= { i_data[ 7:0], {(24){1'b0}} } >> (8*oshift2); 3'b00?: o_wb_data[31:0] <= i_data[31:0]; // // verilator coverage_off 3'b110: o_wb_data <= { {(BUS_WIDTH-16){1'b0}}, i_data[15:0] } << (8*oshift2); 3'b111: o_wb_data <= { {(BUS_WIDTH-8){1'b0}}, i_data[ 7:0] } << (8*oshift2); 3'b10?: o_wb_data <= { {(BUS_WIDTH-32){1'b0}}, i_data[31:0] } << (8*oshift2); // verilator coverage_on // endcase // }}} end else begin // {{{ casez({ OPT_LITTLE_ENDIAN, i_op[2:1] }) 3'b010: o_wb_data <= { i_data[15:0], {(BUS_WIDTH-16){1'b0}} } >> (8*oshift); 3'b011: o_wb_data <= { i_data[ 7:0], {(BUS_WIDTH- 8){1'b0}} } >> (8*oshift); 3'b00?: o_wb_data <= { i_data[31:0], {(BUS_WIDTH-32){1'b0}} } >> (8*oshift); // 3'b110: o_wb_data <= { {(BUS_WIDTH-16){1'b0}}, i_data[15:0] } << (8*oshift); 3'b111: o_wb_data <= { {(BUS_WIDTH-8){1'b0}}, i_data[ 7:0] } << (8*oshift); 3'b10?: o_wb_data <= { {(BUS_WIDTH-32){1'b0}}, i_data[31:0] } << (8*oshift); // endcase // }}} end end else casez({ i_op[2:1] }) 2'b10: o_wb_data <= { (BUS_WIDTH/16){ i_data[15:0] } }; 2'b11: o_wb_data <= { (BUS_WIDTH/ 8){ i_data[7:0] } }; default: o_wb_data <= {(BUS_WIDTH/32){i_data}}; endcase if (lcl_bus) begin o_wb_addr <= i_addr[2 +: (AW+2>32 ? (32-2) : AW)]; o_wb_sel <= oword_sel; end else begin o_wb_addr <= i_addr[WBLSB +: (AW+WBLSB>32 ? (32-WBLSB) : AW)]; o_wb_sel <= pre_sel; end r_op <= { i_op[2:1] , i_addr[WBLSB-1:0] }; end else if ((OPT_LOWPOWER)&&(!o_wb_cyc_gbl)&&(!o_wb_cyc_lcl)) begin o_wb_we <= 1'b0; o_wb_addr <= 0; o_wb_data <= {(BUS_WIDTH){1'b0}}; o_wb_sel <= {(BUS_WIDTH/8){1'b0}}; end // }}} // o_valid // {{{ initial o_valid = 1'b0; always @(posedge i_clk) if (i_reset) o_valid <= 1'b0; else o_valid <= (((o_wb_cyc_gbl)||(o_wb_cyc_lcl)) &&(i_wb_ack)&&(!o_wb_we)); // }}} // o_err // {{{ initial o_err = 1'b0; always @(posedge i_clk) if (i_reset) o_err <= 1'b0; else if ((r_wb_cyc_gbl)||(r_wb_cyc_lcl)) o_err <= i_wb_err; else if ((i_stb)&&(!o_busy)) o_err <= misaligned; else o_err <= 1'b0; // }}} assign o_busy = (r_wb_cyc_gbl)||(r_wb_cyc_lcl); // o_rdbusy // {{{ initial o_rdbusy = 1'b0; always @(posedge i_clk) if (i_reset|| ((o_wb_cyc_gbl || o_wb_cyc_lcl)&&(i_wb_err || i_wb_ack))) o_rdbusy <= 1'b0; else if (i_stb && !i_op[0] && !misaligned) o_rdbusy <= 1'b1; else if (o_valid) o_rdbusy <= 1'b0; // }}} always @(posedge i_clk) if (i_stb) o_wreg <= i_oreg; // o_result // {{{ generate if (OPT_LITTLE_ENDIAN) begin : LILEND_RESULT assign pre_result = i_wb_data >> (8*r_op[$clog2(BUS_WIDTH/8)-1:0]); end else begin : BIGEND_RESULT assign pre_result = i_wb_data << (8*r_op[$clog2(BUS_WIDTH/8)-1:0]); end endgenerate always @(posedge i_clk) if ((OPT_LOWPOWER)&&(!i_wb_ack)) o_result <= 32'h0; else if (o_wb_cyc_lcl && (BUS_WIDTH != 32)) begin // The Local bus is naturally (and only) a 32-bit bus casez({ OPT_LITTLE_ENDIAN, r_op[WBLSB +: 2], r_op[1:0] }) 5'b?01??: o_result <= i_wb_data[31:0]; // // Big endian 5'b0100?: o_result <= { 16'h00, i_wb_data[31:16] }; 5'b0101?: o_result <= { 16'h00, i_wb_data[15: 0] }; 5'b01100: o_result <= { 24'h00, i_wb_data[31:24] }; 5'b01101: o_result <= { 24'h00, i_wb_data[23:16] }; 5'b01110: o_result <= { 24'h00, i_wb_data[15: 8] }; 5'b01111: o_result <= { 24'h00, i_wb_data[ 7: 0] }; // // Little endian : Same bus result, just grab a different bits // from the bus return to send back to the CPU. // verilator coverage_off 5'b1100?: o_result <= { 16'h00, i_wb_data[15: 0] }; 5'b1101?: o_result <= { 16'h00, i_wb_data[31:16] }; 5'b11100: o_result <= { 24'h00, i_wb_data[ 7: 0] }; 5'b11101: o_result <= { 24'h00, i_wb_data[15: 8] }; 5'b11110: o_result <= { 24'h00, i_wb_data[23:16] }; 5'b11111: o_result <= { 24'h00, i_wb_data[31:24] }; // verilator coverage_on default: o_result <= i_wb_data[31:0]; endcase end else begin casez({ OPT_LITTLE_ENDIAN, r_op[$clog2(BUS_WIDTH/8) +: 2] }) // Word // // Big endian 3'b00?: o_result <= pre_result[BUS_WIDTH-1:BUS_WIDTH-32]; 3'b010: o_result <= { 16'h00, pre_result[BUS_WIDTH-1:BUS_WIDTH-16] }; 3'b011: o_result <= { 24'h00, pre_result[BUS_WIDTH-1:BUS_WIDTH-8] }; // // Little endian : Same bus result, just grab a different bits // from the bus return to send back to the CPU. // verilator coverage_off 3'b10?: o_result <= pre_result[31: 0]; 3'b110: o_result <= { 16'h00, pre_result[15: 0] }; 3'b111: o_result <= { 24'h00, pre_result[ 7: 0] }; // verilator coverage_on // // Just to have an (unused) default // default: o_result <= pre_result[31:0]; (Messes w/ coverage) endcase end // }}} // lock_gbl and lock_lcl // {{{ generate if (OPT_LOCK) begin : GEN_LOCK // {{{ reg r_lock_gbl, r_lock_lcl; initial r_lock_gbl = 1'b0; initial r_lock_lcl = 1'b0; always @(posedge i_clk) if (i_reset) begin r_lock_gbl <= 1'b0; r_lock_lcl <= 1'b0; end else if (((i_wb_err)&&((r_wb_cyc_gbl)||(r_wb_cyc_lcl))) ||(misaligned)) begin // Kill the lock if // there's a bus error, or // User requests a misaligned memory op r_lock_gbl <= 1'b0; r_lock_lcl <= 1'b0; end else begin // Kill the lock if // i_lock goes down // User starts on the global bus, then switches // to local or vice versa r_lock_gbl <= (i_lock)&&((r_wb_cyc_gbl)||(lock_gbl)) &&(!lcl_stb); r_lock_lcl <= (i_lock)&&((r_wb_cyc_lcl)||(lock_lcl)) &&(!gbl_stb); end assign lock_gbl = r_lock_gbl; assign lock_lcl = r_lock_lcl; assign o_wb_cyc_gbl = (r_wb_cyc_gbl)||(lock_gbl); assign o_wb_cyc_lcl = (r_wb_cyc_lcl)||(lock_lcl); // }}} end else begin : NO_LOCK // {{{ assign o_wb_cyc_gbl = (r_wb_cyc_gbl); assign o_wb_cyc_lcl = (r_wb_cyc_lcl); assign { lock_gbl, lock_lcl } = 2'b00; // Make verilator happy // verilator lint_off UNUSED wire [2:0] lock_unused; assign lock_unused = { i_lock, lock_gbl, lock_lcl }; // verilator lint_on UNUSED // }}} end endgenerate // }}} `ifdef VERILATOR always @(posedge i_clk) if ((r_wb_cyc_gbl)||(r_wb_cyc_lcl)) assert(!i_stb); `endif // Make verilator happy // {{{ // verilator coverage_off // verilator lint_off UNUSED wire unused; assign unused = &{ 1'b0, pre_result }; generate if (AW < 22) begin : TOO_MANY_ADDRESS_BITS wire [(21-AW):0] unused_addr; assign unused_addr = i_addr[23:(AW+2)]; end endgenerate // verilator lint_on UNUSED // verilator coverage_on // }}} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // Formal properties // {{{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// `ifdef FORMAL `define ASSERT assert `ifdef MEMOPS `define ASSUME assume `else `define ASSUME assert `endif reg f_past_valid; reg [2:0] fcpu_op; reg [31:0] fcpu_addr, fcpu_data;; reg [BUS_WIDTH-1:0] fbus_data, fpre_data; reg [$clog2(BUS_WIDTH/8)-1:0] fcpu_shift; reg fcpu_local, fcpu_misaligned; reg [BUS_WIDTH/8-1:0] fbus_sel, fpre_sel; initial f_past_valid = 0; always @(posedge i_clk) f_past_valid <= 1'b1; always @(*) if (!f_past_valid) `ASSUME(i_reset); //////////////////////////////////////////////////////////////////////// // // Bus properties // {{{ //////////////////////////////////////////////////////////////////////// // // initial `ASSUME(!i_stb); wire f_cyc, f_stb; assign f_cyc = (o_wb_cyc_gbl)||(o_wb_cyc_lcl); assign f_stb = (o_wb_stb_gbl)||(o_wb_stb_lcl); fwb_master #( // {{{ .AW(AW), .F_LGDEPTH(F_LGDEPTH), .DW(BUS_WIDTH), .F_OPT_RMW_BUS_OPTION(OPT_LOCK), .F_OPT_DISCONTINUOUS(OPT_LOCK) // }}} ) f_wb( // {{{ i_clk, i_reset, f_cyc, f_stb, o_wb_we, o_wb_addr, o_wb_data, o_wb_sel, i_wb_ack, i_wb_stall, i_wb_data, i_wb_err, f_nreqs, f_nacks, f_outstanding // }}} ); // Rule: Only one of the two CYC's may be valid, never both always @(posedge i_clk) `ASSERT((!o_wb_cyc_gbl)||(!o_wb_cyc_lcl)); // Rule: Only one of the two STB's may be valid, never both always @(posedge i_clk) `ASSERT((!o_wb_stb_gbl)||(!o_wb_stb_lcl)); // Rule: if WITH_LOCAL_BUS is ever false, neither the local STB nor CYC // may be valid always @(*) if (!WITH_LOCAL_BUS) begin `ASSERT(!o_wb_cyc_lcl); `ASSERT(!o_wb_stb_lcl); end // Rule: If the global CYC is ever true, the LCL one cannot be true // on the next clock without an intervening idle of both always @(posedge i_clk) if ((f_past_valid)&&($past(r_wb_cyc_gbl))) `ASSERT(!r_wb_cyc_lcl); // Same for if the LCL CYC is true always @(posedge i_clk) if ((f_past_valid)&&($past(r_wb_cyc_lcl))) `ASSERT(!r_wb_cyc_gbl); // STB can never be true unless CYC is also true always @(posedge i_clk) if (o_wb_stb_gbl) `ASSERT(r_wb_cyc_gbl); always @(posedge i_clk) if (o_wb_stb_lcl) `ASSERT(r_wb_cyc_lcl); // This core only ever has zero or one outstanding transaction(s) always @(posedge i_clk) if ((o_wb_stb_gbl)||(o_wb_stb_lcl)) begin `ASSERT(f_outstanding == 0); end else `ASSERT((f_outstanding == 0)||(f_outstanding == 1)); // The LOCK function only allows up to two transactions (at most) // before CYC must be dropped. always @(posedge i_clk) if ((o_wb_stb_gbl)||(o_wb_stb_lcl)) begin if (OPT_LOCK) begin `ASSERT((f_outstanding == 0)||(f_outstanding == 1)); end else `ASSERT(f_nreqs <= 1); end // }}} //////////////////////////////////////////////////////////////////////// // // CPU properties // {{{ //////////////////////////////////////////////////////////////////////// // // reg f_done; wire [(F_LGDEPTH-1):0] cpu_outstanding; wire f_pc, f_rdbusy, f_gie, f_read_cycle; wire [4:0] f_last_reg; wire [4:0] f_addr_reg; // Verilator lint_off UNDRIVEN (* anyseq *) reg [4:0] f_areg; // Verilator lint_on UNDRIVEN assign f_rdbusy = f_cyc && (f_stb || f_outstanding > 0) && !o_wb_we; initial f_done = 1'b0; always @(posedge i_clk) if (i_reset) f_done <= 1'b0; else f_done <= ((o_wb_cyc_gbl)||(o_wb_cyc_lcl))&&(i_wb_ack); fmem #( // {{{ .F_LGDEPTH(F_LGDEPTH), .OPT_LOCK(OPT_LOCK), .OPT_MAXDEPTH(1) // }}} ) fmemi( // {{{ .i_clk(i_clk), .i_sys_reset(i_reset), .i_cpu_reset(i_reset), .i_stb(i_stb), .i_pipe_stalled(o_busy), .i_clear_cache(1'b0), .i_lock(i_lock), .i_op(i_op), .i_addr(i_addr), .i_data(i_data), .i_oreg(i_oreg), .i_areg(f_areg), .i_busy(o_busy), .i_rdbusy(f_rdbusy), .i_valid(o_valid), .i_done(f_done), .i_err(o_err), .i_wreg(o_wreg), .i_result(o_result), .f_outstanding(cpu_outstanding), .f_pc(f_pc), .f_gie(f_gie), .f_read_cycle(f_read_cycle), .f_last_reg(f_last_reg), .f_addr_reg(f_addr_reg) // }}} ); always @(*) if (!o_err) assert(cpu_outstanding == f_outstanding + (f_stb ? 1:0) + ((f_done || o_err) ? 1:0)); always @(*) assert(cpu_outstanding <= 1); always @(*) if (f_pc) begin assert(o_wreg[3:1] == 3'h7); end else if (f_rdbusy) assert(o_wreg[3:1] != 3'h7); always @(*) if (o_busy) assert(o_wreg[4] == f_gie); always @(*) if (!o_err) assert(f_rdbusy == o_rdbusy); always @(*) if (o_busy) assert(o_wb_we == !f_read_cycle); always @(*) if (cpu_outstanding > 0) assert(f_last_reg == o_wreg); // }}} //////////////////////////////////////////////////////////////////////// // // Tying the two together // {{{ //////////////////////////////////////////////////////////////////////// // // // Following any i_stb request, assuming we are idle, immediately // begin a bus transaction always @(posedge i_clk) if ((f_past_valid)&&($past(i_stb)) &&(!$past(f_cyc))&&(!$past(i_reset))) begin if ($past(misaligned)) begin `ASSERT(!f_cyc); `ASSERT(!o_busy); `ASSERT(o_err); `ASSERT(!o_valid); end else begin `ASSERT(f_cyc); `ASSERT(o_busy); end end // always @(posedge i_clk) // if (o_busy) // `ASSUME(!i_stb); always @(*) if (o_err || o_valid) `ASSERT(!o_busy); always @(posedge i_clk) if (o_wb_cyc_gbl) `ASSERT((o_busy)||(lock_gbl)); always @(posedge i_clk) if (o_wb_cyc_lcl) `ASSERT((o_busy)||(lock_lcl)); always @(posedge i_clk) if (f_outstanding > 0) `ASSERT(o_busy); // If a transaction ends in an error, send o_err on the output port. always @(posedge i_clk) if (f_past_valid && !$past(i_reset)) begin if (($past(f_cyc))&&($past(i_wb_err))) begin `ASSERT(o_err); end else if ($past(misaligned)) `ASSERT(o_err); end // Always following a successful ACK, return an O_VALID value. always @(posedge i_clk) if (f_past_valid && !$past(i_reset)) begin if(($past(f_cyc))&&($past(i_wb_ack)) &&(!$past(o_wb_we))) begin `ASSERT(o_valid); end else if ($past(misaligned)) begin `ASSERT((!o_valid)&&(o_err)); end else `ASSERT(!o_valid); end always @(posedge i_clk) if (i_stb) begin fcpu_op <= i_op; fcpu_addr <= i_addr; fcpu_data <= i_data; end always @(*) begin fcpu_local = (&fcpu_addr[31:24]) && WITH_LOCAL_BUS; if (OPT_LITTLE_ENDIAN) begin // {{{ casez(fcpu_op[2:1]) 2'b11: fpre_sel = { {(BUS_WIDTH/8-1){1'b0}}, 1'b1 }; 2'b10: fpre_sel = { {(BUS_WIDTH/8-2){1'b0}}, 2'b11 }; 2'b0?: fpre_sel = { {(BUS_WIDTH/8-4){1'b0}}, 4'b1111 }; endcase casez(fcpu_op[2:1]) 2'b11: fpre_data = { {(BUS_WIDTH- 8){1'b0}}, fcpu_data[ 7:0] }; 2'b10: fpre_data = { {(BUS_WIDTH-16){1'b0}}, fcpu_data[15:0] }; 2'b0?: fpre_data = { {(BUS_WIDTH-32){1'b0}}, fcpu_data[31:0] }; endcase // }}} end else if (fcpu_local) begin // {{{ fpre_sel = 0; casez(fcpu_op[2:1]) 2'b11: fpre_sel[3:0] = 4'b1000; 2'b10: fpre_sel[3:0] = 4'b1100; 2'b0?: fpre_sel[3:0] = 4'b1111; endcase fpre_data = 0; casez(fcpu_op[2:1]) 2'b11: fpre_data[31:0] = { fcpu_data[ 7:0], {(24){1'b0}} }; 2'b10: fpre_data[31:0] = { fcpu_data[15:0], {(16){1'b0}} }; 2'b0?: fpre_data[31:0] = fcpu_data[31:0]; endcase // }}} end else begin // {{{ casez(fcpu_op[2:1]) 2'b11: fpre_sel = { 1'b1, {(BUS_WIDTH/8-1){1'b0}} }; 2'b10: fpre_sel = { 2'b11, {(BUS_WIDTH/8-2){1'b0}} }; 2'b0?: fpre_sel = { 4'b1111, {(BUS_WIDTH/8-4){1'b0}} }; endcase casez(fcpu_op[2:1]) 2'b11: fpre_data = { fcpu_data[ 7:0], {(BUS_WIDTH- 8){1'b0}} }; 2'b10: fpre_data = { fcpu_data[15:0], {(BUS_WIDTH-16){1'b0}} }; 2'b0?: fpre_data = { fcpu_data[31:0], {(BUS_WIDTH-32){1'b0}} }; endcase // }}} end casez({ fcpu_op[2:1], fcpu_addr[1:0] }) 4'b01?1: fcpu_misaligned = 1'b1; // Words must be halfword aligned 4'b0110: fcpu_misaligned = 1'b1; // Words must be word aligned 4'b10?1: fcpu_misaligned = 1'b1; // Halfwords must be aligned // 4'b11??: fcpu_misaligned <= 1'b0; Byte access are never misaligned default: fcpu_misaligned = 1'b0; endcase if (fcpu_local) begin fcpu_shift = fcpu_addr[1:0]; if (OPT_LITTLE_ENDIAN) begin fbus_sel = fpre_sel << fcpu_shift; fbus_data = fpre_data << (8*fcpu_shift); end else begin fbus_sel = fpre_sel >> (fcpu_shift + (DATA_WIDTH/8-4)); fbus_data = fpre_data >> (8*(fcpu_shift + (DATA_WIDTH/8-4))); end end else begin fcpu_shift = fcpu_addr[WBLSB-1:0]; if (OPT_LITTLE_ENDIAN) begin fbus_sel = fpre_sel << fcpu_shift; fbus_data = fpre_data << (8*fcpu_shift); end else begin fbus_sel = fpre_sel >> fcpu_shift; fbus_data = fpre_data >> (8*fcpu_shift); end end if (!OPT_LOWPOWER) casez(fcpu_op[2:1]) 2'b11: fbus_data = {(BUS_WIDTH/ 8){fcpu_data[ 7:0] } }; 2'b10: fbus_data = {(BUS_WIDTH/16){fcpu_data[15:0] } }; 2'b0?: fbus_data = {(BUS_WIDTH/32){fcpu_data[31:0] } }; endcase end always @(*) if (OPT_ALIGNMENT_ERR && fcpu_misaligned) assert(!o_valid && !f_cyc); always @(*) if (f_stb) begin if (fcpu_local) begin assert(o_wb_stb_lcl); assert(o_wb_addr == fcpu_addr[AW+1:2]); end else begin assert(o_wb_stb_gbl); assert(o_wb_addr == fcpu_addr[WBLSB +: AW]); end if (fcpu_op[0]) begin `ASSERT(o_wb_we); `ASSERT(fcpu_misaligned || o_wb_sel == fbus_sel); `ASSERT(fcpu_misaligned || o_wb_data == fbus_data); end else begin `ASSERT(!o_wb_we); end end always @(*) if (f_cyc) assert(o_wb_cyc_lcl == fcpu_local); initial o_wb_we = 1'b0; always @(posedge i_clk) if ((f_past_valid)&&(!$past(i_reset))&&($past(i_stb))) begin // On a write, assert o_wb_we should be true assert( $past(i_op[0]) == o_wb_we); end always @(posedge i_clk) if (o_wb_stb_lcl) `ASSERT(fcpu_local); always @(posedge i_clk) if ((f_past_valid)&&(!$past(i_reset))&&($past(misaligned))) begin `ASSERT(!o_wb_cyc_gbl); `ASSERT(!o_wb_cyc_lcl); `ASSERT(!o_wb_stb_gbl); `ASSERT(!o_wb_stb_lcl); `ASSERT(o_err); end // always @(posedge i_clk) // if ((!f_past_valid)||($past(i_reset))) // `ASSUME(!i_stb); always @(posedge i_clk) if ((f_past_valid)&&(OPT_LOCK) &&(!$past(i_reset))&&(!$past(i_wb_err)) &&(!$past(misaligned)) &&(!$past(lcl_stb)) &&($past(i_lock))&&($past(lock_gbl))) assert(lock_gbl); always @(posedge i_clk) if ((f_past_valid)&&(OPT_LOCK) &&(!$past(i_reset))&&(!$past(i_wb_err)) &&(!$past(misaligned)) &&(!$past(lcl_stb)) &&($past(o_wb_cyc_gbl))&&($past(i_lock)) &&($past(lock_gbl))) assert(o_wb_cyc_gbl); always @(posedge i_clk) if ((f_past_valid)&&(OPT_LOCK) &&(!$past(i_reset))&&(!$past(i_wb_err)) &&(!$past(misaligned)) &&(!$past(gbl_stb)) &&($past(o_wb_cyc_lcl))&&($past(i_lock)) &&($past(lock_lcl))) assert(o_wb_cyc_lcl); // }}} //////////////////////////////////////////////////////////////////////// // // Cover properties // {{{ //////////////////////////////////////////////////////////////////////// // // always @(posedge i_clk) cover(i_wb_ack); // Cover a response on the same clock it is made always @(posedge i_clk) cover((o_wb_stb_gbl)&&(i_wb_ack)); // Cover a response a clock later always @(posedge i_clk) cover((o_wb_stb_gbl)&&(i_wb_ack)); always @(posedge i_clk) cover(f_done); always @(posedge i_clk) cover(f_done && !o_busy); generate if (WITH_LOCAL_BUS) begin // Same things on the local bus always @(posedge i_clk) cover((o_wb_cyc_lcl)&&(!o_wb_stb_lcl)&&(i_wb_ack)); always @(posedge i_clk) cover((o_wb_stb_lcl)&&(i_wb_ack)); end endgenerate // }}} // Make Verilator happy // {{{ // Verilator lint_off UNUSED wire unused; assign unused = &{ 1'b0, f_nacks, f_addr_reg }; // Verilator lint_on UNUSED // }}} `endif // }}} endmodule // // // Usage (from yosys): // (BFOR) (!ZOI,ALIGN) (ZOI,ALIGN) (!ZOI,!ALIGN) // Cells 230 226 281 225 // FDRE 114 116 116 116 // LUT2 17 23 76 19 // LUT3 9 23 17 20 // LUT4 15 4 11 14 // LUT5 18 18 7 15 // LUT6 33 18 54 38 // MUX7 16 12 2 // MUX8 8 1 1 // //