Stashing the state.

This commit is contained in:
flabbergast 2023-12-05 11:13:53 +00:00
parent 930b40bef2
commit 546b3002a4
19 changed files with 1138 additions and 5 deletions

View file

@ -4,7 +4,7 @@
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
.ifdef compressed_isa
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0

View file

@ -4,7 +4,7 @@
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
.ifdef compressed_isa
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0

View file

@ -4,7 +4,7 @@
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
.ifdef compressed_isa
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0

View file

@ -1,5 +1,5 @@
#
# p_i2c0.s: basic i2c driver
# p_i2c.s: basic i2c driver
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/

View file

@ -4,7 +4,7 @@
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
.ifdef compressed_isa
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0

View file

@ -132,3 +132,34 @@
.equ I2C_SR_AL, (1 << 5)
.equ I2C_SR_TIP, (1 << 1)
.equ I2C_SR_IP, (1 << 0)
.equ QSPI0_BASE, 0x10014000
.equ SPI1_BASE, 0x10024000
.equ SPI2_BASE, 0x10034000
.equ SPI_SCKDIV, 0x00
.equ SPI_SCKMODE, 0x04
.equ SPI_CSID, 0x10
.equ SPI_CSDEF, 0x14
.equ SPI_CSMODE, 0x18
.equ SPI_DELAY0, 0x28
.equ SPI_DELAY1, 0x2C
.equ SPI_FMT, 0x40
.equ SPI_TXDATA, 0x48
.equ SPI_RXDATA, 0x4C
.equ SPI_TXMARK, 0x50
.equ SPI_RXMARK, 0x54
.equ SPI_FCTRL, 0x60
.equ SPI_FFMT, 0x64
.equ SPI_IE, 0x70
.equ SPI_IP, 0x74
.equ PWM0_BASE, 0x10015000
.equ PWM1_BASE, 0x10025000
.equ PWM2_BASE, 0x10035000
.equ PWMCFG, 0x00
.equ PWMCOUNT, 0x08
.equ PWMS, 0x10
.equ PWMCMP0, 0x20
.equ PWMCMP1, 0x24
.equ PWMCMP2, 0x28
.equ PWMCMP3, 0x2C

31
work/Makefile Normal file
View file

@ -0,0 +1,31 @@
PROG ?= main
JLINKEXE ?= ~/.platformio/packages/tool-jlink/JLinkExe
PREFIX ?= riscv32-elf-
ARCH ?= rv32imac
AFLAGS = --warn --fatal-warnings -g -L
LDFLAGS = -m elf32lriscv -b elf32-littleriscv --discard-none
all: $(PROG).hex
$(PROG).o: $(PROG).s p_spi.s
$(PREFIX)as -march=$(ARCH) $(AFLAGS) $(PROG).s -o $(PROG).o
$(PROG).hex: memmap $(PROG).o
$(PREFIX)ld $(LDFLAGS) -o $(PROG).elf -T memmap $(PROG).o
$(PREFIX)objdump -Mnumeric -D $(PROG).elf > $(PROG).list
$(PREFIX)objcopy $(PROG).elf $(PROG).bin -O binary
$(PREFIX)objcopy $(PROG).elf $(PROG).hex -O ihex
$(PREFIX)size $(PROG).elf
flash: $(PROG).hex
echo -e "loadfile $(PROG).hex\nr\ng\nexit\n" | $(JLINKEXE) -device FE310 -if JTAG -speed 4000 -si JTAG -jtagconf -1,-1 -autoconnect 1
clean:
rm -f *.bin
rm -f *.hex
rm -f *.o
rm -f *.elf
rm -f *.list

13
work/README.md Normal file
View file

@ -0,0 +1,13 @@
# 08-i2c
Add a basic i2c driver. Really only 1 function, `i2c_rw` which writes some bytes to the i2c bus, and then reads some bytes from it (either/both phases can be skipped).
The "demo" code in `main.s` is for messing around with a 24C02 eeprom (256 bytes, so just 1 byte addressing).
### Lessons learned
- Having a debugger is a godsend; I should not ever try to start learning on a bare chip.
- When I write new registers into `platform_regs.inc` by hand, re-check at least 17 times.
- Re-check push/pop patterns, especially when returning early, e.g. from a macro.

50
work/macros.s Normal file
View file

@ -0,0 +1,50 @@
#
# macros.s
#
# 2021 flabbergast <flabbergast@drak.xyz>
# Unlicense: https://unlicense.org/
#
# designate some RAM memory to a symbol
.macro ramallot Name, Size
.equ \Name, rampointer
.set rampointer, rampointer + \Size
.endm
# stack: push, pop
.macro push reg
addi sp, sp, -4
sw \reg, 0(sp)
.endm
.macro pop reg
lw \reg, 0(sp)
addi sp, sp, 4
.endm
# insert a sized string
.macro sized_string Text
.byte 8f - 7f # Compute length of string.
7: .ascii "\Text"
.if compressed_isa
8: .balign 2, 0 # Realign
.else
8: .balign 4, 0 # Realign
.endif
.endm
# data stack
# _s11_ used as the data stack pointer!!
.macro pushda reg
addi s11, s11, -4
sw \reg, 0(s11)
.endm
.macro popda reg
lw \reg, 0(s11)
addi s11, s11, 4
.endm
.macro pushdaconst const
push t0 # yep ... should rethink this
li t0, \const # but it should being used very little
pushda t0
pop t0
.endm

98
work/main.s Normal file
View file

@ -0,0 +1,98 @@
#
# main.s
#
# 2021 flabbergast <flabbergast@drak.xyz>
# Unlicense: https://unlicense.org/
#
.option norelax
.equ compressed_isa, 1
# -----------------------------------------------------------------------------
# macros and RAM variables
# -----------------------------------------------------------------------------
.include "macros.s"
.set rampointer, 0x80000000 # start allocating at the beginning of RAM
# end of RAM is +0x4000
ramallot data_stack_begin, 128 # 32 values
ramallot data_stack_end, 0
ramallot return_stack_begin, 512
ramallot return_stack_end, 0
# -----------------------------------------------------------------------------
# Execution begins here
# -----------------------------------------------------------------------------
.text
j Reset
# -----------------------------------------------------------------------------
# Static data
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Include any supporting code
# -----------------------------------------------------------------------------
.include "platform_regs.inc"
.include "p_interrupts.s"
.include "p_clock.s"
.include "p_delay.s"
.include "p_uart0.s"
.include "p_i2c.s"
.include "p_spi.s"
.include "write.s"
# -----------------------------------------------------------------------------
Reset: # Execution begins here
# -----------------------------------------------------------------------------
# set up stacks
li sp, return_stack_end
li s11, data_stack_end
# set up interrupt and exception handler (ours needs sp)
csrci mstatus, 8 # disable interrupts
la t0, irq_handler
csrw mtvec, t0
csrsi mstatus, 8 # enable interrupts
# the board (usually) runs on 13.8MHz HFROSC on boot?
jal clock_extcryst # switch to external crystal, 16MHz
jal uart_init # initialise UART0 (115200baud)
# if you change clock, you'll need to adjust the
# baud divisor in p_uart0.s
jal write_init # set up default "emit" over uart
jal delay_init # enable the timer interrupt
# jal i2c_init # init i2c: adjust prescaler if you change clock!
puts "Welcome!\n"
## chase again
li s1, GPIO_BASE
li s2, 0b111111<<18 # pins 18-23
sw s2, GPIO_OUTPUT_EN(s1) # set as outputs
li a0, 200 # delay in ms
li s2, 0b1<<18 # start with pin 18
li s3, 0b1<<23 # end with pin 23
1:
sw s2, GPIO_OUTPUT_VAL(s1) # make the pin high
li a0, 200
jal delay
slli s2, s2, 1 # move one to the left
bleu s2, s3, 1b # did we move past last pin?
li s2, 0b1<<18 # back to the first pin
j 1b
end:
j end

1
work/memmap Symbolic link
View file

@ -0,0 +1 @@
../memmap

69
work/p_clock.s Normal file
View file

@ -0,0 +1,69 @@
#
# p_clock.s: functions for setting up the main clock
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0
.endif
.include "platform_regs.inc"
# switch to external crystal (16MHz on red-v thing)
clock_extcryst:
li t0, PRCI_BASE
1: # Wait for crystal to oscillate
lw t1, PRCI_HFXOSCCFG(t0) # hfxoscen(bit30) is on by default on reset
bgtz t1, 1b # ... hfxoscrdy(bit31): makes value negative
li t1, 0x00070df1 # 0x00060df1 | (1<<16) | (1<<17) | (1<<18)
# = Reset value | PLLSEL | PLLREFSEL | PLLBYPASS
sw t1, PRCI_PLLCFG(t0) # Select crystal as the main clock source
ret
# switch to 64MHz PLL driven by 16MHz external crystal
clock_pll_64mhz:
li t0, PRCI_BASE
1: # Wait for crystal to oscillate
lw t1, PRCI_HFXOSCCFG(t0) # hfxoscen(bit30) is on by default on reset
bgtz t1, 1b # ... hfxoscrdy(bit31): makes value negative
# Set PLL to 64 MHz (don't activate); 64 = 16 /(2^1) *(2*(31+1)) /(2^3)
li t1, 0x20df1 # pllr=1, pllf=0x1f, pllq=0x3 (reset vals)
sw t1, PRCI_PLLCFG(t0) # pllsel=0, pllrefsel=1, pllbypass=0(!)
2: # Wait for PLL to lock: plllock(bit31): sign bit
lw t1, PRCI_PLLCFG(t0)
bgtz t1, 2b
li t1, 0x30df1 # pllsel=1 (on top of the above)
sw t1, PRCI_PLLCFG(t0)
ret
# switch to 320MHz PLL driven by 16MHz external crystal
# (the board gets quite warm!)
clock_pll_320mhz:
li t0, PRCI_BASE
1: # Wait for crystal to oscillate
lw t1, PRCI_HFXOSCCFG(t0) # hfxoscen(bit30) is on by default on reset
bgtz t1, 1b # ... hfxoscrdy(bit31): makes value negative
# Set PLL to 320 MHz (don't activate); 320 = 16 /(2^1) *(2*(39+1)) /(2^1)
li t1, 0x20671 # pllr=1, pllf=0x27(!), pllq=1(!)
sw t1, PRCI_PLLCFG(t0) # pllsel=0, pllrefsel=1, pllbypass=0(!)
2: # Wait for PLL to lock: plllock(bit31): sign bit
lw t1, PRCI_PLLCFG(t0)
bgtz t1, 2b
li t1, 0x30671 # pllsel=1 (on top of the above)
sw t1, PRCI_PLLCFG(t0)
ret

92
work/p_delay.s Normal file
View file

@ -0,0 +1,92 @@
#
# p_delay.s: functions for waiting
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0
.endif
.include "platform_regs.inc"
.equ MTIME_FREQUENCY, 33 # 32_768 Hz clock
# enable timer interrupt and put a 'safe' value into MTIMECMP
delay_init:
# clear the interrupt by writing a high value to CLINT_MTIMECMP.h
li t0, CLINT_BASE+CLINT_MTIMECMP
li t1, 0xFFFFF000
sw t1, 4(t0)
li t0, 1<<7
csrs mie, t0 # set MTIE bit
ret
# a0: delay value in milliseconds
# (should be less than 0xFFFF_FFFF/33/4)
# this version uses interrupts if enabled,
# i.e. it waits with wfi, rather than a busy loop
delay:
li t0, CLINT_BASE+CLINT_MTIME+8 # load addresses
li t4, CLINT_BASE+CLINT_MTIMECMP
lw t1, -8(t0) # load the current value of the timer.l
lw t3, -4(t0) # load current timer.h
li t2, MTIME_FREQUENCY # let our clock frequency
mul t2, t2, a0 # multiply milliseconds with frequency
add t2, t1, t2 # target mtime is now in t2
bgeu t2, t1, 4f # _not_ overflown back to zero?
# if overflown, we assume that (signed)t1<0 and t2>0
addi t3, t3, 1 # overflown => increase target time.h by one
4:
sw t2, 0(t4) # set MTIMECMP.l (assuming .h is still very big)
sw t3, 4(t4) # set MTIMECMP.h
sltu t3, t1, t2 # keep the "overflown" flag in t3
1: # main waiting loop
csrr t1, mstatus # check if interrupts are enabled
andi t1, t1, 8
beqz t1, 3f
wfi # if they are, wait for one
3:
lw t1, -8(t0) # read mtime value again
bnez t3, 2f # check overflow
bltu t1, t2, 1b # no overflow: unsigned comparison
2:
blt t1, t2, 1b # overflow: (signed)t1 can be <0
li t1, 0xFFFFF000 # clear timer interrupt
sw t1, 4(t4) # ... by writing big into MTIMECMP.h
ret
# a0: delay value in milliseconds
# (should be less than 0xFFFF_FFFF/33/4)
# delay:
# li t0, CLINT_BASE+CLINT_MTIME+8 # load the timer registers base
# lw t1, -8(t0) # load the current value of the timer (lsw)
#
# li t2, MTIME_FREQUENCY # let our clock frequency
# mul t2, t2, a0 # multiply milliseconds with frequency
# add t2, t1, t2 # target mtime is now in t2
# bltu t2, t1, 2f # overflown back to zero?
# # if yes, assume that (signed)t1<0 and t2>0
#
# 1:
# lw t1, -8(t0) # read mtime value again
# bltu t1, t2, 1b # keep looping until timout
# ret
#
# 2: # overflown branch
# lw t1, -8(t0) # read mtime; t1 can be "<0"
# blt t1, t2, 2b # do signed compare
# ret
# a0: (delay in cycles)/2
busy_delay_cycles:
1:
addi a0, a0, -1
bnez a0, 1b
ret

163
work/p_i2c.s Normal file
View file

@ -0,0 +1,163 @@
#
# p_i2c0.s: basic i2c driver
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
# no description in the manual, refers to https://opencores.org/projects/i2c
# (that requires registration which requires manual approval from someone)
# here's a copy of i2cspecs.pdf:
# https://pilvi.disasm.info/index.php/s/MqvdhL3lBqFxY0P/download
#
# prescaler:
# - max 0xFFFF
# - (clock_rate/(5*baud)) - 1
# - on 16MHz core, 400kHz I2C => 7
# - on 16MHz core, 100kHz I2C => 31
# - set when CONTROL_EN is off
# sample usage:
# jal i2c_init (somewhere at the beginning)
#
# ## i2c bus scanner
# li s1, 1 # start with address
# li s2, 127 # end with address
# 2:
# mv a0, s1
# li a2, 0
# li a4, 0
# jal i2c_rw # try address
# bltz a0, 3f # no response=>skip
# pushda s1 # print message
# puts "Found a device at %B\n"
# 3:
# li a0, 2 # wait a little
# jal delay # ... seems to be too fast otherwise
# addi s1, s1, 1 # inc counter
# bleu s1, s2, 2b # repeat
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0
.endif
.include "platform_regs.inc"
i2c_init:
li t0, GPIO_BASE # UART0 RX/TX are selected in IOF_SEL (IOF0) on Reset.
li t2, (1<<12)|(1<<13) # which bits
lw t1, GPIO_IOF_EN(t0) # set IOF_EN bits ... read existing
or t1, t1, t2 # twiddle bits
sw t1, GPIO_IOF_EN(t0) # write back
not t2, t2 # invert bits
lw t1, GPIO_IOF_SEL(t0) # clear IOF_SEL bits (tho they should be fine on Reset)
and t1, t1, t2 # twiddle bits
sw t1, GPIO_IOF_SEL(t0) # write back
li t0, I2C0_BASE
sb zero, I2C_CTR(t0) # control: disable en, ie
sb zero, I2C_PRERhi(t0) # prescaler MSB
li t1, 31
sb t1, I2C_PRERlo(t0) # prescaler LSB
li t1, I2C_CTR_EN
sb t1, I2C_CTR(t0) # enable module; irq disabled
ret
.macro _wait_for_tip
1: # wait-for-transfer loop
lbu t1, I2C_SR(t0) # status register
andi t1, t1, I2C_SR_TIP # "transfer-in-progress" bit
bnez t1, 1b # wait for TIP to clear
.endm
.macro _check_ack
# check ack
lbu t1, I2C_SR(t0) # status again
andi t1, t1, I2C_SR_RXACK # ack received?
beqz t1, 1f # should be 0
li t1, I2C_CR_STOP # slave did not respond, so...
sb t1, I2C_CR(t0) # set stop condition on the bus
li a0, -1 # set 'fail' return value
ret
1:
.endm
# read/write bytes through I2C
# expects:
# - a0: I2C address
# - a1: pointer to data to write
# - a2: how many bytes to write
# - a3: pointer to data to read
# - a4: how many bytes to read
# returns: a0: 0 success, -1 problem
# a1 and a3 point to 1 byte after the last used
# note: both r/w can have 0 bytes to r/w
# in which case that part is skipped
# note: does not jal anything, so does not push/pop ra!
# if you want to e.g. puts, need to add this,
# including in the macros above!
i2c_rw:
li t0, I2C0_BASE
slli a0, a0, 1
# if zero write nonzero read, goto read
bnez a2, 1f # writing some bytes=>skip
bnez a4, 5f # reading=>goto reading part
1: # writing
sb a0, I2C_TXR(t0) # write the address to tx register
li t1, I2C_CR_START|I2C_CR_WRITE
sb t1, I2C_CR(t0) # start cond, write to bus
_wait_for_tip
_check_ack
add t2, a1, a2 # t2: end of loop pointer
bnez a2, 3f # writing some bytes=>skip
li t1, I2C_CR_STOP # here: 0 write, 0 read
sb t1, I2C_CR(t0) # just set stop
j 9f # and exit
3: # write loop
lbu t1, 0(a1) # read current byte to send
sb t1, I2C_TXR(t0) # write data to tx register
li t1, I2C_CR_WRITE
addi a1, a1, 1 # inc address
bltu a1, t2, 1f # if not the last byte, skip
bnez a4, 1f # subsequent read => do not set stop cond
ori t1, t1, I2C_CR_STOP # last byte => stop condition
1:
sb t1, I2C_CR(t0) # do the deed
_wait_for_tip
_check_ack
bltu a1, t2, 3b # repeat for the next byte
beqz a4, 9f # nothing to read=>goto end
5: # reading
ori t1, a0, 1 # address | read_bit
sb t1, I2C_TXR(t0) # write the address to tx register
li t1, I2C_CR_START|I2C_CR_WRITE
sb t1, I2C_CR(t0) # start cond, write to bus
_wait_for_tip
_check_ack
add t2, a3, a4 # t2: end of loop pointer
addi t2, t2, -1 # one less (to detect the last byte)
6: # read loop
li t1, I2C_CR_READ
bne a3, t2, 1f # if not the last byte=>skip
ori t1, t1, I2C_CR_STOP|I2C_CR_ACK # last byte=>stop and nack
1:
sb t1, I2C_CR(t0)
_wait_for_tip
lbu t1, I2C_RXR(t0) # read the received byte
sb t1, 0(a3) # store it
addi a3, a3, 1 # inc pointer
bleu a3, t2, 6b # repeat for the next byte
9: # end
li a0, 0
ret

114
work/p_interrupts.s Normal file
View file

@ -0,0 +1,114 @@
#
# interrupts.s: basic irq handling setup
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
# ! when you edit the irq handler, don't forget to push/pop
# any extra registers that you use !
# - we don't use the vectored interrupt handling, so
# there is a single function that's executed on any
# interrupt or exception (which needs to make decisions
# based on mcause)
# - the address of this fn should be loaded into mtvec
#
# (SiFive FE310 only has 3 interrupt sources (sw, timer, plic),
# so any vector table would only have 3 "used" entries.)
#
.include "platform_regs.inc"
# irq handler needs to be always 4-aligned
.balign 4, 0
irq_handler:
addi sp, sp, 9*(-4)
sw ra, 8*4(sp) # x1
sw s1, 7*4(sp) # x9
sw t6, 6*4(sp) # x31
sw t5, 5*4(sp) # x30
sw t4, 4*4(sp) # x29
sw t3, 3*4(sp) # x28
sw t2, 2*4(sp) # x7
sw t1, 1*4(sp) # x6
sw t0, 0*4(sp) # x5
# check if it's an interrupt or an exception
csrr t0, mcause
blt t0, zero, .interrupts
## exceptions
.exceptions:
# read CSR and stall
csrr t1, mepc # tells us where it occured
# t0 still contains mcause
jal zero, .exceptions
## interrupts
.interrupts:
andi t0, t0, 0xF # FE310 only has irqs 3,7,11
srli t0, t0, 2
bne t0, zero, 1f
## 3, software interrupt
jal zero, irq_handler_exit
1:
srli t0, t0, 1
bne t0, zero, 1f
## 7, timer interrupt
# clear the interrupt by writing a high value to CLINT_MTIMECMP.h
li t0, CLINT_BASE+CLINT_MTIMECMP
li t1, 0xFFFFF000
sw t1, 4(t0)
jal zero, irq_handler_exit
1:
## 11, external interrupt
# # use PLIC claim/complete mechanism
# li t0, PLIC_BASE+PLIC_CLAIM-4 # easier load
# lw s1, 4(t0) # claim: highest pending irq id
#
# # UART0
# li t1, 3 # UART0 is source 3
# bne t1, s1, 1f # if not UART0, skip
# jal ra, irq_handler_uart0
#
# 1:
# # any other PLIC sources should go here
#
# li t0, PLIC_BASE+PLIC_CLAIM-4
# sw s1, 4(t0) # complete the claimed irq
# exit
irq_handler_exit:
lw t0, 0*4(sp) # x5
lw t1, 1*4(sp) # x6
lw t2, 2*4(sp) # x7
lw t3, 3*4(sp) # x28
lw t4, 4*4(sp) # x29
lw t5, 5*4(sp) # x30
lw t6, 6*4(sp) # x31
lw s1, 7*4(sp) # x9
lw ra, 8*4(sp) # x1
addi sp, sp, 9*4
mret
# enable enternal interrupts (PLIC)
plic_init:
li t0, PLIC_BASE+PLIC_THRESHOLD
sw zero, 0(t0) # set PLIC threshold 0 (all irqs unmasked)
li t0, PLIC_BASE+PLIC_ENABLE1
sw zero, 0(t0) # disable all PLIC irq sources
sw zero, 4(t0) # (a lot seem enabled after reset?)
li t0, 1<<11
csrs mie, t0 # set MEIE(bit11)
ret

105
work/p_spi.s Normal file
View file

@ -0,0 +1,105 @@
#
# p_spi.s: basic SPI driver
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
# It seems that QSPI0 peripheral is used to map the flash
# from which the firmware runs. So not touching that.
# Other than that, there is only SPI1.
#
# There are 3 possible hardware comtrolled CS pins; each
# has selectable "always asserted" (HOLD) mode, or normal
# "AUTO" mode (sel/desel every byte). Possible to also mark
# it "OFF" and fiddle with the pin(s) manually.
# This driver will use hw control on CS0 = IO2 pin.
# (MOSI=IO3, MISO=IO4, SCK=IO5)
#
# Adjust the usual stuff in _init: phase/polarity (0/0),
# clock, #bits-per-frame (8), msb/lsb
#
# clock: sckdiv is 12 bits wide
# spi clock = core_clock / ( 2*(sckdiv+1) )
# (default sckdiv = 3)
#
# sample usage:
# jal spi_init (somewhere at the beginning)
.equ spi_hw_cs, 1
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0
.endif
.include "platform_regs.inc"
spi_init:
# set up pins
li t0, GPIO_BASE # UART0 RX/TX are selected in IOF_SEL (IOF0) on Reset.
.if spi_hw_cs
li t2, (1<<2)|(1<<3)|(1<<4)|(1<<5) # SS0|MOSI|MISO|SCK
.else
li t2, (1<<3)|(1<<4)|(1<<5) # MOSI|MISO|SCK
.endif
lw t1, GPIO_IOF_EN(t0) # set IOF_EN bits ... read existing
or t1, t1, t2 # twiddle bits
sw t1, GPIO_IOF_EN(t0) # write back
not t2, t2 # invert bits
lw t1, GPIO_IOF_SEL(t0) # clear IOF_SEL bits (tho they should be fine on Reset)
and t1, t1, t2 # twiddle bits
sw t1, GPIO_IOF_SEL(t0) # write back
.if spi_hw_cs
.else
# set up the gpio controlled CS pin
li t2, 1<<23
lw t1, GPIO_OUTPUT_VAL(t0)
or t1, t1, t2
sw t1, GPIO_OUTPUT_VAL(t0)
lw t1, GPIO_OUTPUT_EN(t0)
or t1, t1, t2
sw t1, GPIO_OUTPUT_EN(t0)
.endif
# set up SPI peripheral
li t0, SPI1_BASE
li t1, 7 # sckdiv: 1MHz (from 16MHz main clock)
sw t1, SPI_SCKDIV(t0)
# sw zero, SPI_SCKMODE(t0) # pol(inact_cs=0), pha(sample=lead,shift=trail)
.if spi_hw_cs
# li t1, 0b1111
# sw t1, SPI_CSDEF(t0) # all CS pins high when inactive
li t1, 0b0001
sw t1, SPI_CSID(t0) # which CSn pin to use
sw zero, SPI_CSMODE(t0) # AUTO mode for CS (hw-controlled, on/off every transfer)
.else
li t1, 3
sw t1, SPI_CSMODE(t0) # OFF mode for CS (no hw CS control)
.endif
li t1, 0x80000 # dir=Tx, endian=MSB, len=8, proto=Single
sw t1, SPI_FMT(t0)
ret
# transfer one byte out&in
# a0: byte to send
# returns incoming byte in a0
spi_rw:
.if spi_hw_cs
.else
.endif
li t0, SPI1_BASE+SPI_TXDATA
1: # repeat until accepted
amoor.w t1, a0, (t0) # (t0)->t1, then write (t0)|a0 into (t0)
bnez t1, 1b # non-zero means fifo full
addi t0, t0, SPI_RXDATA-SPI_TXDATA
2: # wait until we get a byte from rx fifo
lw a0, 0(t0) # bit31="empty", bit7-bit0 data
bltz a0, 2b
.if spi_hw_cs
.else
.endif
ret

76
work/p_uart0.s Normal file
View file

@ -0,0 +1,76 @@
#
# p_uart0.s: basic uart0
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0
.endif
.include "platform_regs.inc"
# UART0: RX:pin16 TX:pin17
# init
# !! need to change baud setting if the main clock changes
# clobbers: t0, t1, t2
uart_init:
li t0, GPIO_BASE # UART0 RX/TX are selected in IOF_SEL (IOF0) on Reset.
li t2, (1<<17)|(1<<16) # which bits
lw t1, GPIO_IOF_EN(t0) # set IOF_EN bits ... read existing
or t1, t1, t2 # twiddle bits
sw t1, GPIO_IOF_EN(t0) # write back
not t2, t2 # invert bits
lw t1, GPIO_IOF_SEL(t0) # clear IOF_SEL bits (tho they should be fine on Reset)
and t1, t1, t2 # twiddle bits
sw t1, GPIO_IOF_SEL(t0) # write back
li t0, UART0_BASE # setting baud rate
li t1, 139-1 # 16 MHz / 115200 Baud = 138.89
sw t1, UART0_DIV(t0)
li t1, 1
sw t1, UART0_TXCTRL(t0) # enable transmit
sw t1, UART0_RXCTRL(t0) # enable receive
ret
# emit one byte
# a0: (unsigned!) byte to send
# blocking until byte sent
# clobbers: t0, t1
uart_emit:
li t0, UART0_BASE+UART0_TXDATA
1: # repeat until accepted
amoor.w t1, a0, (t0) # (t0)->t1, then write (t0)|a0 into (t0)
bnez t1, 1b # non-zero means tx fifo full
ret
# check if ready to transmit
# returns: a0 non-zero if tx fifo full
# clobbers: t0
q_uart_emit:
li t0, UART0_BASE
lw a0, UART0_TXDATA(t0) # bit31 indicates "full"
ret
# receive a byte
# returns: a0: received byte
# blocking until available
# clobbers: t0
uart_recv:
li t0, UART0_BASE
1: # wait until we get a byte from rx fifo
lw a0, UART0_RXDATA(t0) # bit31="empty", bit7-bit0 data
bltz a0, 1b
ret
# check if there is an incoming byte waiting
# returns: a0 non-zero if rx fifo empty
# clobbers: t0
q_uart_recv:
li t0, UART0_BASE
lw a0, UART0_RXDATA(t0) # bit31 indicates "empty"...
ret # (there can be data in b7-b0 even if "empty")

1
work/platform_regs.inc Symbolic link
View file

@ -0,0 +1 @@
../platform_regs.inc

289
work/write.s Normal file
View file

@ -0,0 +1,289 @@
#
# write.s: printing strings
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
# assumes: stack macros, ramallot macro, uart_emit symbol
# if 'fixed_emit'==0, assumes that an address for "emit" is
# at 'write_emit_routine_p'
# (and assumes that the routine expects the byte in a0)
#
# uses sized strings: b_len b_c1 .. b_cn
#
# we use the stack/memory here - the expectation is that
# "emit" is 'slow' anyway, so don't worry about speed
# can use up to 48 bytes on return stack (temporarily of course)
#
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0
.endif
# either just always do uart_emit, or use a pointer to RAM to
# store an "emit" routine address
.equ fixed_emit, 0
.if fixed_emit
.equ emit_routine, uart_emit
.else
# reserve a location where the address of an "emit" routine is stored
ramallot write_emit_routine_p, 4
.endif
# record the default "emit" routine
# clobbers: t0, t1
write_init:
.if fixed_emit
.else
la t0, uart_emit # let write.s know what to use for printing
li t1, write_emit_routine_p
sw t0, (t1)
.endif
ret
# print a single hexadecimal digit (internal)
# expects:
# - a0: value to print (assumed 0-15)
# - s7: "emit" routine to use
_write_custom_hexdigit:
addi a0, a0, -10 # check if we're adding '0' or 'a'
bltz a0, 1f
addi a0, a0, 'a' # a-f
jr s7 # jump to "emit" routine
1:
addi a0, a0, 10+'0' # 0-9
jr s7 # jump to "emit" routine
# print a hexadecimal unsigned value, up to some #of digits
# expects:
# - a0: value to print
# - a1: shift to 1st nibble to print (e.g. 16bit->12)
# - a2: if non zero, print leading zero digits
write_value_hex:
push ra # push return addr onto stack; we are doing calls
push s0 # we'll use s0 (as temp)
push s7 # s7 as well (for "emit" address)
.if fixed_emit
la s7, emit_routine
.else
li s7, write_emit_routine_p
lw s7, 0(s7) # load "emit" address into s7
.endif
or s0, a0, a2 # for checking if a0==a2==0
bnez s0, 1f # if s0==0, then set a1=0 (only printing the last zero)
li a1, 0
1:
bnez a1, 2f # if a1==0, set a2=1 (always print the last digit)
li a2, 1
2:
mv s0, a0 # save the value that we're printing
3: # main digit printing loop
srl a0, s0, a1 # extract the nibble into a0
andi a0, a0, 0xF # ...
bnez a0, 4f # it's not zero, we're printing it
beqz a2, 5f # not requesting to print zero
4:
li a2, 1 # if we're printing a digit, we're printing the rest
jal _write_custom_hexdigit
5:
addi a1, a1, -4 # move to the next nibble if a1 didn't become negative yet
bgez a1, 3b
pop s7
pop s0
pop ra # restore the return address
ret
# print a byte, hexadecimal, unsigned
# expects:
# - a0: value to print (lowest byte printed)
# - a2: if non zero, always print two digits
# clobbers: a1
write_byte_hex:
li a1, 4
j write_value_hex
# print bytes from memory, hex, padded
# expects:
# - a0: number of bytes
# - a1: address to memory
write_bytes_hex:
push ra # push return addr onto stack; we are doing calls
push s0 # preserve s0
push s1 # preserve s1
mv s1, a1 # we'll use it for looping
li a2, 1 # want padding with zeroes
add s0, s1, a0 # compute final address
1: # byte printing loop
lbu a0, 0(s1) # get the byte
li a1, 4 # want to print two nibbles
jal write_value_hex
addi s1, s1, 1 # increment the address pointer
bgeu s0, s1, 1b # loop if we're not beyond s0 yet
pop s1 # restore s1
pop s0 # restore s0
pop ra # restore the return address
ret
# output a fixed string
.macro puts Text
jal _puts
sized_string "\Text"
.endm
# output a string, address in ra
# need to also fix ra
# clobbers: a0
_puts:
push t6 # let's not clobber
mv a0, ra
lbu t6, 0(ra) # get the string length
addi ra, ra, 1 # skip size byte
add ra, ra, t6 # skip string
# round up to the next half/word boundary
.if compressed_isa
andi t6, ra, 1
add ra, ra, t6
.else
andi t6, ra, 1
add ra, ra, t6
andi t6, ra, 2
add ra, ra, t6
.endif
pop t6
# ! will continue to write_str belo