// gpio_timer: an APB4 peripheral with eight GPIO pins, an interval timer and one interrupt line. // The register map, from the brief (every field below is named from this table): // 0x00 DIR read/write [7:0] 1 drives the pin from OUT // 0x04 OUT read/write [7:0] the value driven // 0x08 IN read-only [7:0] the pins, through a two-flop synchronizer // 0x0c CTRL read/write [0] EN: the timer runs // 0x10 PERIOD read/write [7:0] cycles from one timer event to the next (0 and 1: every cycle) // 0x14 STATUS write-1-to-clear [0] TMR: the timer expired [1] PIN: pin 0 rose // 0x18 IRQEN read/write [0] TMR [1] PIN: which STATUS bits drive irq // Fields sit in byte 0 and change only when PSTRB[0] is 1. A write to IN, or any access to // another address, ends with PSLVERR and changes nothing. No wait states. module gpio_timer ( input logic pclk, input logic presetn, // active-low, asynchronous input logic psel, input logic penable, input logic pwrite, input logic [31:0] paddr, input logic [31:0] pwdata, input logic [3:0] pstrb, input logic [2:0] pprot, output logic pready, output logic [31:0] prdata, output logic pslverr, input logic [7:0] gpio_in, // the pins, asynchronous to pclk output logic [7:0] gpio_out, output logic [7:0] gpio_oe, output logic irq // registered: 1 while an enabled STATUS bit is 1 ); // The map as constants: addresses and bit positions. localparam logic [7:0] A_DIR = 8'h00, A_OUT = 8'h04, A_IN = 8'h08, A_CTRL = 8'h0c, A_PERIOD = 8'h10, A_STATUS = 8'h14, A_IRQEN = 8'h18; localparam int TMR = 0, PIN = 1; logic [7:0] dir_q, out_q, period_q, cnt_q, s1_q, s2_q; logic en_q, pin0_q; logic [1:0] status_q, irqen_q; // The bus (R3.5): no wait states, so a transfer completes in its first ACCESS cycle. assign pready = 1'b1; wire [7:0] a = paddr[7:0]; wire done = psel && penable; wire known = (a == A_DIR) || (a == A_OUT) || (a == A_IN) || (a == A_CTRL) || (a == A_PERIOD) || (a == A_STATUS) || (a == A_IRQEN); wire bad = !known || (pwrite && a == A_IN); assign pslverr = done && bad; wire wr = done && !bad && pwrite && pstrb[0]; // a write that lands, at the completion edge // The read mux: one arm per row of the map. always_comb begin prdata = 32'd0; case (a) A_DIR: prdata[7:0] = dir_q; A_OUT: prdata[7:0] = out_q; A_IN: prdata[7:0] = s2_q; A_CTRL: prdata[0] = en_q; A_PERIOD: prdata[7:0] = period_q; A_STATUS: prdata[1:0] = status_q; A_IRQEN: prdata[1:0] = irqen_q; default: prdata = 32'd0; endcase end // The events. wire tmr_ev = en_q && ({1'b0, cnt_q} + 9'd1 >= {1'b0, period_q}); // the last cycle of a period wire pin_ev = s2_q[0] && !pin0_q; // pin 0 rose // STATUS: write 1 to clear; an event in the same cycle as its clear wins. logic [1:0] status_d; always_comb begin status_d = status_q; if (wr && a == A_STATUS) status_d = status_d & ~pwdata[1:0]; if (tmr_ev) status_d[TMR] = 1'b1; if (pin_ev) status_d[PIN] = 1'b1; end always_ff @(posedge pclk or negedge presetn) if (!presetn) begin dir_q <= '0; out_q <= '0; en_q <= 1'b0; period_q <= '0; irqen_q <= '0; status_q <= '0; cnt_q <= '0; s1_q <= '0; s2_q <= '0; pin0_q <= 1'b0; irq <= 1'b0; end else begin if (wr && a == A_DIR) dir_q <= pwdata[7:0]; if (wr && a == A_OUT) out_q <= pwdata[7:0]; if (wr && a == A_CTRL) en_q <= pwdata[0]; if (wr && a == A_PERIOD) period_q <= pwdata[7:0]; if (wr && a == A_IRQEN) irqen_q <= pwdata[1:0]; cnt_q <= (!en_q || tmr_ev) ? 8'd0 : cnt_q + 8'd1; s1_q <= gpio_in; // two-flop synchronizer (R6.2) s2_q <= s1_q; pin0_q <= s2_q[0]; status_q <= status_d; irq <= |(status_q & irqen_q); // registered OR of the enabled bits end assign gpio_out = out_q; assign gpio_oe = dir_q; endmodule