//////////////////////////////////////////////////////////////////////////////// // // Filename: axilgpio // {{{ // Project: WB2AXIPSP: bus bridges and other odds and ends // // Purpose: A simple and basic AXI-lite input and output module. // Tristates are not supported internally, although output bits // may be used externally to create a tristate input. For example, when // driving an I2C controller, you might wish to do something like: // // assign i2c_scl = gpio_output[1] ? 1'bz : gpio_output[1]; // assign i2c_sda = gpio_output[0] ? 1'bz : gpio_output[0]; // // Or, as another example: // // assign generic_io = gpio_output[3] ? 1'bz : gpio_output[2]; // // Registers: // 0: LOAD // Write to this register will overwrite the output data bits. // 4: SET // Writes to this register will set every output data bit where // the written bit is set. // // OUTPUT[k] = OLD BIT[k] || NEW_BIT[k] // // 8: CLEAR // Writes to this register will clear every output data bit where // the written bit is set. // // OUTPUT[k] = OLD BIT[k] && (!NEW_BIT[k]) // // 12: TOGGLE // Writes to this register will toggle every output bit where // the bit written is set. // // OUTPUT[k] = OLD BIT[k] ^ NEW_BIT[k] // // The next four registers are present if (and only if) NIN > 0. If not, // the prior four registers will be repeated. // // 16: Input data (if NIN > 0) // This is the input data, following a two-register CDC. // 20: Input data toggle detection // A bit will be set in this register if ever the associated // input data bit toggles. To clear, write a '1' to the toggled // bit in this register. // // Bits from this register that are set will then create an // outgoing interrupt--provided they are not masked. // // 24: Input data interrupt mask // If any "mask" bit is set, then toggled data will not trigger // an interrupt. // 28: Input data interrupts active // This is the AND of toggled data and a clear interrupt mask bit. // // An output interrupt is generated if any of the bits in the interrupt // active register is high. // // Creator: Dan Gisselquist, Ph.D. // Gisselquist Technology, LLC // //////////////////////////////////////////////////////////////////////////////// // }}} // Copyright (C) 2021-2024, Gisselquist Technology, LLC // {{{ // // This file is part of the WB2AXIP project. // // The WB2AXIP 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 axilgpio #( // {{{ // // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite // is fixed at a width of 32-bits by Xilinx def'n, and 2) since // we only ever have 4 configuration words. parameter C_AXI_ADDR_WIDTH = 5, localparam C_AXI_DATA_WIDTH = 32, // OPT_SKIDBUFFER will increase throughput to 100% from 50% parameter [0:0] OPT_SKIDBUFFER = 1'b1, // OPT_LOWPOWER will force RDATA to zero if ever !RVALID parameter [0:0] OPT_LOWPOWER = 0, // NOUT : Number of output bits. Must be > 0 parameter NOUT = 30, // NIN: Number of input bits. May be zero if desired. parameter NIN = 5, parameter [NOUT-1:0] DEFAULT_OUTPUT = 0 // }}} ) ( // {{{ input wire S_AXI_ACLK, input wire S_AXI_ARESETN, // AXI-lite interface // {{{ input wire S_AXI_AWVALID, output wire S_AXI_AWREADY, input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, input wire [2:0] S_AXI_AWPROT, // input wire S_AXI_WVALID, output wire S_AXI_WREADY, input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, // output wire S_AXI_BVALID, input wire S_AXI_BREADY, output wire [1:0] S_AXI_BRESP, // input wire S_AXI_ARVALID, output wire S_AXI_ARREADY, input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, input wire [2:0] S_AXI_ARPROT, // output wire S_AXI_RVALID, input wire S_AXI_RREADY, output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, output wire [1:0] S_AXI_RRESP, // }}} output wire [NOUT-1:0] o_gpio, input wire [((NIN>0) ? (NIN-1):0):0] i_gpio, output wire o_int // }}} ); //////////////////////////////////////////////////////////////////////// // // Register/wire signal declarations // {{{ //////////////////////////////////////////////////////////////////////// // localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3; localparam [2:0] // ADDR_LOAD = 3'b000, // ADDR_SET = 3'b001, // ADDR_CLEAR = 3'b010, // ADDR_TOGGLE = 3'b011, ADDR_INDATA = 3'b100, ADDR_CHANGED= 3'b101, ADDR_MASK = 3'b110, ADDR_INT = 3'b111; wire i_reset = !S_AXI_ARESETN; wire axil_write_ready; wire [C_AXI_ADDR_WIDTH-ADDRLSB-1:0] awskd_addr; // wire [C_AXI_DATA_WIDTH-1:0] wskd_data; wire [C_AXI_DATA_WIDTH/8-1:0] wskd_strb; reg axil_bvalid; // wire axil_read_ready; wire [C_AXI_ADDR_WIDTH-ADDRLSB-1:0] arskd_addr; reg [C_AXI_DATA_WIDTH-1:0] axil_read_data; reg axil_read_valid; reg [31:0] r_gpio; wire [31:0] ck_gpio, ck_toggled, w_mask, int_toggled, wskd_gpio; // }}} //////////////////////////////////////////////////////////////////////// // // AXI-lite signaling // {{{ //////////////////////////////////////////////////////////////////////// // // // // Write signaling // // {{{ generate if (OPT_SKIDBUFFER) begin : SKIDBUFFER_WRITE // {{{ wire awskd_valid, wskd_valid; skidbuffer #( // {{{ .OPT_OUTREG(0), .OPT_LOWPOWER(OPT_LOWPOWER), .DW(C_AXI_ADDR_WIDTH-ADDRLSB) // }}} ) axilawskid( // {{{ .i_clk(S_AXI_ACLK), .i_reset(i_reset), .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), .i_data(S_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]), .o_valid(awskd_valid), .i_ready(axil_write_ready), .o_data(awskd_addr) // }}} ); skidbuffer #( // {{{ .OPT_OUTREG(0), .OPT_LOWPOWER(OPT_LOWPOWER), .DW(C_AXI_DATA_WIDTH+C_AXI_DATA_WIDTH/8) // }}} ) axilwskid( // {{{ .i_clk(S_AXI_ACLK), .i_reset(i_reset), .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), .i_data({ S_AXI_WDATA, S_AXI_WSTRB }), .o_valid(wskd_valid), .i_ready(axil_write_ready), .o_data({ wskd_data, wskd_strb }) // }}} ); assign axil_write_ready = awskd_valid && wskd_valid && (!S_AXI_BVALID || S_AXI_BREADY); // }}} end else begin : SIMPLE_WRITES // {{{ reg axil_awready; initial axil_awready = 1'b0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) axil_awready <= 1'b0; else axil_awready <= !axil_awready && (S_AXI_AWVALID && S_AXI_WVALID) && (!S_AXI_BVALID || S_AXI_BREADY); assign S_AXI_AWREADY = axil_awready; assign S_AXI_WREADY = axil_awready; assign awskd_addr = S_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]; assign wskd_data = S_AXI_WDATA; assign wskd_strb = S_AXI_WSTRB; assign axil_write_ready = axil_awready; // }}} end endgenerate initial axil_bvalid = 0; always @(posedge S_AXI_ACLK) if (i_reset) axil_bvalid <= 0; else if (axil_write_ready) axil_bvalid <= 1; else if (S_AXI_BREADY) axil_bvalid <= 0; assign S_AXI_BVALID = axil_bvalid; assign S_AXI_BRESP = 2'b00; // }}} // // Read signaling // // {{{ generate if (OPT_SKIDBUFFER) begin : SKIDBUFFER_READ // {{{ wire arskd_valid; skidbuffer #(.OPT_OUTREG(0), .OPT_LOWPOWER(OPT_LOWPOWER), .DW(C_AXI_ADDR_WIDTH-ADDRLSB)) axilarskid(// .i_clk(S_AXI_ACLK), .i_reset(i_reset), .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), .i_data(S_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]), .o_valid(arskd_valid), .i_ready(axil_read_ready), .o_data(arskd_addr)); assign axil_read_ready = arskd_valid && (!axil_read_valid || S_AXI_RREADY); // }}} end else begin : SIMPLE_READS // {{{ reg axil_arready; always @(*) axil_arready = !S_AXI_RVALID; assign arskd_addr = S_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]; assign S_AXI_ARREADY = axil_arready; assign axil_read_ready = (S_AXI_ARVALID && S_AXI_ARREADY); // }}} end endgenerate initial axil_read_valid = 1'b0; always @(posedge S_AXI_ACLK) if (i_reset) axil_read_valid <= 1'b0; else if (axil_read_ready) axil_read_valid <= 1'b1; else if (S_AXI_RREADY) axil_read_valid <= 1'b0; assign S_AXI_RVALID = axil_read_valid; assign S_AXI_RDATA = axil_read_data; assign S_AXI_RRESP = 2'b00; // }}} // }}} //////////////////////////////////////////////////////////////////////// // // AXI-lite register logic // {{{ //////////////////////////////////////////////////////////////////////// // // assign wskd_gpio = apply_wstrb(r_gpio, wskd_data, wskd_strb); // r_gpio, o_gpio // {{{ initial r_gpio = 0; always @(posedge S_AXI_ACLK) begin if (axil_write_ready) begin case({ (awskd_addr[2] && (NIN > 0)), awskd_addr[1:0] }) 3'b000: r_gpio <= wskd_gpio; 3'b001: begin // SET // {{{ if (wskd_strb[0]) r_gpio[ 7: 0]<= r_gpio[ 7: 0]| wskd_data[ 7: 0]; if (wskd_strb[1]) r_gpio[15: 8]<= r_gpio[15: 8]| wskd_data[15: 8]; if (wskd_strb[2]) r_gpio[23:16]<= r_gpio[23:16]| wskd_data[23:16]; if (wskd_strb[3]) r_gpio[31:24]<= r_gpio[31:24]| wskd_data[31:24]; end // }}} 3'b010: begin // CLEAR // {{{ if (wskd_strb[0]) r_gpio[ 7: 0]<= r_gpio[ 7: 0]&~wskd_data[ 7: 0]; if (wskd_strb[1]) r_gpio[15: 8]<= r_gpio[15: 8]&~wskd_data[15: 8]; if (wskd_strb[2]) r_gpio[23:16]<= r_gpio[23:16]&~wskd_data[23:16]; if (wskd_strb[3]) r_gpio[31:24]<= r_gpio[31:24]&~wskd_data[31:24]; end // }}} 3'b011: begin // TOGGLE // {{{ if (wskd_strb[0]) r_gpio[ 7: 0]<=r_gpio[ 7: 0] ^ wskd_data[ 7: 0]; if (wskd_strb[1]) r_gpio[15: 8]<=r_gpio[15: 8] ^ wskd_data[15: 8]; if (wskd_strb[2]) r_gpio[23:16]<=r_gpio[23:16] ^ wskd_data[23:16]; if (wskd_strb[3]) r_gpio[31:24]<=r_gpio[31:24] ^ wskd_data[31:24]; end // }}} default: begin end // Input registers endcase end if (i_reset) r_gpio[NOUT-1:0] <= DEFAULT_OUTPUT; if (NOUT < 32) r_gpio[31:((NOUT < 32) ? NOUT : 31)] <= 0; end assign o_gpio = r_gpio[NOUT-1:0]; // }}} // i_gpio -> ck_gpio, $changed(i_gpio) -> ck_toggled, r_mask // {{{ generate if (NIN > 0) begin : INPUT_HANDLING // {{{ // Poss interrupts: Toggle, Rise, Fall // Only toggle is implemented here reg [NIN-1:0] last_gpio, qq_gpio, q_gpio, toggled, r_mask; wire [31:0] wstrb_mask; reg r_int; integer ik; // r_int // {{{ initial r_int = 0; always @(posedge S_AXI_ACLK) if (i_reset) r_int <= 0; else r_int <= |(r_mask & toggled); // }}} // Two clock CDC: last_gpio, qq_gpio, q_gpio // {{{ initial { last_gpio, qq_gpio, q_gpio } = 0; always @(posedge S_AXI_ACLK) if (i_reset) { last_gpio, qq_gpio, q_gpio } <= 0; else { last_gpio, qq_gpio, q_gpio } <= { qq_gpio, q_gpio, i_gpio }; // }}} // Toggled // {{{ initial toggled = 0; always @(posedge S_AXI_ACLK) if (i_reset) toggled <= 0; else begin for(ik=0; ik0)&& arskd_addr[2]), arskd_addr[1:0] }) 3'b0??: axil_read_data[NOUT-1:0] <= r_gpio[NOUT-1:0]; ADDR_INDATA: axil_read_data <= ck_gpio; ADDR_CHANGED: axil_read_data <= ck_toggled; ADDR_MASK: axil_read_data <= w_mask; ADDR_INT: axil_read_data <= int_toggled; endcase if (OPT_LOWPOWER && !axil_read_ready) axil_read_data <= 0; end // }}} function [C_AXI_DATA_WIDTH-1:0] apply_wstrb; // {{{ input [C_AXI_DATA_WIDTH-1:0] prior_data; input [C_AXI_DATA_WIDTH-1:0] new_data; input [C_AXI_DATA_WIDTH/8-1:0] wstrb; integer k; for(k=0; k 0) begin : CHECK_INPUT (* anyconst *) reg [$clog2(NIN)-1:0] f_ibit; always @(*) assume(f_ibit < NIN); always @(posedge S_AXI_ACLK) if ($past(i_reset) || i_reset) begin if ($past(i_reset)) assert(!ck_gpio[f_ibit]); end else begin if (!$past(i_reset, 2) && $past(ck_gpio[f_ibit]) != $past(ck_gpio[f_ibit],2)) assert(ck_toggled[f_ibit]); else if ($past(axil_write_ready && awskd_addr == ADDR_CHANGED && wskd_strb[f_ibit/8] && wskd_data[f_ibit])) assert(!ck_toggled[f_ibit]); end faxil_register #( // {{{ .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH), .ADDR({ ADDR_INDATA, {(ADDRLSB){1'b0}} }), .MASK({(C_AXI_DATA_WIDTH){1'b0}}) // }}} ) finputs ( // {{{ .S_AXI_ACLK(S_AXI_ACLK), .S_AXI_ARESETN(S_AXI_ARESETN), .S_AXIL_AWW(axil_write_ready), .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), .S_AXIL_WDATA(wskd_data), .S_AXIL_WSTRB(wskd_strb), .S_AXIL_BVALID(S_AXI_BVALID), .S_AXIL_AR(axil_read_ready), .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), .S_AXIL_RVALID(S_AXI_RVALID), .S_AXIL_RDATA(S_AXI_RDATA), .i_register(ck_gpio) // }}} ); faxil_register #( // {{{ .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH), .ADDR({ ADDR_CHANGED, {(ADDRLSB){1'b0}} }), .MASK({(C_AXI_DATA_WIDTH){1'b0}}) // }}} ) ftoggled ( // {{{ .S_AXI_ACLK(S_AXI_ACLK), .S_AXI_ARESETN(S_AXI_ARESETN), .S_AXIL_AWW(axil_write_ready), .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), .S_AXIL_WDATA(wskd_data), .S_AXIL_WSTRB(wskd_strb), .S_AXIL_BVALID(S_AXI_BVALID), .S_AXIL_AR(axil_read_ready), .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), .S_AXIL_RVALID(S_AXI_RVALID), .S_AXIL_RDATA(S_AXI_RDATA), .i_register(ck_toggled) // }}} ); faxil_register #( // {{{ .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH), .ADDR({ ADDR_MASK, {(ADDRLSB){1'b0}} }), .MASK({ {(C_AXI_DATA_WIDTH-NIN){1'b0}}, {(NIN){1'b1}} }) // }}} ) fmask ( // {{{ .S_AXI_ACLK(S_AXI_ACLK), .S_AXI_ARESETN(S_AXI_ARESETN), .S_AXIL_AWW(axil_write_ready), .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), .S_AXIL_WDATA(wskd_data), .S_AXIL_WSTRB(wskd_strb), .S_AXIL_BVALID(S_AXI_BVALID), .S_AXIL_AR(axil_read_ready), .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), .S_AXIL_RVALID(S_AXI_RVALID), .S_AXIL_RDATA(S_AXI_RDATA), .i_register(w_mask) // }}} ); end endgenerate `endif // }}} //////////////////////////////////////////////////////////////////////// // // Induction checks // {{{ //////////////////////////////////////////////////////////////////////// // // // }}} //////////////////////////////////////////////////////////////////////// // // Cover checks // {{{ //////////////////////////////////////////////////////////////////////// // // always @(posedge S_AXI_ACLK) if (!i_reset) cover(o_int); always @(posedge S_AXI_ACLK) if (!i_reset) cover($fell(o_int)); // }}} `endif // }}} endmodule