code wiki / _hdl_build / rv64im_min_sim_smoke.nx
rv64im_min_sim_smoke.nx source
↩ module page · 151 lines · 6021 B
1// rv64im_min_sim_smoke.nx -- end-to-end smoke for the RV64IM-min sim.
2//
3// Hand-codes a tiny RV64IM program that writes "OK\n" via the UART
4// MMIO + poweroffs via the SiFive finisher. Runs it on the
5// simulator (which ties decoder + ALU + regfile + CSR + CLINT + UART
6// together) and asserts the captured UART byte stream matches
7// "OK\n".
8//
9// This is the first integration test that exercises every module
10// shipped in nishi-silicon. If this passes, the path from
11// "NishiHDL DSL declared" to "RV64IM program executes" is proven --
12// the remaining steps to FPGA bring-up are: ELF loader, kernel
13// boot, then nishi-synth + Yosys.
14//
15// Hand-coded program (RV64IM, position-independent, starts at PC):
16// lui a0, 0x10000 ; a0 = 0x10000000 (UART base)
17// addi a1, zero, 'O' ; a1 = 0x4F
18// sb a1, 0(a0) ; UART THR <- 'O'
19// addi a1, zero, 'K' ; a1 = 0x4B
20// sb a1, 0(a0) ; UART THR <- 'K'
21// addi a1, zero, '\n' ; a1 = 0x0A
22// sb a1, 0(a0) ; UART THR <- '\n'
23// lui a0, 0x100 ; a0 = 0x100000 (finisher base)
24// lui a1, 0x5 ; a1 = 0x5000
25// addi a1, a1, 0x555 ; a1 = 0x5555
26// sw a1, 0(a0) ; finisher <- 0x5555 -> sim halts
27//
28// Encoded as 32-bit little-endian instruction words below.
29
30import "nx_syscalls.nx"
31import "nishi_hdl_primitives.nx"
32import "rv64im_min_decoder.nx"
33import "rv64im_min_alu.nx"
34import "rv64im_min_regfile.nx"
35import "rv64im_min_csr.nx"
36import "rv64im_min_clint.nx"
37import "rv64im_min_uart.nx"
38import "rv64im_min_sim.nx"
39
40const SMOKE_MEM_BASE: i64 = 0x80000000
41const SMOKE_MEM_SIZE: i64 = 4096
42const SMOKE_TX_CAP: i64 = 256
43
44// Helper: write a 32-bit value little-endian to a byte buffer.
45func smoke_write32(buf: *u8, off: i64, value: i64) -> i64 {
46 buf[off] = (value & 0xff) as u8
47 buf[off + 1] = ((value >> 8) & 0xff) as u8
48 buf[off + 2] = ((value >> 16) & 0xff) as u8
49 buf[off + 3] = ((value >> 24) & 0xff) as u8
50 return 0
51}
52
53// Hand-encoded RV64IM "write OK\n then poweroff" program.
54// 11 instructions x 4 bytes = 44 bytes of .text.
55func smoke_load_program(mem: *u8) -> i64 {
56 // 00: lui a0, 0x10000 (a0 = 0x10000000)
57 // opcode=0x37 rd=10 imm=0x10000
58 // imm[31:12]=0x10000 -> shifted into [31:12]
59 // encoding: imm[31:12] << 12 | rd << 7 | 0x37
60 // = 0x10000000 | 0x00000500 | 0x37 = 0x10000537
61 smoke_write32(mem, 0, 0x10000537)
62
63 // 04: addi a1, zero, 0x4F ('O')
64 // opcode=0x13 rd=11 funct3=0 rs1=0 imm=0x04F
65 // encoding: imm[11:0] << 20 | rs1 << 15 | funct3 << 12 | rd << 7 | 0x13
66 // = (0x04F << 20) | (0 << 15) | (0 << 12) | (11 << 7) | 0x13
67 // = 0x04F00000 | 0x00000580 | 0x13 = 0x04F00593
68 smoke_write32(mem, 4, 0x04f00593)
69
70 // 08: sb a1, 0(a0)
71 // opcode=0x23 funct3=0 rs1=10 rs2=11 imm=0
72 // imm[11:5]=0 imm[4:0]=0
73 // encoding: imm[11:5] << 25 | rs2 << 20 | rs1 << 15 | funct3 << 12 | imm[4:0] << 7 | 0x23
74 // = 0 | (11 << 20) | (10 << 15) | 0 | 0 | 0x23
75 // = 0x00B00000 | 0x00050000 | 0x23 = 0x00B50023
76 smoke_write32(mem, 8, 0x00b50023)
77
78 // 0C: addi a1, zero, 0x4B ('K')
79 // = (0x04B << 20) | (11 << 7) | 0x13 = 0x04B00593
80 smoke_write32(mem, 12, 0x04b00593)
81
82 // 10: sb a1, 0(a0)
83 smoke_write32(mem, 16, 0x00b50023)
84
85 // 14: addi a1, zero, 0x0A ('\n')
86 // = (0x00A << 20) | (11 << 7) | 0x13 = 0x00A00593
87 smoke_write32(mem, 20, 0x00a00593)
88
89 // 18: sb a1, 0(a0)
90 smoke_write32(mem, 24, 0x00b50023)
91
92 // 1C: lui a0, 0x100 (a0 = 0x100000, finisher base)
93 // = (0x100 << 12) | (10 << 7) | 0x37 = 0x00100537
94 smoke_write32(mem, 28, 0x00100537)
95
96 // 20: lui a1, 0x5 (a1 = 0x5000)
97 // = (0x5 << 12) | (11 << 7) | 0x37 = 0x000055B7
98 smoke_write32(mem, 32, 0x000055b7)
99
100 // 24: addi a1, a1, 0x555 (a1 = 0x5555)
101 // = (0x555 << 20) | (11 << 15) | 0 | (11 << 7) | 0x13
102 // = 0x55500000 | 0x00058000 | 0x00000580 | 0x13 = 0x55558593
103 smoke_write32(mem, 36, 0x55558593)
104
105 // 28: sw a1, 0(a0) (finisher write -> sim halts)
106 // opcode=0x23 funct3=2 rs1=10 rs2=11 imm=0
107 // = 0 | (11 << 20) | (10 << 15) | (2 << 12) | 0 | 0x23
108 // = 0x00B00000 | 0x00050000 | 0x00002000 | 0x23 = 0x00B52023
109 smoke_write32(mem, 40, 0x00b52023)
110
111 return 0
112}
113
114func main() -> i64 {
115 // ----- allocate device backings -----
116 let rf_storage: *i64 = (sys_mmap(8 * NX_RV64IM_RF_N_REGS)) as *i64
117 let csr_storage: *i64 = (sys_mmap(8 * NX_CSR_SLOT_N)) as *i64
118 let clint_storage: *i64 = (sys_mmap(8 * NX_CLINT_SLOT_N)) as *i64
119 let uart_storage: *i64 = (sys_mmap(8 * NX_UART_SLOT_N)) as *i64
120 let mem: *u8 = sys_mmap(SMOKE_MEM_SIZE)
121 let tx_buf: *u8 = sys_mmap(SMOKE_TX_CAP)
122
123 let rf: *NxRv64imRegfile = (sys_mmap(64)) as *NxRv64imRegfile
124 let csr: *NxRv64imCsrFile = (sys_mmap(64)) as *NxRv64imCsrFile
125 let clint: *NxClint = (sys_mmap(64)) as *NxClint
126 let uart: *NxUart = (sys_mmap(64)) as *NxUart
127 let sim: *NxRv64imSim = (sys_mmap(128)) as *NxRv64imSim
128
129 nx_rv64im_rf_init(rf, rf_storage)
130 nx_rv64im_csr_init(csr, csr_storage, 0)
131 nx_clint_init(clint, clint_storage)
132 nx_uart_init(uart, uart_storage, tx_buf, SMOKE_TX_CAP)
133 nx_rv64im_sim_init(sim, rf, csr, clint, uart,
134 SMOKE_MEM_BASE, mem, SMOKE_MEM_SIZE, 0)
135
136 smoke_load_program(mem)
137
138 // ----- run the program -----
139 let max_steps: i64 = 100
140 nx_rv64im_sim_run(sim, max_steps)
141
142 // ----- assert -----
143 if sim.halted != 1 { return 1 } // didn't halt cleanly
144 if nx_uart_tx_count(uart) != 3 { return 2 } // expected 3 bytes
145 if (tx_buf[0] & 0xff) != 0x4f { return 3 } // 'O'
146 if (tx_buf[1] & 0xff) != 0x4b { return 4 } // 'K'
147 if (tx_buf[2] & 0xff) != 0x0a { return 5 } // '\n'
148 if sim.steps != 11 { return 6 } // 11 instructions
149
150 return 0
151}