diff --git a/hdl/hazard3_csr.v b/hdl/hazard3_csr.v
index e1c94fb..0a026ee 100644
--- a/hdl/hazard3_csr.v
+++ b/hdl/hazard3_csr.v
@@ -495,22 +495,22 @@ always @ (*) begin
 	MSTATUS: if (CSR_M_MANDATORY || CSR_M_TRAP) begin
 		decode_match = match_mrw;
 		rdata = {
-			1'b0,         // Never any dirty state besides GPRs
-			8'd0,         // (WPRI)
-			1'b0,         // TSR (Trap SRET), tied 0 if no S mode.
-			1'b0,         // TW (Timeout Wait), tied 0 if only M mode.
-			1'b0,         // TVM (trap virtual memory), tied 0 if no S mode.
-			1'b0,         // MXR (Make eXecutable Readable), tied 0 if not S mode.
-			1'b0,         // SUM, tied 0, we have no S or U mode
-			mstatus_mprv, // MPRV (modify privilege)
-			4'd0,         // XS, FS always "off" (no extension state to clear!)
-			{2{m_mode}},  // MPP (M-mode previous privilege), only M and U supported
-			2'd0,         // (WPRI)
-			1'b0,         // SPP, tied 0 if S mode not supported
-			mstatus_mpie, // Previous interrupt enable
-			3'd0,         // No S, U
-			mstatus_mie,  // Interrupt enable
-			3'd0          // No S, U
+			1'b0,             // Never any dirty state besides GPRs
+			8'd0,             // (WPRI)
+			1'b0,             // TSR (Trap SRET), tied 0 if no S mode.
+			1'b0,             // TW (Timeout Wait), tied 0 if only M mode.
+			1'b0,             // TVM (trap virtual memory), tied 0 if no S mode.
+			1'b0,             // MXR (Make eXecutable Readable), tied 0 if not S mode.
+			1'b0,             // SUM, tied 0, we have no S or U mode
+			mstatus_mprv,     // MPRV (modify privilege)
+			4'd0,             // XS, FS always "off" (no extension state to clear!)
+			{2{mstatus_mpp}}, // MPP (M-mode previous privilege), only M and U supported
+			2'd0,             // (WPRI)
+			1'b0,             // SPP, tied 0 if S mode not supported
+			mstatus_mpie,     // Previous interrupt enable
+			3'd0,             // No S, U
+			mstatus_mie,      // Interrupt enable
+			3'd0              // No S, U
 		};
 	end
 
diff --git a/test/sim/sw_testcases/csr_readable_umode.c b/test/sim/sw_testcases/csr_readable_umode.c
new file mode 100644
index 0000000..b4319f8
--- /dev/null
+++ b/test/sim/sw_testcases/csr_readable_umode.c
@@ -0,0 +1,348 @@
+#include "tb_cxxrtl_io.h"
+#include "hazard3_csr.h"
+
+// Check all implemented M-mode and D-mode CSRs are unreadable in U mode.
+// Check that an M-mode trap taken from U mode is able to access the M-mode
+// trap CSRs.
+
+// These are new (priv-1.12) and may not be recognised by the toolchain:
+#define mconfigptr 0xf15
+#define mstatush 0x310
+
+// Exceptions here are: medeleg, mideleg, tdata1, dcsr, dpc, dscratch1,
+// dscratch0, dmdata0 (custom). medeleg/mideleg are just a couple of
+// unimplemented registers sprinkled in for a sanity check, and the remainder
+// are D-mode registers.
+//
+// Note we permit reads but not writes to tselect, to work around a logic
+// error in openocd. Planning to implement triggers at some point, so this
+// oddity will vanish.
+
+/*EXPECTED-OUTPUT***************************************************************
+-> exception, mcause = 2, mpp = 0 // mvendorid
+CSR was f11
+-> exception, mcause = 2, mpp = 0 // marchid
+CSR was f12
+-> exception, mcause = 2, mpp = 0 // mimpid
+CSR was f13
+-> exception, mcause = 2, mpp = 0 // mhartid
+CSR was f14
+-> exception, mcause = 2, mpp = 0 // mconfigptr
+CSR was f15
+-> exception, mcause = 2, mpp = 0 // misa
+CSR was 301
+-> exception, mcause = 2, mpp = 0 // mstatus
+CSR was 300
+-> exception, mcause = 2, mpp = 0 // mstatush
+CSR was 310
+-> exception, mcause = 2, mpp = 0 // mie
+CSR was 304
+-> exception, mcause = 2, mpp = 0 // mip
+CSR was 344
+-> exception, mcause = 2, mpp = 0 // mtvec
+CSR was 305
+-> exception, mcause = 2, mpp = 0 // mscratch
+CSR was 340
+-> exception, mcause = 2, mpp = 0 // mepc
+CSR was 341
+-> exception, mcause = 2, mpp = 0 // mcause
+CSR was 342
+-> exception, mcause = 2, mpp = 0 // mtval
+CSR was 343
+-> exception, mcause = 2, mpp = 0 // mcounteren
+CSR was 306
+-> exception, mcause = 2, mpp = 0 // mcycle
+CSR was b00
+-> exception, mcause = 2, mpp = 0 // mcycleh
+CSR was b80
+-> exception, mcause = 2, mpp = 0 // minstret
+CSR was b02
+-> exception, mcause = 2, mpp = 0 // minstreth
+CSR was b82
+-> exception, mcause = 2, mpp = 0 // mphmcounter3
+CSR was b03
+-> exception, mcause = 2, mpp = 0 // ...
+CSR was b04
+-> exception, mcause = 2, mpp = 0
+CSR was b05
+-> exception, mcause = 2, mpp = 0
+CSR was b06
+-> exception, mcause = 2, mpp = 0
+CSR was b07
+-> exception, mcause = 2, mpp = 0
+CSR was b08
+-> exception, mcause = 2, mpp = 0
+CSR was b09
+-> exception, mcause = 2, mpp = 0
+CSR was b0a
+-> exception, mcause = 2, mpp = 0
+CSR was b0b
+-> exception, mcause = 2, mpp = 0
+CSR was b0c
+-> exception, mcause = 2, mpp = 0
+CSR was b0d
+-> exception, mcause = 2, mpp = 0
+CSR was b0e
+-> exception, mcause = 2, mpp = 0
+CSR was b0f
+-> exception, mcause = 2, mpp = 0
+CSR was b10
+-> exception, mcause = 2, mpp = 0
+CSR was b11
+-> exception, mcause = 2, mpp = 0
+CSR was b12
+-> exception, mcause = 2, mpp = 0
+CSR was b13
+-> exception, mcause = 2, mpp = 0
+CSR was b14
+-> exception, mcause = 2, mpp = 0
+CSR was b15
+-> exception, mcause = 2, mpp = 0
+CSR was b16
+-> exception, mcause = 2, mpp = 0
+CSR was b17
+-> exception, mcause = 2, mpp = 0
+CSR was b18
+-> exception, mcause = 2, mpp = 0
+CSR was b19
+-> exception, mcause = 2, mpp = 0
+CSR was b1a
+-> exception, mcause = 2, mpp = 0
+CSR was b1b
+-> exception, mcause = 2, mpp = 0
+CSR was b1c
+-> exception, mcause = 2, mpp = 0
+CSR was b1d
+-> exception, mcause = 2, mpp = 0
+CSR was b1e
+-> exception, mcause = 2, mpp = 0 // ... mhpmcounter31
+CSR was b1f
+-> exception, mcause = 2, mpp = 0 // mhpmcounter3h
+CSR was b83
+-> exception, mcause = 2, mpp = 0 // ...
+CSR was b84
+-> exception, mcause = 2, mpp = 0
+CSR was b85
+-> exception, mcause = 2, mpp = 0
+CSR was b86
+-> exception, mcause = 2, mpp = 0
+CSR was b87
+-> exception, mcause = 2, mpp = 0
+CSR was b88
+-> exception, mcause = 2, mpp = 0
+CSR was b89
+-> exception, mcause = 2, mpp = 0
+CSR was b8a
+-> exception, mcause = 2, mpp = 0
+CSR was b8b
+-> exception, mcause = 2, mpp = 0
+CSR was b8c
+-> exception, mcause = 2, mpp = 0
+CSR was b8d
+-> exception, mcause = 2, mpp = 0
+CSR was b8e
+-> exception, mcause = 2, mpp = 0
+CSR was b8f
+-> exception, mcause = 2, mpp = 0
+CSR was b90
+-> exception, mcause = 2, mpp = 0
+CSR was b91
+-> exception, mcause = 2, mpp = 0
+CSR was b92
+-> exception, mcause = 2, mpp = 0
+CSR was b93
+-> exception, mcause = 2, mpp = 0
+CSR was b94
+-> exception, mcause = 2, mpp = 0
+CSR was b95
+-> exception, mcause = 2, mpp = 0
+CSR was b96
+-> exception, mcause = 2, mpp = 0
+CSR was b97
+-> exception, mcause = 2, mpp = 0
+CSR was b98
+-> exception, mcause = 2, mpp = 0
+CSR was b99
+-> exception, mcause = 2, mpp = 0
+CSR was b9a
+-> exception, mcause = 2, mpp = 0
+CSR was b9b
+-> exception, mcause = 2, mpp = 0
+CSR was b9c
+-> exception, mcause = 2, mpp = 0
+CSR was b9d
+-> exception, mcause = 2, mpp = 0
+CSR was b9e
+-> exception, mcause = 2, mpp = 0 // ... mhpmcounter31h
+CSR was b9f                       // followed by U-mode counters, which shouldn't trap...
+-> exception, mcause = 2, mpp = 0 // mcountinhibit
+CSR was 320
+-> exception, mcause = 2, mpp = 0 // mhpmevent3
+CSR was 323
+-> exception, mcause = 2, mpp = 0 // tselect
+CSR was 7a0
+-> exception, mcause = 2, mpp = 0 // tdata1
+CSR was 7a1
+-> exception, mcause = 2, mpp = 0 // dcsr
+CSR was 7b0
+-> exception, mcause = 2, mpp = 0 // dpc
+CSR was 7b1
+-> exception, mcause = 2, mpp = 0 // dscratch0
+CSR was 7b2
+-> exception, mcause = 2, mpp = 0 // dscratch1
+CSR was 7b3
+-> exception, mcause = 2, mpp = 0 // hazard3 dmdata0
+CSR was bff
+-> exception, mcause = 2, mpp = 0 // hazard3 meie0
+CSR was be0
+-> exception, mcause = 2, mpp = 0 // hazard3 meip0
+CSR was fe0
+-> exception, mcause = 2, mpp = 0 // hazard3 mlei
+CSR was fe4
+-> exception, mcause = 3, mpp = 0 // This is the ebreak that ends the test
+
+*******************************************************************************/
+
+// This function is run in U mode. It returns to a trampoline that ebreaks to M mode.
+void read_all_csrs() {
+	(void)read_csr(mvendorid);
+	(void)read_csr(marchid);
+	(void)read_csr(mimpid);
+	(void)read_csr(mhartid);
+	(void)read_csr(mconfigptr);
+	(void)read_csr(misa);
+
+	(void)read_csr(mstatus);
+	(void)read_csr(mstatush);
+	(void)read_csr(mie);
+	(void)read_csr(mip);
+	(void)read_csr(mtvec);
+	(void)read_csr(mscratch);
+	(void)read_csr(mepc);
+	(void)read_csr(mcause);
+	(void)read_csr(mtval);
+	(void)read_csr(mcounteren);
+
+	(void)read_csr(mcycle);
+	(void)read_csr(mcycleh);
+	(void)read_csr(minstret);
+	(void)read_csr(minstreth);
+
+	(void)read_csr(mhpmcounter3);
+	(void)read_csr(mhpmcounter4);
+	(void)read_csr(mhpmcounter5);
+	(void)read_csr(mhpmcounter6);
+	(void)read_csr(mhpmcounter7);
+	(void)read_csr(mhpmcounter8);
+	(void)read_csr(mhpmcounter9);
+	(void)read_csr(mhpmcounter10);
+	(void)read_csr(mhpmcounter11);
+	(void)read_csr(mhpmcounter12);
+	(void)read_csr(mhpmcounter13);
+	(void)read_csr(mhpmcounter14);
+	(void)read_csr(mhpmcounter15);
+	(void)read_csr(mhpmcounter16);
+	(void)read_csr(mhpmcounter17);
+	(void)read_csr(mhpmcounter18);
+	(void)read_csr(mhpmcounter19);
+	(void)read_csr(mhpmcounter20);
+	(void)read_csr(mhpmcounter21);
+	(void)read_csr(mhpmcounter22);
+	(void)read_csr(mhpmcounter23);
+	(void)read_csr(mhpmcounter24);
+	(void)read_csr(mhpmcounter25);
+	(void)read_csr(mhpmcounter26);
+	(void)read_csr(mhpmcounter27);
+	(void)read_csr(mhpmcounter28);
+	(void)read_csr(mhpmcounter29);
+	(void)read_csr(mhpmcounter30);
+	(void)read_csr(mhpmcounter31);
+
+	(void)read_csr(mhpmcounter3h);
+	(void)read_csr(mhpmcounter4h);
+	(void)read_csr(mhpmcounter5h);
+	(void)read_csr(mhpmcounter6h);
+	(void)read_csr(mhpmcounter7h);
+	(void)read_csr(mhpmcounter8h);
+	(void)read_csr(mhpmcounter9h);
+	(void)read_csr(mhpmcounter10h);
+	(void)read_csr(mhpmcounter11h);
+	(void)read_csr(mhpmcounter12h);
+	(void)read_csr(mhpmcounter13h);
+	(void)read_csr(mhpmcounter14h);
+	(void)read_csr(mhpmcounter15h);
+	(void)read_csr(mhpmcounter16h);
+	(void)read_csr(mhpmcounter17h);
+	(void)read_csr(mhpmcounter18h);
+	(void)read_csr(mhpmcounter19h);
+	(void)read_csr(mhpmcounter20h);
+	(void)read_csr(mhpmcounter21h);
+	(void)read_csr(mhpmcounter22h);
+	(void)read_csr(mhpmcounter23h);
+	(void)read_csr(mhpmcounter24h);
+	(void)read_csr(mhpmcounter25h);
+	(void)read_csr(mhpmcounter26h);
+	(void)read_csr(mhpmcounter27h);
+	(void)read_csr(mhpmcounter28h);
+	(void)read_csr(mhpmcounter29h);
+	(void)read_csr(mhpmcounter30h);
+	(void)read_csr(mhpmcounter31h);
+
+	(void)read_csr(cycle);
+	(void)read_csr(cycleh);
+	(void)read_csr(instret);
+	(void)read_csr(instreth);
+
+	(void)read_csr(mcountinhibit);
+	(void)read_csr(mhpmevent3);
+	(void)read_csr(tselect);
+	(void)read_csr(tdata1);
+	(void)read_csr(dcsr);
+	(void)read_csr(dpc);
+	(void)read_csr(dscratch0);
+	(void)read_csr(dscratch1);
+	(void)read_csr(hazard3_csr_dmdata0);
+	(void)read_csr(hazard3_csr_meie0);
+	(void)read_csr(hazard3_csr_meip0);
+	(void)read_csr(hazard3_csr_mlei);
+}
+
+void __attribute__((naked)) ebreak_trampoline() {
+	asm ("ebreak");
+}
+
+int main() {
+	// Make counters accessible to U mode
+	write_csr(mcounteren, -1u);
+
+	// Enter function in U mode, return via ebreak trampoline
+	write_csr(mstatus, read_csr(mstatus) & ~0x1800u);
+	write_csr(mepc, &read_all_csrs);
+	asm (
+		"la ra, ebreak_trampoline\n"
+		"mret\n"
+	);
+
+	return 0;
+}
+
+void __attribute__((interrupt)) handle_exception() {
+	uint32_t mcause = read_csr(mcause);
+	tb_printf("-> exception, mcause = %u, mpp = %u\n", mcause, read_csr(mstatus) >> 11 & 0x3u);
+	write_csr(mcause, 0);
+	if (mcause == 3) {
+		// ebreak -> end of test
+		tb_exit(0);
+	}
+
+	uint32_t mepc = read_csr(mepc);
+	if ((*(uint16_t*)mepc & 0x3) == 0x3) {
+		tb_printf("CSR was %03x\n", *(uint16_t*)(mepc + 2) >> 4);
+		mepc += 4;
+	}
+	else {
+		tb_puts("Exception on 16-bit instruction?!\n");
+		tb_exit(-1);
+	}
+	write_csr(mepc, mepc);
+}