From 0ee5e92264b539e4938427c296e8d1477ae704a7 Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Sun, 26 Jul 2020 21:27:53 +0100 Subject: [PATCH] start on conversion of xics.vhdl to nmigen see https://bugs.libre-soc.org/show_bug.cgi?id=407 --- src/soc/interrupts/xics.py | 442 +++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 src/soc/interrupts/xics.py diff --git a/src/soc/interrupts/xics.py b/src/soc/interrupts/xics.py new file mode 100644 index 00000000..b67cea7b --- /dev/null +++ b/src/soc/interrupts/xics.py @@ -0,0 +1,442 @@ +"""Microwatt xics.vhdl converted to nmigen +-- +# This is a simple XICS compliant interrupt controller. This is a +# Presenter (ICP) and Source (ICS) in two small units directly +# connected to each other with no routing layer. +-- +# The sources have a configurable IRQ priority set a set of ICS +# registers in the source units. +-- +# The source ids start at 16 for int_level_in(0) and go up from +# there (ie int_level_in(1) is source id 17). XXX Make a generic +-- +# The presentation layer will pick an interupt that is more +# favourable than the current CPPR and present it via the XISR and +# send an interrpt to the processor (via e_out). This may not be the +# highest priority interrupt currently presented (which is allowed +# via XICS) +-- +""" +from nmigen import Elaboratable, Module, Signal, Cat, Const, Record +from nmutil.iocontrol import RecordObject +from nmigen.cli import rtlil +from soc.minerva.wishbone import make_wb_layout + + +class ICS2ICP(RecordObject): + """ + # Level interrupts only, ICS just keeps prsenting the + # highest priority interrupt. Once handling edge, something + # smarter involving handshake & reject support will be needed + """ + def __init__(self, name): + super().__init__(name=name) + self.src = Signal(4, reset_less=True) + self.pri = Signal(8, reset_less=True) + +# hardwire the hardware IRQ priority +HW_PRIORITY = Const(0x80, 8) + +# 8 bit offsets for each presentation +XIRR_POLL = 0x00 +XIRR = 0x04 +RESV0 = 0x08 +MFRR = 0x0c + + +class RegInternal(RecordObject): + def __init__(self, name=None): + super().__init__(name=name) + self.xisr = Signal(24) + self.cppr = Signal(8) + self.mfrr = Signal(8, reset=0xff) # mask everything on reset + self.irq = Signal(1) + self.wb_rd_data = Signal(32) + self.wb_ack = Signal(1) + + +def bswap(v): + return Cat(v[24:32], v[16:24], v[8:16], v[0:8]) + + +class XICS_ICP(Elaboratable): + + def __init__(self): + class Spec: pass + spec = Spec() + spec.addr_wid = 30 + spec.mask_wid = 4 + spec.reg_wid = 32 + self.bus = Record(make_wb_layout(spec)) + self.ics_i = ICS2ICP("ics_i") + self.core_irq_o = Signal() + + def elaborate(self, platform): + m = Module() + comb, sync = m.d.comb, m.d.sync + + r = RegInternal() + r_next = RegInternal() + + sync += r.eq(r_next) + # We delay core_irq_out by a cycle to help with timing + sync += self.core_irq_o.eq(r.irq) + + comb += self.bus.dat_w.eq(r.wb_rd_data) + comb += self.bus.ack.eq(r.wb_ack) + + v = RegInternal() + xirr_accept_rd = Signal() + + be_in = Signal(32) + be_out = Signal(32) + + pending_priority = Signal(8) + + comb += v.eq(r) + comb += v.wb_ack.eq(0) + + comb += xirr_accept_rd.eq(0) + + comb += be_in.eq(bswap(self.bus.dat_r)) + comb += be_out.eq(0) + + with m.If(self.bus.cyc & self.bus.stb): + comb += v.wb_ack.eq(1) # always ack + with m.If(self.bus.we): # write + # writes to both XIRR are the same + with m.Switch( self.bus.adr[:8]): + with m.Case(XIRR_POLL): + # report "ICP XIRR_POLL write"; + comb += v.cppr.eq(be_in[24:32]) + with m.Case(XIRR): + comb += v.cppr.eq(be_in[24:32]) + with m.If(self.bus.sel == 0xf): # # 4 byte + #report "ICP XIRR write word (EOI) :" & \ + # to_hstring(be_in); + pass + with m.Elif(self.bus.sel == 0x1): # 1 byte + #report "ICP XIRR write byte (CPPR):" & \ + #to_hstring(be_in(31 downto 24)); + pass + with m.Else(): + #report "ICP XIRR UNSUPPORTED write ! sel=" & \ + # to_hstring(wb_in.sel); + pass + with m.Case(MFRR ): + comb += v.mfrr.eq(be_in[24:32]) + with m.If(self.bus.sel == 0xf): # # 4 byte + # report "ICP MFRR write word:" & to_hstring(be_in); + pass + with m.Elif(self.bus.sel == 0x1): # 1 byte + # report "ICP MFRR write byte:" & \ + # to_hstring(be_in(31 downto 24)); + pass + with m.Else(): + # report "ICP MFRR UNSUPPORTED write ! sel=" & \ + # to_hstring(wb_in.sel); + pass + + with m.Else(): # read + + with m.Switch( self.bus.adr[:8]): + with m.Case(XIRR_POLL): + # report "ICP XIRR_POLL read"; + comb += be_out.eq(r.xisr & r.cppr ) + with m.Case(XIRR): + # report "ICP XIRR read"; + comb += be_out.eq(Cat(r.xisr, r.cppr)) + with m.If(self.bus.sel == 0xf): # # 4 byte + comb += xirr_accept_rd.eq(1) + with m.Case(MFRR): + # report "ICP MFRR read"; + comb += be_out.eq(r.mfrr) + + comb += pending_priority.eq(0xff) + comb += v.xisr.eq(0x0) + comb += v.irq.eq(0x0) + + with m.If(self.ics_i.pri != 0xff): + comb += v.xisr.eq(Cat(self.ics_i.src, Const(0x00001))) + comb += pending_priority.eq(self.ics_i.pri) + + # Check MFRR + with m.If(r.mfrr < pending_priority): + # special XICS MFRR IRQ source number + comb += v.xisr.eq(Const(0x000002)) + comb += pending_priority.eq(r.mfrr) + + # Accept the interrupt + with m.If(xirr_accept_rd): + #report "XICS: ICP ACCEPT" & + # " cppr:" & to_hstring(r.cppr) & + # " xisr:" & to_hstring(r.xisr) & + # " mfrr:" & to_hstring(r.mfrr); + comb += v.cppr.eq(pending_priority) + + comb += v.wb_rd_data.eq(bswap(be_out)) + + with m.If(pending_priority < v.cppr): + with m.If(~r.irq): + #report "IRQ set"; + pass + comb += v.irq.eq(1) + with m.Elif(r.irq): + #report "IRQ clr"; + pass + + comb += r_next.eq(v) + + return m + + def __iter__(self): + for field in self.bus.fields.values(): + yield field + yield from self.ics_i + yield self.core_irq_o + + def ports(self): + return list(self) + + +""" +end architecture behaviour; + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library work; +use work.common.all; +use work.wishbone_types.all; + +entity xics_ics is + generic ( + SRC_NUM : integer range 1 to 256 := 16; + PRIO_BITS : integer range 1 to 8 := 8 + ); + port ( + clk : in std_logic; + rst : in std_logic; + + wb_in : in wb_io_master_out; + wb_out : out wb_io_slave_out; + + int_level_in : in std_ulogic_vector(SRC_NUM - 1 downto 0); + icp_out : out ics_to_icp_t + ); +end xics_ics; + +architecture rtl of xics_ics is + + subtype pri_t is std_ulogic_vector(PRIO_BITS-1 downto 0); + type xive_t is record + pri : pri_t; + end record; + constant pri_masked : pri_t := (others => '1'); + + type xive_array_t is array(0 to SRC_NUM-1) of xive_t; + signal xives : xive_array_t; + + signal wb_valid : std_ulogic; + signal reg_idx : integer range 0 to SRC_NUM - 1; + signal icp_out_next : ics_to_icp_t; + signal int_level_l : std_ulogic_vector(SRC_NUM - 1 downto 0); + + function bswap(v : in std_ulogic_vector(31 downto 0)) return std_ulogic_vector is + variable r : std_ulogic_vector(31 downto 0); + begin + r( 7 downto 0) := v(31 downto 24); + r(15 downto 8) := v(23 downto 16); + r(23 downto 16) := v(15 downto 8); + r(31 downto 24) := v( 7 downto 0); + return r; + end function; + + function get_config return std_ulogic_vector is + variable r: std_ulogic_vector(31 downto 0); + begin + r := (others => '0'); + r(23 downto 0) := std_ulogic_vector(to_unsigned(SRC_NUM, 24)); + r(27 downto 24) := std_ulogic_vector(to_unsigned(PRIO_BITS, 4)); + return r; + end function; + + function prio_pack(pri8: std_ulogic_vector(7 downto 0)) return pri_t is + begin + return pri8(PRIO_BITS-1 downto 0); + end function; + + function prio_unpack(pri: pri_t) return std_ulogic_vector is + variable r : std_ulogic_vector(7 downto 0); + begin + if pri = pri_masked then + r := x"ff"; + else + r := (others => '0'); + r(PRIO_BITS-1 downto 0) := pri; + end if; + return r; + end function; + + +# Register map + # 0 : Config + # 4 : Debug/diagnostics + # 800 : XIVE0 + # 804 : XIVE1 ... + -- + # Config register format: + -- + # 23.. 0 : Interrupt base (hard wired to 16) + # 27.. 24 : #prio bits (1..8) + -- + # XIVE register format: + -- + # 31 : input bit (reflects interrupt input) + # 30 : reserved + # 29 : P (mirrors input for now) + # 28 : Q (not implemented in this version) + # 30 .. : reserved + # 19 .. 8 : target (not implemented in this version) + # 7 .. 0 : prio/mask + + signal reg_is_xive : std_ulogic; + signal reg_is_config : std_ulogic; + signal reg_is_debug : std_ulogic; + +begin + + assert SRC_NUM = 16 report "Fixup address decode with log2"; + + reg_is_xive <= wb_in.adr(11); + reg_is_config <= '1' when wb_in.adr(11 downto 0) = x"000" else '0'; + reg_is_debug <= '1' when wb_in.adr(11 downto 0) = x"004" else '0'; + + # Register index XX FIXME: figure out bits from SRC_NUM + reg_idx <= to_integer(unsigned(wb_in.adr(5 downto 2))); + + # Latch interrupt inputs for timing + int_latch: process(clk) + begin + if rising_edge(clk) then + int_level_l <= int_level_in; + end if; + end process; + + # We don't stall. Acks are sent by the read machine one cycle + # after a request, but we can handle one access per cycle. + wb_out.stall <= '0'; + wb_valid <= wb_in.cyc and wb_in.stb; + + # Big read mux. This could be replaced by a slower state + # machine iterating registers instead if timing gets tight. + reg_read: process(clk) + variable be_out : std_ulogic_vector(31 downto 0); + begin + if rising_edge(clk) then + be_out := (others => '0'); + + if reg_is_xive = '1' then + be_out := int_level_l(reg_idx) & + '0' & + int_level_l(reg_idx) & + '0' & + x"00000" & + prio_unpack(xives(reg_idx).pri); + elsif reg_is_config = '1' then + be_out := get_config; + elsif reg_is_debug = '1' then + be_out := x"00000" & icp_out_next.src & icp_out_next.pri; + end if; + wb_out.dat <= bswap(be_out); + wb_out.ack <= wb_valid; + end if; + end process; + + # Register write machine + reg_write: process(clk) + variable be_in : std_ulogic_vector(31 downto 0); + begin + # Byteswapped input + be_in := bswap(wb_in.dat); + + if rising_edge(clk) then + if rst = '1' then + for i in 0 to SRC_NUM - 1 loop + xives(i) <= (pri => pri_masked); + end loop; + elsif wb_valid = '1' and wb_in.we = '1' then + if reg_is_xive then + # TODO: When adding support for other bits, make sure to + # properly implement wb_in.sel to allow partial writes. + xives(reg_idx).pri <= prio_pack(be_in(7 downto 0)); + report "ICS irq " & integer'image(reg_idx) & + " set to:" & to_hstring(be_in(7 downto 0)); + end if; + end if; + end if; + end process; + + # generate interrupt. This is a simple combinational process, + # potentially wasteful in HW for large number of interrupts. + -- + # could be replaced with iterative state machines and a message + # system between ICSs' (plural) and ICP incl. reject etc... + -- + irq_gen_sync: process(clk) + begin + if rising_edge(clk) then + icp_out <= icp_out_next; + end if; + end process; + + irq_gen: process(all) + variable max_idx : integer range 0 to SRC_NUM-1; + variable max_pri : pri_t; + + # A more favored than b ? + function a_mf_b(a: pri_t; b: pri_t) return boolean is + variable a_i : unsigned(PRIO_BITS-1 downto 0); + variable b_i : unsigned(PRIO_BITS-1 downto 0); + begin + a_i := unsigned(a); + b_i := unsigned(b); + report "a_mf_b a=" & to_hstring(a) & + " b=" & to_hstring(b) & + " r=" & boolean'image(a < b); + return a_i < b_i; + end function; + begin + # XXX FIXME: Use a tree + max_pri := pri_masked; + max_idx := 0; + for i in 0 to SRC_NUM - 1 loop + if int_level_l(i) = '1' and a_mf_b(xives(i).pri, max_pri) then + max_pri := xives(i).pri; + max_idx := i; + end if; + end loop; + if max_pri /= pri_masked then + report "MFI: " & integer'image(max_idx) & " pri=" & to_hstring(prio_unpack(max_pri)); + end if; + icp_out_next.src <= std_ulogic_vector(to_unsigned(max_idx, 4)); + icp_out_next.pri <= prio_unpack(max_pri); + end process; + +end architecture rtl; +""" + +def test_xics_icp(): + + dut = XICS_ICP() + vl = rtlil.convert(dut, ports=dut.ports()) + with open("test_xics_icp.il", "w") as f: + f.write(vl) + + #run_simulation(dut, ldst_sim(dut), vcd_name='test_ldst_regspec.vcd') + + +if __name__ == '__main__': + test_xics_icp() + -- 2.30.2