-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworker.mlog.jinja
More file actions
2790 lines (2255 loc) · 84.1 KB
/
worker.mlog.jinja
File metadata and controls
2790 lines (2255 loc) · 84.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#%# linked buildings
#% set UART_START_LINK = 16
#% set REGISTERS = 'cell1'
#% set LABELS = 'cell2'
#% set CSRS = 'processor17'
#% set INCR = 'processor18'
#% set CONFIG = 'processor19'
#% set CSR_LABELS = 'processor20'
#% set CONTROLLER = 'processor21'
#% set ERROR_OUTPUT = 'message1'
#%# constants
#% set LN_2 = '0.6931471805599453'
#%# Sv32
#% set PAGESIZE = 2**12
#% set LEVELS = 2
#% set PTESIZE = 4
#%# macros
#% macro start_assert_length(n)
#{- n
#directive start_assert_length {{ n }}
#%- endmacro
reset:
setrate 1000
# load config
read MEMORY_X {{CONFIG}} "MEMORY_X"
read MEMORY_Y {{CONFIG}} "MEMORY_Y"
read MEMORY_WIDTH {{CONFIG}} "MEMORY_WIDTH"
read PROGRAM_ROM_ICACHE_SIZE {{CONFIG}} "PROGRAM_ROM_ICACHE_SIZE"
read ROM_SIZE {{CONFIG}} "ROM_SIZE"
read RAM_SIZE {{CONFIG}} "RAM_SIZE"
read RAM_END {{CONFIG}} "RAM_END"
read ICACHE_END {{CONFIG}} "ICACHE_END"
read RAM_ICACHE_OFFSET {{CONFIG}} "RAM_ICACHE_OFFSET"
read UART_FIFO_MODULO {{CONFIG}} "UART_FIFO_MODULO"
op idiv RAM_START_PROC ROM_SIZE {{ROM_PROC_BYTES}}
wait 1e-5
# @counter is moved here when the next worker starts executing
next_tick:
# fetch saved variables from the previous worker
read prev_proc {{CONTROLLER}} "prev_proc"
#directive start_fetch
#% macro fetch_variables()
#% do reset_locals()
read {{local_variable()}} prev_proc "{{ local_variable(1) }}"
read {{local_variable()}} prev_proc "{{ local_variable(2) }}"
read {{local_variable()}} prev_proc "{{ local_variable(3) }}"
read {{local_variable()}} prev_proc "{{ local_variable(4) }}"
read {{local_variable()}} prev_proc "{{ local_variable(5) }}"
read {{local_variable()}} prev_proc "{{ local_variable(6) }}"
read {{local_variable()}} prev_proc "{{ local_variable(7) }}"
read {{local_variable()}} prev_proc "{{ local_variable(8) }}"
read nonlocal1 prev_proc "nonlocal1"
read nonlocal2 prev_proc "nonlocal2"
read __etext prev_proc "__etext"
read _address prev_proc "_address"
read address prev_proc "address"
read breakpoint_address prev_proc "breakpoint_address"
read csr_mcycle prev_proc "csr_mcycle"
read csr_mie prev_proc "csr_mie"
read csr_minstret prev_proc "csr_minstret"
read csr_mip prev_proc "csr_mip"
read csr_mstatus prev_proc "csr_mstatus"
read csr_mtime prev_proc "csr_mtime"
read csr_mtimecmp prev_proc "csr_mtimecmp"
read csr_mtimecmph prev_proc "csr_mtimecmph"
read csr_mtimeh prev_proc "csr_mtimeh"
read csr_stimecmp prev_proc "csr_stimecmp"
read csr_stimecmph prev_proc "csr_stimecmph"
read effective_privilege_mode prev_proc "effective_privilege_mode"
read icache_ram prev_proc "icache_ram"
read icache_var prev_proc "icache_var"
read imm prev_proc "imm"
read length prev_proc "length"
read mcause prev_proc "mcause"
read mtval prev_proc "mtval"
read next_pc prev_proc "next_pc"
read op_id prev_proc "op_id"
read pc prev_proc "pc"
read privilege_mode prev_proc "privilege_mode"
read ram prev_proc "ram"
read rd prev_proc "rd"
read rd_id prev_proc "rd_id"
read result prev_proc "result"
read rs1 prev_proc "rs1"
read rs1_id prev_proc "rs1_id"
read rs2 prev_proc "rs2"
read rs2_id prev_proc "rs2_id"
read state prev_proc "state"
read uart_flags prev_proc "uart_flags"
read value prev_proc "value"
read variable prev_proc "variable"
read ret prev_proc "ret"
read ret2 prev_proc "ret2"
read ret3 prev_proc "ret3"
read ret4 prev_proc "ret4"
#% endmacro
#{ fetch_variables()
#directive end_fetch
jump reset notEqual state "running"
# tell the next worker to fetch the hart state from us
write @this {{CONTROLLER}} "prev_proc"
# these variables don't need to be saved because this should only ever run at the start of a tick
# increment mcycle
op add csr_mcycle csr_mcycle 1
jump reset__no_overflow lessThan csr_mcycle 0x100000000
# overflow mcycle into mcycleh
# TODO: handle mcycleh overflow
set csr_mcycle 0
#directive push_saved __mcycleh
read __mcycleh {{CSRS}} "{{ 'mcycleh'|csr }}"
op add __mcycleh __mcycleh 1
write __mcycleh {{CSRS}} "{{ 'mcycleh'|csr }}"
#directive pop_saved __mcycleh
reset__no_overflow:
# if we just started executing, don't restore @counter
jump poll_interrupts equal prev_proc {{CONTROLLER}}
#directive push_saved __counter
# otherwise, jump to whatever instruction the previous worker would have executed next
read __counter prev_proc "@counter"
write %next_tick% prev_proc "@counter"
set @counter __counter
#directive pop_saved __counter
# check if software-manipulable interrupts should become pending
# this runs at the start of the first worker each tick, and at the start of the next worker if certain CSRs/MMRs are modified
poll_interrupts:
#% do reset_locals()
# poll -
# MEIP -
# MTIP -
# STIP -
op and csr_mip csr_mip 0b01111111111111111111011101011111
# machine external interrupt (UARTs)
op shr $uart_interrupts uart_flags 4
op and $uart_interrupts $uart_interrupts uart_flags
select $mip.meip notEqual $uart_interrupts 0 0b100000000000 0
op or csr_mip csr_mip $mip.meip
# machine timer interrupt
# fire if mtimeh > mtimecmph || (mtimeh == mtimecmph && mtime >= mtimecmp)
jump poll_interrupts__no_mti lessThan csr_mtimeh csr_mtimecmph
select $mip.mtip greaterThan csr_mtimeh csr_mtimecmph 0b10000000 0
select $mip.mtip greaterThanEq csr_mtime csr_mtimecmp 0b10000000 $mip.mtip
op or csr_mip csr_mip $mip.mtip
poll_interrupts__no_mti:
# supervisor timer interrupt
# fire if mtimeh > stimecmph || (mtimeh == stimecmph && mtime >= stimecmp)
jump poll_interrupts__no_sti lessThan csr_mtimeh csr_stimecmph
select $mip.stip greaterThan csr_mtimeh csr_stimecmph 0b100000 0
select $mip.stip greaterThanEq csr_mtime csr_stimecmp 0b100000 $mip.stip
op or csr_mip csr_mip $mip.stip
poll_interrupts__no_sti:
# check if interrupts should fire
# this runs at the start of the first worker each tick, if certain CSRs are modified, and after an xRET instruction
fire_interrupts:
#% set trap_locals = ["$$mxdeleg"]
#% do declare_locals(trap_locals)
op and csr_mip csr_mip 0b10111111111111111111111111111111 # clear interrupt_flags.fire
# fire interrupt i if bit i is set in both mip and mie
op and $interrupts csr_mip csr_mie
# if no interrupts would be fired, short-circuit
jump main equal $interrupts 0
# read mideleg for later
read $$mxdeleg {{CSRS}} "{{ 'mideleg'|csr }}"
# in U-mode, interrupts are always enabled
jump fire_interrupts__ok lessThan privilege_mode 0b01
# in S-mode or M-mode, interrupts are enabled if mstatus.xIE is 1
# conveniently, SIE is bit 1 and MIE is bit 3
# FIXME: this is incorrect, we should still take interrupts to M-mode if in S-mode with SIE=0
op shl $mstatus.xie 1 privilege_mode
op and $mstatus.xie $mstatus.xie csr_mstatus
jump main equal $mstatus.xie 0
# in M-mode, don't take delegated interrupts
jump fire_interrupts__ok lessThan privilege_mode 0b11
op not $mask $$mxdeleg
op and $interrupts $interrupts $mask
fire_interrupts__ok:
# interrupt priority order: MEI, MSI*, MTI, SEI, SSI, STI, LCOFI*
# * not implemented
# we can check consecutive interrupts i and j at the same time if i > j
# for example, if MEI (11) and MTI (7) are both set, then floor(log_2(0b100010000000)) = 11
# MEI -
# MTI -
op and mcause $interrupts 0b100010000000
jump interrupt notEqual mcause 0
# SEI -
# SSI -
op and mcause $interrupts 0b001000000010
jump interrupt notEqual mcause 0
# STI -
op and mcause $interrupts 0b000000100000
jump interrupt notEqual mcause 0
main:
# store the pc for the following instruction in a separate variable, so jumps and traps don't need to account for the pc being incremented at the end of an instruction
op add next_pc pc 4
# get the current instruction cache processor and variable
jump main__read_icache notEqual icache_var null
# convert virtual pc to physical address
# we check privilege_mode here because mstatus.MPRV does not affect instruction fetch
set address pc
set mcause 1 # instruction access fault
set mtval pc
op add ret3 @counter 1
jump translate_virtual_address lessThan privilege_mode 0b11
# fall back to slow instruction fetch/decode if not in valid icache range
# note: MLOGSYS enforces __etext <= ICACHE_SIZE and __etext <= PROGRAM_ROM_SIZE
jump main__access_rom_icache lessThan address __etext
jump main__slow_instruction_fetch lessThan address {{RAM_START}}
op add _address RAM_ICACHE_OFFSET address
jump main__access_ram_icache lessThan address ICACHE_END
main__slow_instruction_fetch:
# prevent executing from MMIO
op add ret @counter 1
select @counter greaterThanEq address RAM_END %trap% %load_word%
set address null
# IMPORTANT: remember to update this value if anything in access_icache or read_icache is changed
op add ret3 @counter {{# start_assert_length(10) }}
jump decode_value_in_result always
main__access_rom_icache:
# the icache is stored in RAM cells immediately after the end of RAM with the same density as regular memory
# if address in ROM: _address = RAM_SIZE + address
# else: _address = RAM_SIZE + PROGRAM_ROM_SIZE + address - RAM_START
# = RAM_ICACHE_OFFSET + address
op add _address RAM_SIZE address
main__access_ram_icache:
op add ret2 @counter 1
jump access_ram_raw always
set icache_ram ram
set icache_var variable
main__read_icache:
# load instruction and unpack arguments
read result icache_ram icache_var
read icache_var {{INCR}} icache_var
op and imm result 0xffffffff
op shr op_id result 47
#directive end_assert_length
# ret3 jumps here for the slow path
# we can't jump any later because decode doesn't fully isolate the register fields
# NOTE: FENCE assumes the icache payload is in result
op shr rd_id result 32
op and rd_id rd_id 0b11111
op shr rs1_id result 37
op and rs1_id rs1_id 0b11111
op shr rs2_id result 42
op and rs2_id rs2_id 0b11111
# read rs1 and rs2
# this wastes 1 instruction for I-type and 2 for U-type and J-type, but it saves a huge amount of space
read rs1 {{REGISTERS}} rs1_id
read rs2 {{REGISTERS}} rs2_id
jump main__breakpoint strictEqual pc breakpoint_address
jump main__no_breakpoint notEqual breakpoint_address "*"
main__breakpoint:
# the controller will increment @counter and set state to "running" when it's time to unpause
set state "breakpoint"
stop
main__no_breakpoint:
#% do reset_locals()
# jump to instruction handler
# op_id is in the range [-64, 63]
# and the instruction handlers in {{LABELS}} start at index 128
# so we need to add 64 + 128 = 192
op add $index op_id 192
read @counter {{LABELS}} $index
# this is a normal length for a label
end_instruction_with_rd_and_poll_interrupts:
op or csr_mip csr_mip 0b10000000000000000000000000000000 # set interrupt_flags.poll
# continue into end_instruction_with_rd_and_fire_interrupts
# this is too
end_instruction_with_rd_and_fire_interrupts:
op or csr_mip csr_mip 0b01000000000000000000000000000000 # set interrupt_flags.fire
# continue into end_instruction_with_rd
# most instructions with an output register jump here after completing successfully
# writes value rd to register rd_id if rd_id is not x0
end_instruction_with_rd:
jump end_instruction equal rd_id 0
write rd {{REGISTERS}} rd_id
# continue into end_instruction
# all instructions jump here after completing successfully
end_instruction:
#% do reset_locals()
# increment instret
op add csr_minstret csr_minstret 1
jump end_instruction_trap lessThan csr_minstret 0x100000000
# overflow instret
set csr_minstret 0
read $minstreth {{CSRS}} "{{ 'minstreth'|csr }}"
op add $minstreth $minstreth 1
write $minstreth {{CSRS}} "{{ 'minstreth'|csr }}"
end_instruction_trap:
# next_pc is set to pc + 4 at the start of main
# if we're in the icache, icache_var was already incremented
# if next_pc was modified, icache_var should have also been set to null
set pc next_pc
jump main lessThan csr_mip 0b01000000000000000000000000000000
jump fire_interrupts lessThan csr_mip 0b10000000000000000000000000000000
jump poll_interrupts always
# exceptions
default_xtvec_handler:
# if we set state before printing, atomicity should be guaranteed
set state "halt"
print "Trap taken to {{SYSCON}} (default trap vector), halting."
printflush {{ERROR_OUTPUT}}
jump reset always
interrupt:
#% do declare_locals(trap_locals)
# the highest bit set in mcause is the bit in (mip & mie) that triggered the interrupt
# so convert mcause to the actual trap code via floor(log_2(mcause)) = ln(mcause) // ln(2)
op log mcause mcause
op idiv mcause mcause {{LN_2}}
# set interrupt bit
op or mcause mcause 0x80000000
# continue into trap_without_mtval
trap_without_mtval:
set mtval 0
jump trap__check_mxdeleg greaterThanEq mcause 0x80000000
# continue into trap
trap:
#% do declare_locals(trap_locals)
# check medeleg
read $$mxdeleg {{CSRS}} "{{ 'medeleg'|csr }}"
trap__check_mxdeleg:
# don't trap from M-mode to S-mode
jump trap__machine greaterThan privilege_mode 0b01
# if not in M-mode, trap to S-mode if the corresponding mxdeleg bit is set, else trap to M-mode
# this is safe for interrupt mcause values: https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.19
op shr $$mxdeleg $$mxdeleg mcause
op and $$mxdeleg $$mxdeleg 0b1
jump trap__machine equal $$mxdeleg 0
#% do reset_locals()
# set sstatus.SPIE to sstatus.SIE, set sstatus.SIE to 0, and set sstatus.SPP to privilege_mode
op and $mstatus.xpie csr_mstatus 0b10
op shl $mstatus.xpp privilege_mode 8
op and csr_mstatus csr_mstatus 0b11111111111111111111111011011101
# set scause
write mcause {{CSRS}} "{{ 'scause'|csr }}"
# set stval
write mtval {{CSRS}} "{{ 'stval'|csr }}"
# set sepc to current pc
write pc {{CSRS}} "{{ 'sepc'|csr }}"
# we only support direct mode, so set next_pc to stvec
read next_pc {{CSRS}} "{{ 'stvec'|csr }}"
# trap into S-mode
set privilege_mode 0b01
jump trap__common always
trap__machine:
#% do reset_locals()
# set mstatus.MPIE to mstatus.MIE, set mstatus.MIE to 0, and set mstatus.MPP to privilege_mode
op and $mstatus.xpie csr_mstatus 0b1000
op shl $mstatus.xpp privilege_mode 11
op and csr_mstatus csr_mstatus 0b11111111111111111110011101110111
# set mcause
write mcause {{CSRS}} "{{ 'mcause'|csr }}"
# set mtval
write mtval {{CSRS}} "{{ 'mtval'|csr }}"
# set mepc to current pc
write pc {{CSRS}} "{{ 'mepc'|csr }}"
# we only support direct mode, so set next_pc to mtvec
read next_pc {{CSRS}} "{{ 'mtvec'|csr }}"
# trap into M-mode
set privilege_mode 0b11
trap__common:
op shl $mstatus.xpie $mstatus.xpie 4
op or csr_mstatus csr_mstatus $mstatus.xpie
op or csr_mstatus csr_mstatus $mstatus.xpp
# check for breakpoints
jump trap__breakpoint strictEqual pc breakpoint_address
jump trap__no_breakpoint notEqual breakpoint_address "*"
trap__breakpoint:
set state "trap_breakpoint"
stop
trap__no_breakpoint:
# update effective_privilege_mode and check for unhandled traps
# tail call into end_instruction_trap so we don't update instret
select ret equal next_pc {{SYSCON}} %default_xtvec_handler% %end_instruction_trap%
# continue into get_effective_privilege_mode_and_invalidate_icache
# update effective_privilege_mode
# we don't need to check the current privilege mode here because MRET/SRET clear MPRV if leaving M-mode
# this must stay directly after trap
get_effective_privilege_mode_and_invalidate_icache:
set icache_var null
get_effective_privilege_mode:
#% do reset_locals()
op shr $mstatus.mpp csr_mstatus 11
op and $mstatus.mpp $mstatus.mpp 0b11
op and $mstatus.mprv csr_mstatus 0b100000000000000000
# if MPRV=0, return privilege_mode
# if MPRV=1, return mstatus.MPP
select effective_privilege_mode equal $mstatus.mprv 0 privilege_mode $mstatus.mpp
set @counter ret
# helper functions
# apply Sv32 virtual address translation
# jumps to this function must be conditional on privilege_mode or effective_privilege_mode
# address is assumed to already be truncated to 32 bits
# mcause must contain the access fault code for the original access type (ie. 1, 5, or 7)
# mtval must contain the same value as address
# address, mcause, mtval -> address
# ret: ret3
# clobbers: locals, nonlocal2
translate_virtual_address:
#% do declare_locals("$$a")
# if this function is called, we know that the current effective privilege mode permits address translation
# so the only thing we need to check here is satp.MODE
read $satp {{CSRS}} "{{ 'satp'|csr }}"
jump translate_virtual_address__unchanged lessThan $satp 0x80000000
# begin virtual address translation algorithm
# PAGESIZE=2^12, LEVELS=2, PTESIZE=4
# step 1
# a = satp.ppn * PAGESIZE
op and $$a $satp 0x3fffff # satp.ppn
op mul $$a $$a {{PAGESIZE}}
# i = LEVELS - 1 = 1
# nonlocal2 = (i + 1) * 10
# i is stored in this form because it simplifies step 2 and doesn't really matter for other steps
# we use nonlocal2 because amo uses nonlocal1
set nonlocal2 20 # i=1
# step 2
translate_virtual_address__step_2:
# pte = load(a + va.vpn[i] * PTESIZE)
# = load(a + (va.vpn[i] << 2))
# = load(a + (va[y:x] << 2))
# = load(a + (va >> (x - 2))[11:2])
op shr address mtval nonlocal2
op and address address 0b111111111100
op add address address $$a
op add ret @counter 1
jump load_word always
# result = pte
# step 3
# if pte is not a pointer, we check pte.a here instead of step 7
# so pte is invalid if v=0, or if r=0 and w=1, or if (r=1 or x=1) and a=0
# a00xwrv
# a | x | w | r | v | index | valid?
# - | - | - | - | - | ----- | --------
# 0 | 0 | 0 | 0 | 0 | 0 | no (v=0)
# 0 | 0 | 0 | 0 | 1 | 1 | yes
# 0 | 0 | 0 | 1 | 0 | 2 | no (v=0)
# 0 | 0 | 0 | 1 | 1 | 3 | no (a=0)
# 0 | 0 | 1 | 0 | 0 | 4 | no (v=0)
# 0 | 0 | 1 | 0 | 1 | 5 | no (a=0)
# 0 | 0 | 1 | 1 | 0 | 6 | no (v=0)
# 0 | 0 | 1 | 1 | 1 | 7 | no (a=0)
# 0 | 1 | 0 | 0 | 0 | 8 | no (v=0)
# 0 | 1 | 0 | 0 | 1 | 9 | no (a=0)
# 0 | 1 | 0 | 1 | 0 | 10 | no (v=0)
# 0 | 1 | 0 | 1 | 1 | 11 | no (a=0)
# 0 | 1 | 1 | 0 | 0 | 12 | no (v=0)
# 0 | 1 | 1 | 0 | 1 | 13 | no (a=0)
# 0 | 1 | 1 | 1 | 0 | 14 | no (v=0)
# 0 | 1 | 1 | 1 | 1 | 15 | no (a=0)
# 1 | 0 | 0 | 0 | 0 | 64 | no (v=0)
# 1 | 0 | 0 | 0 | 1 | 65 | yes
# 1 | 0 | 0 | 1 | 0 | 66 | no (v=0)
# 1 | 0 | 0 | 1 | 1 | 67 | yes
# 1 | 0 | 1 | 0 | 0 | 68 | no (v=0)
# 1 | 0 | 1 | 0 | 1 | 69 | no (r=0)
# 1 | 0 | 1 | 1 | 0 | 70 | no (v=0)
# 1 | 0 | 1 | 1 | 1 | 71 | yes
# 1 | 1 | 0 | 0 | 0 | 72 | no (v=0)
# 1 | 1 | 0 | 0 | 1 | 73 | yes
# 1 | 1 | 0 | 1 | 0 | 74 | no (v=0)
# 1 | 1 | 0 | 1 | 1 | 75 | yes
# 1 | 1 | 1 | 0 | 0 | 76 | no (v=0)
# 1 | 1 | 1 | 0 | 1 | 77 | no (r=0)
# 1 | 1 | 1 | 1 | 0 | 78 | no (v=0)
# 1 | 1 | 1 | 1 | 1 | 79 | yes
#% set valid_indices = [1, 65, 67, 71, 73, 75, 79]
#% do reset_locals()
op and $a_x_w_r_v result 0b1001111
read $valid "{% for i in range(80) %}{{ (i in valid_indices)|int }}{% endfor %}" $a_x_w_r_v
jump translate_virtual_address__page_fault notEqual $valid 49 # ascii 1
# step 4
# if pte.r=1 or pte.x=1, this is a leaf PTE
op and $x_r result 0b1010
jump translate_virtual_address__step_5 notEqual $x_r 0
# otherwise, if nonlocal2 has been updated, then i-1 < 0, so raise a page fault exception
jump translate_virtual_address__page_fault notEqual nonlocal2 20
# otherwise, let a=pte.ppn * PAGESIZE, let i=i-1, and go to step 2
#% do declare_locals("$$a")
op and $$a result 0xfffffc00
op shl $$a $$a 2
set nonlocal2 10 # i=0
jump translate_virtual_address__step_2 always
# step 5
translate_virtual_address__step_5:
# effective_privilege_mode is either S (1) or U (0)
# SUCCEED IF
# execute:
# x == 1
# && (effective_privilege_mode == U ? u == 1 : u == 0)
# load:
# (r == 1 || MXR == 1 && x == 1)
# && (effective_privilege_mode == U ? u == 1 : (u == 0 || SUM == 1))
# store:
# w == 1
# && (effective_privilege_mode == U ? u == 1 : (u == 0 || SUM == 1))
# FAIL IF
# execute:
# x == 0
# || u == 0 && effective_privilege_mode == 0
# || u == 1 && effective_privilege_mode == 1
# load:
# r == 0 && (MXR == 0 || x == 0)
# || u == 0 && effective_privilege_mode == 0
# || u == 1 && effective_privilege_mode == 1 && SUM == 0
# store:
# w == 0
# || u == 0 && effective_privilege_mode == 0
# || u == 1 && effective_privilege_mode == 1 && SUM == 0
# prevent user mode from accessing non-user pages
#% do reset_locals()
op and $u_priv result 0b10000
op or $u_priv $u_priv effective_privilege_mode
jump translate_virtual_address__page_fault equal $u_priv 0
# prevent supervisor mode from executing user pages
jump translate_virtual_address__step_5__execute equal mcause 1 # execute
# prevent supervisor mode from reading/writing user pages if mstatus.SUM is not set
op and $mstatus.sum csr_mstatus 0b1000000000000000000
jump translate_virtual_address__step_5__sum notEqual $mstatus.sum 0
translate_virtual_address__step_5__execute:
jump translate_virtual_address__page_fault equal $u_priv 0b10001
translate_virtual_address__step_5__sum:
# check if the required rwx bits are set
# PTE: ...daguxwrv
# if we mask off g and u, we can use ASCII characters 0b0011___0
# type | mcause | MXR | PTE mask || index | char
# ------- | ------ | --- | -------- || ----- | ----
# execute | 1 | 0 | 0b1000 || 1 | '8' (0011 1000)
# execute | 1 | 1 | 0b1000 || 2 | '8' (0011 1000)
# load | 5 | 0 | 0b0010 || 5 | '2' (0011 0010)
# load | 5 | 1 | 0b1010 || 6 | ':' (0011 1010)
# store | 7 | 0 | 0b0100 || 7 | '4' (0011 0100)
# store | 7 | 1 | 0b0100 || 8 | '4' (0011 0100)
# extract fields from PTE
# this value is also used in step 7
#% do reset_locals()
# ------- DO NOT RESET LOCALS AFTER THIS LINE -------
op and $d_x_w_r result 0b10001110
# extract MXR bit
op shr $mstatus.mxr csr_mstatus 19
op and $mstatus.mxr $mstatus.mxr 0b1
# fetch the bitmask from the string as per the above table
op add $mask mcause $mstatus.mxr
read $mask "_88__2:44" $mask
# fail if NONE of the required bits are set in pte
# this is fine because none of the things we're checking here require more than one bit to be set
# in particular, if MXR is 1, loads succeed if r is 1 OR x is 1
op and $mask $d_x_w_r $mask
jump translate_virtual_address__page_fault equal $mask 0
# step 6
# skip this step if i == 0
# nonlocal2 == (i + 1) * 10
jump translate_virtual_address__step_7 equal nonlocal2 10
# if i > 0 and pte.ppn[i-1:0] != 0, this is a misaligned superpage
# in Sv32, i is either 1 or 0
# so we need to check pte.ppn[0]
op and $pte.ppn[0] result 0xffc00
jump translate_virtual_address__page_fault notEqual $pte.ppn[0] 0
# step 7
translate_virtual_address__step_7:
# we implement Svade for this step because it's simpler
# if pte.a=0, or if the original memory access is a store and pte.d=0, raise a page fault exception
# pte.a was checked in step 3
# fault if storing and pte.d=0
jump translate_virtual_address__step_8 notEqual mcause 7 # store/AMO
jump translate_virtual_address__page_fault lessThan $d_x_w_r 0b10000000
# ------- end critical locals section -------
# step 8
translate_virtual_address__step_8:
# move the pte.ppn fields into position
op shl result result 2
# raise an access fault exception if the resulting physical address will be wider than 32 bits
# TODO: is there any case where we *don't* want to do this?
jump trap greaterThan result 0xffffffff
# if i>0, this is a superpage
jump translate_virtual_address__step_8__superpage greaterThan nonlocal2 10
# pa.pgoff = va.pgoff
op and address mtval 0xfff
# pa.ppn = pte.ppn
op and result result 0xfffff000
op or address address result
set @counter ret3
translate_virtual_address__step_8__superpage:
# pa.pgoff = va.pgoff
# pa.ppn[0] = va.ppn[0]
op and address mtval 0x3fffff
# pa.ppn[1] = pte.ppn[1]
op and result result 0xffc00000
op or address address result
translate_virtual_address__unchanged:
set @counter ret3
translate_virtual_address__page_fault:
# we need to convert mcause from access fault to page fault
# instruction: 1 -> 12
# load: 5 -> 13
# store/AMO: 7 -> 15
# for load/store/AMO, page fault = access fault + 8
# for instruction, 1 + 8 = 9 < 12 < 13 < 15
# instruction: max(1 + 8, 12) = max(9, 12) = 12
# load: max(5 + 8, 12) = max(13, 12) = 13
# store/AMO: max(7 + 8, 12) = max(15, 12) = 15
op add mcause mcause 8
op max mcause mcause 12
jump trap always
# caller must ensure offset is 4-byte aligned and in uart range
# ret: ret2
access_uart:
#% set access_uart_locals = ["$$offset", "$$uart_shift", "$$uart", "$$rx_read", "$$rx_write", "$$tx_read", "$$tx_write", "$$next_tx_write"]
#% do declare_locals(access_uart_locals)
op sub $$offset $$offset {{UART_RX_OFFSET}}
op idiv $$uart_shift $$offset {{UART_MMIO_SIZE}}
op mod $$offset $$offset {{UART_MMIO_SIZE}}
op add $$offset $$offset {{UART_RX_OFFSET}}
op add $$uart $$uart_shift {{UART_START_LINK}}
getlink $$uart $$uart
read $$rx_read $$uart {{UART_RX_READ}}
read $$rx_write $$uart {{UART_RX_WRITE}}
read $$tx_read $$uart {{UART_TX_READ}}
read $$tx_write $$uart {{UART_TX_WRITE}}
op add $$next_tx_write $$tx_write 1
op mod $$next_tx_write $$next_tx_write UART_FIFO_MODULO
set @counter ret2
# loads the word from memory that contains the specified address
# mcause is required in order to raise the correct exception in case of access fault
# address, mcause, mtval -> result
# ret: ret
# clobbers: locals, ret2, value
load_word:
jump load_rom_word_unchecked lessThan address ROM_SIZE
load_ram_or_mmio_word:
jump load_mmio_word greaterThanEq address {{MMIO_START}}
jump trap lessThan address {{RAM_START}}
jump trap greaterThanEq address RAM_END
# continue into load_ram_word_unchecked
load_ram_word_unchecked:
# locate and read value from ram
op add ret2 @counter {{# start_assert_length(17) }}
# continue into access_ram
# helper function to find the ram proc and variable for a given address
# address -> ram, variable
# ret: ret2
access_ram:
#% do reset_locals()
# _address is named because it's also an input for lookup_variable
op sub _address address {{RAM_START}}
# _address -> ram, variable
access_ram_raw:
# we store 4 bytes in each value
op idiv _address _address 4
# get the ram proc containing this address
op idiv $ram_index _address {{RAM_PROC_VARS}}
op add $ram_index $ram_index RAM_START_PROC
op mod $ram_x $ram_index MEMORY_WIDTH
op add $ram_x $ram_x MEMORY_X
op idiv $ram_y $ram_index MEMORY_WIDTH
op add $ram_y $ram_y MEMORY_Y
getblock building ram $ram_x $ram_y
# get the variable within the ram proc containing this address
op mod _address _address {{RAM_PROC_VARS}}
# given a value 0 <= _address < RAM_PROC_VARS, resolve that variable in the lookup table
# this must stay directly after access_ram
# _address -> variable
# ret: ret2
# NOTE: modify_csr assumes this function uses one local
lookup_variable:
#% do reset_locals()
# the lookup is assumed to start at link 0, so we don't need to add an offset here
op idiv $lookup _address {{LOOKUP_PROC_SIZE}}
getlink $lookup $lookup
op mod variable _address {{LOOKUP_PROC_SIZE}}
lookup block variable variable
sensor variable variable @name
read variable $lookup variable
set @counter ret2
#directive end_assert_length
read value ram variable
# null is coerced to 0 by swap_endianness
# tail call, swap_endianness will jump to the ret value of load_word's caller
# converts a little endian 32-bit number to big endian, or vice versa
# https://stackoverflow.com/a/2182184
# this MUST stay directly after load_word
# value -> result
swap_endianness:
#% do reset_locals()
# byte 3 -> byte 0
op shr result value 24
op and result result 0xff
# byte 1 -> byte 2
op shl $tmp value 8
op and $tmp $tmp 0xff0000
op or result result $tmp
# byte 2 -> byte 1
op shr $tmp value 8
op and $tmp $tmp 0xff00
op or result result $tmp
# byte 0 -> byte 3
op shl $tmp value 24
op and $tmp $tmp 0xff000000
op or result result $tmp
set @counter ret
# loads a word from ROM
# address -> result
load_rom_word_unchecked:
#% do declare_locals("$$rom", "$$str_index")
# align to 4 bytes
op and $address address 0xfffffffc
# get rom proc data
op idiv $rom_index $address {{ROM_PROC_BYTES}}
op mod $$str_index $address {{ROM_PROC_BYTES}}
op mod $rom_x $rom_index MEMORY_WIDTH
op add $rom_x $rom_x MEMORY_X
op idiv $rom_y $rom_index MEMORY_WIDTH
op add $rom_y $rom_y MEMORY_Y
getblock building $$rom $rom_x $rom_y
read $$rom $$rom "v"
#% do declare_locals("$$rom", "$$str_index")
# read bytes of word
read $byte0 $$rom $$str_index
op add $$str_index $$str_index 1
read $byte1 $$rom $$str_index
op add $$str_index $$str_index 1
read $byte2 $$rom $$str_index
op add $$str_index $$str_index 1
read $$rom $$rom $$str_index # overwrite rom variable to avoid issues with the vars menu and block data size
# merge into big endian
op shl $byte1 $byte1 8
op shl $byte2 $byte2 16
op shl $$rom $$rom 24
op add result $byte0 $byte1
op add result result $byte2
op add result result $$rom
op sub result result {{# ROM_BYTE_OFFSET * (1 + 2**8 + 2**16 + 2**24) }}
op max result result 0 # if we're reading out of bounds, default to 0
set @counter ret
# address, mcause, mtval -> result
load_mmio_word:
#% do declare_locals(access_uart_locals)
# linux syscon driver does a read-modify-write, so the syscon needs to be readable
set result 0
jump load_mmio_word__ret equal address {{SYSCON}}
# the extra 3 at the start is in case we jump here from address translation
# in that case, address may be up to 34 bits
op sub $$offset address {{MMIO_START}}
op and $$offset $$offset 0x3fffffffc
jump trap greaterThanEq $$offset {{# (UART_RX_OFFSET|int(base=16) + 4 * UART_MMIO_SIZE|int(base=16))|hex }}
# if we're in UART range, look it up
# skip privilege check if accessing uart
op add ret2 @counter {{# start_assert_length(2) }}
jump access_uart greaterThanEq $$offset {{UART_RX_OFFSET}}
# otherwise, we're in machine timer range, so trap for PMA if not in M-mode
# MMIO is not executable, so always use the effective privilege mode here
jump trap lessThan effective_privilege_mode 0b11
#directive end_assert_length
# return 0 for write-only registers
jump load_mmio_word__uart_status equal $$offset {{UART_STATUS_OFFSET}}
jump load_mmio_word__not_zero lessThanEq $$offset {{UART_RX_OFFSET}}
load_mmio_word__ret:
set @counter ret
load_mmio_word__not_zero:
op idiv $$offset $$offset 2
op add @counter @counter $$offset
# mtime
set result csr_mtime
set @counter ret
# mtimeh
set result csr_mtimeh
set @counter ret
# mtimecmp
set result csr_mtimecmp
set @counter ret
# mtimecmph
set result csr_mtimecmph
set @counter ret
# UART RX FIFO
#% do declare_locals(access_uart_locals)
#% do free_locals("$$offset")
# return 0 if the FIFO is empty
jump load_mmio_word__ret equal $$rx_read $$rx_write
# HACK: if mcause is 7, we're loading this word as part of a store instruction, so return 0 and don't modify UART state
jump load_mmio_word__ret equal mcause 7
# read one value from the FIFO
read result $$uart $$rx_read
op and result result 0xffffffff
# advance read
op add $$rx_read $$rx_read 1
op mod $$rx_read $$rx_read UART_FIFO_MODULO
write $$rx_read $$uart {{UART_RX_READ}}
set @counter ret
load_mmio_word__uart_status:
#% do declare_locals(access_uart_locals)
#% do free_locals("$$offset", "$$uart")
# interrupt enabled
op shr result uart_flags $$uart_shift