cores/cpu: add cv32e40p
authorPiotr Binkowski <pbinkowski@antmicro.com>
Tue, 19 May 2020 15:05:25 +0000 (17:05 +0200)
committerPiotr Binkowski <pbinkowski@antmicro.com>
Wed, 20 May 2020 11:46:37 +0000 (13:46 +0200)
litex/soc/cores/cpu/__init__.py
litex/soc/cores/cpu/cv32e40p/__init__.py [new file with mode: 0644]
litex/soc/cores/cpu/cv32e40p/boot-helper.S [new file with mode: 0644]
litex/soc/cores/cpu/cv32e40p/core.py [new file with mode: 0644]
litex/soc/cores/cpu/cv32e40p/crt0.S [new file with mode: 0644]
litex/soc/cores/cpu/cv32e40p/csr-defs.h [new file with mode: 0644]
litex/soc/cores/cpu/cv32e40p/irq.h [new file with mode: 0644]
litex/soc/cores/cpu/cv32e40p/system.h [new file with mode: 0644]

index d861bf07ed48113226e4001727427d8362f833ec..12e2d0574bd4635612ed55a3c0c5008287088b29 100644 (file)
@@ -57,6 +57,7 @@ from litex.soc.cores.cpu.minerva import Minerva
 from litex.soc.cores.cpu.vexriscv import VexRiscv
 from litex.soc.cores.cpu.rocket import RocketRV64
 from litex.soc.cores.cpu.blackparrot import BlackParrotRV64
+from litex.soc.cores.cpu.cv32e40p import CV32E40P
 
 CPUS = {
     # None
@@ -76,6 +77,7 @@ CPUS = {
     "picorv32"    : PicoRV32,
     "minerva"     : Minerva,
     "vexriscv"    : VexRiscv,
+    "cv32e40p"    : CV32E40P,
 
     # RISC-V 64-bit
     "rocket"      : RocketRV64,
diff --git a/litex/soc/cores/cpu/cv32e40p/__init__.py b/litex/soc/cores/cpu/cv32e40p/__init__.py
new file mode 100644 (file)
index 0000000..5bb2122
--- /dev/null
@@ -0,0 +1 @@
+from litex.soc.cores.cpu.cv32e40p.core import CV32E40P
diff --git a/litex/soc/cores/cpu/cv32e40p/boot-helper.S b/litex/soc/cores/cpu/cv32e40p/boot-helper.S
new file mode 100644 (file)
index 0000000..6dd74aa
--- /dev/null
@@ -0,0 +1,4 @@
+.section    .text, "ax", @progbits
+.global     boot_helper
+boot_helper:
+       jr x13
diff --git a/litex/soc/cores/cpu/cv32e40p/core.py b/litex/soc/cores/cpu/cv32e40p/core.py
new file mode 100644 (file)
index 0000000..9f8d6ff
--- /dev/null
@@ -0,0 +1,417 @@
+#!/usr/bin/env python3
+
+import os
+import re
+
+from migen import *
+from migen.fhdl.specials import Tristate
+
+from litex import get_data_mod
+from litex.soc.interconnect import wishbone, stream
+from litex.soc.interconnect.csr import *
+from litex.soc.cores.cpu import CPU, CPU_GCC_TRIPLE_RISCV32
+
+
+CPU_VARIANTS = ["standard", "full"]
+
+GCC_FLAGS = {
+    #                               /-------- Base ISA
+    #                               |/------- Hardware Multiply + Divide
+    #                               ||/----- Atomics
+    #                               |||/---- Compressed ISA
+    #                               ||||/--- Single-Precision Floating-Point
+    #                               |||||/-- Double-Precision Floating-Point
+    #                               imacfd
+    "standard":         "-march=rv32imc    -mabi=ilp32 ",
+    "full":             "-march=rv32imfc   -mabi=ilp32 ",
+}
+
+obi_layout = [
+    ("req", 1),
+    ("gnt", 1),
+    ("addr", 32),
+    ("we", 1),
+    ("be", 4),
+    ("wdata", 32),
+    ("rvalid", 1),
+    ("rdata", 32),
+]
+
+apb_layout = [
+    ("paddr", 32),
+    ("pwdata", 32),
+    ("pwrite", 1),
+    ("psel", 1),
+    ("penable", 1),
+    ("prdata", 32),
+    ("pready", 1),
+    ("pslverr", 1),
+]
+
+trace_layout = [
+    ("ivalid", 1),
+    ("iexception", 1),
+    ("interrupt", 1),
+    ("cause", 5),
+    ("tval", 32),
+    ("priv", 3),
+    ("iaddr", 32),
+    ("instr", 32),
+    ("compressed", 1),
+]
+
+def add_manifest_sources(platform, manifest):
+    basedir = get_data_mod("cpu", "cv32e40p").data_location
+    with open(os.path.join(basedir, manifest), 'r') as f:
+        for l in f:
+            res = re.search('\$\{DESIGN_RTL_DIR\}/(.+)', l)
+            if res and not re.match('//', l):
+                if re.match('\+incdir\+', l):
+                    platform.add_verilog_include_path(os.path.join(basedir, 'rtl', res.group(1)))
+                else:
+                    platform.add_source(os.path.join(basedir, 'rtl', res.group(1)))
+
+
+class OBI2Wishbone(Module):
+    def __init__(self, obi, wb):
+        dat_r_d = Signal().like(wb.dat_r)
+        addr_d = Signal().like(obi.addr)
+        ack_d = Signal()
+
+        self.sync += [
+            dat_r_d.eq(wb.dat_r),
+            ack_d.eq(wb.ack),
+            addr_d.eq(obi.addr),
+        ]
+
+        self.comb += [
+            wb.adr.eq(obi.addr[2:32]),
+            wb.stb.eq(obi.req & (~ack_d)),
+            wb.dat_w.eq(obi.wdata),
+            wb.cyc.eq(obi.req),
+            wb.sel.eq(obi.be),
+            wb.we.eq(obi.we),
+            obi.gnt.eq(wb.ack & (addr_d == obi.addr)),
+            obi.rvalid.eq(ack_d),
+            obi.rdata.eq(dat_r_d),
+        ]
+
+class Wishbone2OBI(Module):
+    def __init__(self, wb, obi):
+        self.submodules.fsm = fsm = FSM(reset_state="IDLE")
+        fsm.act("IDLE",
+            If(wb.cyc & wb.stb,
+                obi.req.eq(1),
+                NextState("ACK"),
+            )
+        )
+        fsm.act("ACK",
+            wb.ack.eq(1),
+            NextState("IDLE"),
+        )
+
+        self.comb += [
+            obi.we.eq(wb.we),
+            obi.be.eq(wb.sel),
+            obi.addr.eq(Cat(0, 0, wb.adr)),
+            obi.wdata.eq(wb.dat_w),
+            wb.dat_r.eq(obi.rdata),
+        ]
+
+class Wishbone2APB(Module):
+    def __init__(self, wb, apb):
+        self.submodules.fsm = fsm = FSM(reset_state="IDLE")
+        fsm.act("IDLE",
+            If(wb.cyc & wb.stb,
+                NextState("ACK"),
+            )
+        )
+        fsm.act("ACK",
+            apb.penable.eq(1),
+            wb.ack.eq(1),
+            NextState("IDLE"),
+        )
+
+        self.comb += [
+            apb.paddr.eq(Cat(0, 0, wb.adr)),
+            apb.pwrite.eq(wb.we),
+            apb.psel.eq(1),
+            apb.pwdata.eq(wb.dat_w),
+            wb.dat_r.eq(apb.prdata),
+        ]
+
+class TraceCollector(Module, AutoCSR):
+    def __init__(self, trace_depth=16384):
+        self.bus  = bus  = wishbone.Interface()
+        self.sink = sink = stream.Endpoint([("data", 32)])
+
+        clear   = Signal()
+        enable  = Signal()
+        pointer = Signal(32)
+
+        self._enable  = CSRStorage()
+        self._clear   = CSRStorage()
+        self._pointer = CSRStatus(32)
+
+        mem = Memory(32, trace_depth)
+        rd_port = mem.get_port()
+        wr_port = mem.get_port(write_capable=True)
+
+        self.specials += rd_port, wr_port, mem
+
+        self.sync += [
+            # wishbone
+            bus.ack.eq(0),
+            If(bus.cyc & bus.stb & ~bus.ack, bus.ack.eq(1)),
+            # trace core
+            If(clear, pointer.eq(0)).Else(
+                If(sink.ready & sink.valid, pointer.eq(pointer+1)),
+            ),
+        ]
+
+        self.comb += [
+            # wishbone
+            rd_port.adr.eq(bus.adr),
+            bus.dat_r.eq(rd_port.dat_r),
+            # trace core
+            wr_port.adr.eq(pointer),
+            wr_port.dat_w.eq(sink.data),
+            wr_port.we.eq(sink.ready & sink.valid),
+            sink.ready.eq(enable & (pointer < trace_depth)),
+            # csrs
+            enable.eq(self._enable.storage),
+            clear.eq(self._clear.storage),
+            self._pointer.status.eq(pointer),
+        ]
+
+class TraceDebugger(Module):
+    def __init__(self):
+        self.bus      = wishbone.Interface()
+        self.source   = source   = stream.Endpoint([("data", 32)])
+        self.trace_if = trace_if = Record(trace_layout)
+
+        apb = Record(apb_layout)
+
+        self.submodules.bus_conv = Wishbone2APB(self.bus, apb)
+
+        self.trace_params = dict(
+            i_clk_i=ClockSignal(),
+            i_rst_ni=~ResetSignal(),
+            i_test_mode_i=0,
+            # cpu interface
+            i_ivalid_i=trace_if.ivalid,
+            i_iexception_i=trace_if.iexception,
+            i_interrupt_i=trace_if.interrupt,
+            i_cause_i=trace_if.cause,
+            i_tval_i=trace_if.tval,
+            i_priv_i=trace_if.priv,
+            i_iaddr_i=trace_if.iaddr,
+            i_instr_i=trace_if.instr,
+            i_compressed_i=trace_if.compressed,
+            # apb interface
+            i_paddr_i=apb.paddr,
+            i_pwdata_i=apb.pwdata,
+            i_pwrite_i=apb.pwrite,
+            i_psel_i=apb.psel,
+            i_penable_i=apb.penable,
+            o_prdata_o=apb.prdata,
+            o_pready_o=apb.pready,
+            o_pslverr_o=apb.pslverr,
+            # data output
+            o_packet_word_o=source.data,
+            o_packet_word_valid_o=source.valid,
+            i_grant_i=source.ready,
+        )
+        self.specials += Instance("trace_debugger", **self.trace_params)
+
+    @staticmethod
+    def add_sources(platform):
+        add_manifest_sources(platform, "cv32e40p_trace_manifest.flist")
+
+class DebugModule(Module):
+    jtag_layout = [
+        ("tck", 1),
+        ("tms", 1),
+        ("trst", 1),
+        ("tdi", 1),
+        ("tdo", 1),
+    ]
+    def __init__(self, pads=None):
+        self.dmbus = wishbone.Interface()
+        self.sbbus = wishbone.Interface()
+        dmbus = Record(obi_layout)
+        sbbus = Record(obi_layout)
+
+        self.submodules.sbbus_conv = OBI2Wishbone(sbbus, self.sbbus)
+        self.submodules.dmbus_conv = Wishbone2OBI(self.dmbus, dmbus)
+
+        self.debug_req = Signal()
+        self.ndmreset = Signal()
+
+        tdo_i = Signal()
+        tdo_o = Signal()
+        tdo_oe = Signal()
+
+        if pads is None:
+            pads = Record(self.jtag_layout)
+
+        self.pads = pads
+
+        self.specials += Tristate(pads.tdo, tdo_o, tdo_oe, tdo_i)
+
+        self.dm_params = dict(
+            i_clk=ClockSignal(),
+            i_rst_n=~ResetSignal(),
+            o_ndmreset=self.ndmreset,
+            o_debug_req=self.debug_req,
+            # slave bus
+            i_dm_req=dmbus.req,
+            i_dm_we=dmbus.we,
+            i_dm_addr=dmbus.addr,
+            i_dm_be=dmbus.be,
+            i_dm_wdata=dmbus.wdata,
+            o_dm_rdata=dmbus.rdata,
+            # master bus
+            o_sb_req=sbbus.req,
+            o_sb_addr=sbbus.addr,
+            o_sb_we=sbbus.we,
+            o_sb_wdata=sbbus.wdata,
+            o_sb_be=sbbus.be,
+            i_sb_gnt=sbbus.gnt,
+            i_sb_rvalid=sbbus.rvalid,
+            i_sb_rdata=sbbus.rdata,
+            # jtag
+            i_tck=pads.tck,
+            i_tms=pads.tms,
+            i_trst_n=pads.trst,
+            i_tdi=pads.tdi,
+            o_tdo=tdo_o,
+            o_tdo_oe=tdo_oe,
+        )
+
+        self.comb += [
+            dmbus.gnt.eq(dmbus.req),
+            dmbus.rvalid.eq(dmbus.gnt),
+        ]
+
+        self.specials += Instance("dm_wrap", **self.dm_params)
+
+    @staticmethod
+    def add_sources(platform):
+        add_manifest_sources(platform, "cv32e40p_dm_manifest.flist")
+
+
+class CV32E40P(CPU):
+    name                 = "cv32e40p"
+    human_name           = "CV32E40P"
+    data_width           = 32
+    endianness           = "little"
+    gcc_triple           = CPU_GCC_TRIPLE_RISCV32
+    linker_output_format = "elf32-littleriscv"
+    nop                  = "nop"
+    io_regions           = {0x80000000: 0x80000000} # origin, length
+
+    has_fpu              = ["full"]
+
+    @property
+    def gcc_flags(self):
+        flags = GCC_FLAGS[self.variant]
+        flags += "-D__cv32e40p__ "
+
+        return flags
+
+    def __init__(self, platform, variant="standard"):
+        assert variant in CPU_VARIANTS, "Unsupported variant %s" % variant
+        self.platform     = platform
+        self.variant      = variant
+        self.reset        = Signal()
+        self.ibus         = wishbone.Interface()
+        self.dbus         = wishbone.Interface()
+        self.periph_buses = [self.ibus, self.dbus]
+        self.memory_buses = []
+        self.interrupt    = Signal(15)
+
+        ibus = Record(obi_layout)
+        dbus = Record(obi_layout)
+
+        self.submodules.ibus_conv = OBI2Wishbone(ibus, self.ibus)
+        self.submodules.dbus_conv = OBI2Wishbone(dbus, self.dbus)
+
+        self.comb += [
+            ibus.we.eq(0),
+            ibus.be.eq(1111),
+        ]
+
+        self.cpu_params = dict(
+            i_clk_i=ClockSignal(),
+            i_rst_ni=~ResetSignal(),
+            i_clock_en_i=1,
+            i_test_en_i=0,
+            i_fregfile_disable_i=0,
+            i_core_id_i=0,
+            i_cluster_id_i=0,
+            # ibus
+            o_instr_req_o=ibus.req,
+            i_instr_gnt_i=ibus.gnt,
+            i_instr_rvalid_i=ibus.rvalid,
+            o_instr_addr_o=ibus.addr,
+            i_instr_rdata_i=ibus.rdata,
+            # dbus
+            o_data_req_o=dbus.req,
+            i_data_gnt_i=dbus.gnt,
+            i_data_rvalid_i=dbus.rvalid,
+            o_data_we_o=dbus.we,
+            o_data_be_o=dbus.be,
+            o_data_addr_o=dbus.addr,
+            o_data_wdata_o=dbus.wdata,
+            i_data_rdata_i=dbus.rdata,
+            # apu
+            i_apu_master_gnt_i=0,
+            i_apu_master_valid_i=0,
+            # irq
+            i_irq_sec_i=0,
+            i_irq_software_i=0,
+            i_irq_external_i=0,
+            i_irq_fast_i=self.interrupt,
+            i_irq_nmi_i=0,
+            i_irq_fastx_i=0,
+            # debug
+            i_debug_req_i=0,
+            # cpu control
+            i_fetch_enable_i=1,
+        )
+
+        # add verilog sources
+        add_manifest_sources(platform, 'cv32e40p_manifest.flist')
+
+        if variant in self.has_fpu:
+            self.cpu_params.update(p_FPU=1)
+            add_manifest_sources(platform, 'cv32e40p_fpu_manifest.flist')
+
+    def add_debug_module(self, dm):
+        self.cpu_params.update(i_debug_req_i=dm.debug_req)
+        self.cpu_params.update(i_rst_ni=~(ResetSignal() | dm.ndmreset))
+
+    def add_trace_core(self, trace):
+        trace_if = trace.trace_if
+
+        self.cpu_params.update(
+            o_ivalid_o=trace_if.ivalid,
+            o_iexception_o=trace_if.iexception,
+            o_interrupt_o=trace_if.interrupt,
+            o_cause_o=trace_if.cause,
+            o_tval_o=trace_if.tval,
+            o_priv_o=trace_if.priv,
+            o_iaddr_o=trace_if.iaddr,
+            o_instr_o=trace_if.instr,
+            o_compressed_o=trace_if.compressed,
+        )
+
+    def set_reset_address(self, reset_address):
+        assert not hasattr(self, "reset_address")
+        self.reset_address = reset_address
+        self.cpu_params.update(i_boot_addr_i=Signal(32, reset=reset_address))
+
+    def do_finalize(self):
+        assert hasattr(self, "reset_address")
+        self.specials += Instance("riscv_core", **self.cpu_params)
diff --git a/litex/soc/cores/cpu/cv32e40p/crt0.S b/litex/soc/cores/cpu/cv32e40p/crt0.S
new file mode 100644 (file)
index 0000000..30b372a
--- /dev/null
@@ -0,0 +1,112 @@
+.global main
+.global isr
+.global _start
+
+_start:
+  j crt_init
+  nop
+  nop
+  nop
+  nop
+  nop
+  nop
+  nop
+
+.balign 256
+
+vector_table:
+  j trap_entry # 0 unused
+  j trap_entry # 1 unused
+  j trap_entry # 2 unused
+  j trap_entry # 3 software
+  j trap_entry # 4 unused
+  j trap_entry # 5 unused
+  j trap_entry # 6 unused
+  j trap_entry # 7 timer
+  j trap_entry # 8 unused
+  j trap_entry # 9 unused
+  j trap_entry # 10 unused
+  j trap_entry # 11 external
+  j trap_entry # 12 unused
+  j trap_entry # 13 unused
+  j trap_entry # 14 unused
+  j trap_entry # 15 unused
+  j trap_entry # 16 firq0
+  j trap_entry # 17 firq1
+  j trap_entry # 18 firq2
+  j trap_entry # 19 firq3
+  j trap_entry # 20 firq4
+  j trap_entry # 21 firq5
+  j trap_entry # 22 firq6
+  j trap_entry # 23 firq7
+  j trap_entry # 24 firq8
+  j trap_entry # 25 firq9
+  j trap_entry # 26 firq10
+  j trap_entry # 27 firq11
+  j trap_entry # 28 firq12
+  j trap_entry # 29 firq13
+  j trap_entry # 30 firq14
+  j trap_entry # 31 unused
+
+.global  trap_entry
+trap_entry:
+  sw x1,  - 1*4(sp)
+  sw x5,  - 2*4(sp)
+  sw x6,  - 3*4(sp)
+  sw x7,  - 4*4(sp)
+  sw x10, - 5*4(sp)
+  sw x11, - 6*4(sp)
+  sw x12, - 7*4(sp)
+  sw x13, - 8*4(sp)
+  sw x14, - 9*4(sp)
+  sw x15, -10*4(sp)
+  sw x16, -11*4(sp)
+  sw x17, -12*4(sp)
+  sw x28, -13*4(sp)
+  sw x29, -14*4(sp)
+  sw x30, -15*4(sp)
+  sw x31, -16*4(sp)
+  addi sp,sp,-16*4
+  call isr
+  lw x1 , 15*4(sp)
+  lw x5,  14*4(sp)
+  lw x6,  13*4(sp)
+  lw x7,  12*4(sp)
+  lw x10, 11*4(sp)
+  lw x11, 10*4(sp)
+  lw x12,  9*4(sp)
+  lw x13,  8*4(sp)
+  lw x14,  7*4(sp)
+  lw x15,  6*4(sp)
+  lw x16,  5*4(sp)
+  lw x17,  4*4(sp)
+  lw x28,  3*4(sp)
+  lw x29,  2*4(sp)
+  lw x30,  1*4(sp)
+  lw x31,  0*4(sp)
+  addi sp,sp,16*4
+  mret
+  .text
+
+
+crt_init:
+  la sp, _fstack + 4
+  la a0, vector_table
+  csrw mtvec, a0
+
+bss_init:
+  la a0, _fbss
+  la a1, _ebss
+bss_loop:
+  beq a0,a1,bss_done
+  sw zero,0(a0)
+  add a0,a0,4
+  j bss_loop
+bss_done:
+
+  li a0, 0x7FFF0880  //7FFF0880 enable timer + external interrupt + fast interrupt sources (until mstatus.MIE is set, they will never trigger an interrupt)
+  csrw mie,a0
+
+  j main
+infinit_loop:
+  j infinit_loop
diff --git a/litex/soc/cores/cpu/cv32e40p/csr-defs.h b/litex/soc/cores/cpu/cv32e40p/csr-defs.h
new file mode 100644 (file)
index 0000000..d98e8df
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef CSR_DEFS__H
+#define CSR_DEFS__H
+
+#define CSR_MSTATUS_MIE 0x8
+
+#define CSR_IRQ_MASK 0xBC0
+#define CSR_IRQ_PENDING 0xFC0
+
+#define CSR_DCACHE_INFO 0xCC0
+
+#endif /* CSR_DEFS__H */
diff --git a/litex/soc/cores/cpu/cv32e40p/irq.h b/litex/soc/cores/cpu/cv32e40p/irq.h
new file mode 100644 (file)
index 0000000..f1dd4c2
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef __IRQ_H
+#define __IRQ_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <system.h>
+#include <generated/csr.h>
+
+static inline unsigned int irq_getie(void)
+{
+    return (csrr(mstatus) & CSR_MSTATUS_MIE) != 0;
+}
+
+static inline void irq_setie(unsigned int ie)
+{
+       if(ie) csrs(mstatus,CSR_MSTATUS_MIE); else csrc(mstatus,CSR_MSTATUS_MIE);
+}
+
+static inline unsigned int irq_getmask(void)
+{
+    return 0; // FIXME
+}
+
+static inline void irq_setmask(unsigned int mask)
+{
+    // FIXME
+}
+
+static inline unsigned int irq_pending(void)
+{
+    return 0;// FIXME
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __IRQ_H */
diff --git a/litex/soc/cores/cpu/cv32e40p/system.h b/litex/soc/cores/cpu/cv32e40p/system.h
new file mode 100644 (file)
index 0000000..b787320
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef __SYSTEM_H
+#define __SYSTEM_H
+
+#include <csr-defs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+__attribute__((unused)) static void flush_cpu_icache(void)
+{
+    // FIXME
+       asm volatile("nop");
+}
+
+__attribute__((unused)) static void flush_cpu_dcache(void)
+{
+    // FIXME
+       asm volatile("nop");
+}
+
+void flush_l2_cache(void);
+
+void busy_wait(unsigned int ms);
+
+#define csrr(reg) ({ unsigned long __tmp; \
+  asm volatile ("csrr %0, " #reg : "=r"(__tmp)); \
+  __tmp; })
+
+#define csrw(reg, val) ({ \
+  if (__builtin_constant_p(val) && (unsigned long)(val) < 32) \
+       asm volatile ("csrw " #reg ", %0" :: "i"(val)); \
+  else \
+       asm volatile ("csrw " #reg ", %0" :: "r"(val)); })
+
+#define csrs(reg, bit) ({ \
+  if (__builtin_constant_p(bit) && (unsigned long)(bit) < 32) \
+       asm volatile ("csrrs x0, " #reg ", %0" :: "i"(bit)); \
+  else \
+       asm volatile ("csrrs x0, " #reg ", %0" :: "r"(bit)); })
+
+#define csrc(reg, bit) ({ \
+  if (__builtin_constant_p(bit) && (unsigned long)(bit) < 32) \
+       asm volatile ("csrrc x0, " #reg ", %0" :: "i"(bit)); \
+  else \
+       asm volatile ("csrrc x0, " #reg ", %0" :: "r"(bit)); })
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SYSTEM_H */