
\ -----------------------------------------------------------------------------
\  Bootloader for iCE40 FPGA on Mystorm
\ -----------------------------------------------------------------------------

\ Various connections on the Mystorm board:
\
\ PA9:  TX
\ PA10: RX
\ PA11: D-
\ PA12: D+
\ PA13: SWDIO
\ PA14: SWCLK
\ PA15: State-LED
\
\ FPGA connections:
\
\ PB3: SCK
\ PB4: MISO
\ PB5: MOSI
\ PB6: Reset
\ PB7: Done
\
\ PD2: /SS
\
\ Parallel bus connection:
\
\ PA0: RD0   UART4 TXD connected to J1 RXD
\ PA1: RD1   UART4 RXD connected to J1 TXD
\ PA2: RD2   
\ PA3: RD3
\ PA4: RD4
\ PA5: RD5
\ PA6: RD6
\ PA7: RD7
\ PA8: RD8
\
\ RSEL: PC13  J1 /Reset
\ RCLK: PC14
\ RDIR: PC15
\
\ Bitstream image needs to be available in the core as
\   bitstream ( -- addr len )

\ -----------------------------------------------------------------------------
\  Ports and tools
\ -----------------------------------------------------------------------------

: ms ( u -- ) \ A very simple busy loop.
  0 ?do
    12019 0 do loop
  loop
;

$48000000 constant PORTA_Base
$48000400 constant PORTB_Base
$48000800 constant PORTC_Base
$48000C00 constant PORTD_Base
$48001000 constant PORTE_Base
$48001400 constant PORTF_Base
$48001000 constant PORTG_Base
$48001400 constant PORTH_Base

PORTA_BASE $00 + constant PORTA_MODER    \ Reset 0 Port Mode Register - 00=Input  01=Output  10=Alternate  11=Analog
PORTA_BASE $04 + constant PORTA_OTYPER   \ Reset 0 Port Output type register - (0) Push/Pull vs. (1) Open Drain
PORTA_BASE $08 + constant PORTA_OSPEEDR  \ Reset 0 Output Speed Register - 00=2 MHz  01=25 MHz  10=50 MHz  11=100 MHz
PORTA_BASE $0C + constant PORTA_PUPDR    \ Reset 0 Pullup / Pulldown - 00=none  01=Pullup  10=Pulldown
PORTA_BASE $10 + constant PORTA_IDR      \ RO      Input Data Register
PORTA_BASE $14 + constant PORTA_ODR      \ Reset 0 Output Data Register
PORTA_BASE $18 + constant PORTA_BSRR     \ WO      Bit set/reset register   31:16 Reset 15:0 Set
        \ +$1C                                     ... is Lock Register, unused
PORTA_BASE $20 + constant PORTA_AFRL     \ Reset 0 Alternate function  low register
PORTA_BASE $24 + constant PORTA_AFRH     \ Reset 0 Alternate function high register


PORTB_BASE $00 + constant PORTB_MODER    \ Reset 0 Port Mode Register - 00=Input  01=Output  10=Alternate  11=Analog
PORTB_BASE $04 + constant PORTB_OTYPER   \ Reset 0 Port Output type register - (0) Push/Pull vs. (1) Open Drain
PORTB_BASE $08 + constant PORTB_OSPEEDR  \ Reset 0 Output Speed Register - 00=2 MHz  01=25 MHz  10=50 MHz  11=100 MHz
PORTB_BASE $0C + constant PORTB_PUPDR    \ Reset 0 Pullup / Pulldown - 00=none  01=Pullup  10=Pulldown
PORTB_BASE $10 + constant PORTB_IDR      \ RO      Input Data Register
PORTB_BASE $14 + constant PORTB_ODR      \ Reset 0 Output Data Register
PORTB_BASE $18 + constant PORTB_BSRR     \ WO      Bit set/reset register   31:16 Reset 15:0 Set
        \ +$1C                                     ... is Lock Register, unused
PORTB_BASE $20 + constant PORTB_AFRL     \ Reset 0 Alternate function  low register
PORTB_BASE $24 + constant PORTB_AFRH     \ Reset 0 Alternate function high register


PORTC_BASE $00 + constant PORTC_MODER    \ Reset 0 Port Mode Register - 00=Input  01=Output  10=Alternate  11=Analog
PORTC_BASE $04 + constant PORTC_OTYPER   \ Reset 0 Port Output type register - (0) Push/Pull vs. (1) Open Drain
PORTC_BASE $08 + constant PORTC_OSPEEDR  \ Reset 0 Output Speed Register - 00=2 MHz  01=25 MHz  10=50 MHz  11=100 MHz
PORTC_BASE $0C + constant PORTC_PUPDR    \ Reset 0 Pullup / Pulldown - 00=none  01=Pullup  10=Pulldown
PORTC_BASE $10 + constant PORTC_IDR      \ RO      Input Data Register
PORTC_BASE $14 + constant PORTC_ODR      \ Reset 0 Output Data Register
PORTC_BASE $18 + constant PORTC_BSRR     \ WO      Bit set/reset register   31:16 Reset 15:0 Set
        \ +$1C                                     ... is Lock Register, unused
PORTC_BASE $20 + constant PORTC_AFRL     \ Reset 0 Alternate function  low register
PORTC_BASE $24 + constant PORTC_AFRH     \ Reset 0 Alternate function high register


PORTD_BASE $00 + constant PORTD_MODER    \ Reset 0 Port Mode Register - 00=Input  01=Output  10=Alternate  11=Analog
PORTD_BASE $04 + constant PORTD_OTYPER   \ Reset 0 Port Output type register - (0) Push/Pull vs. (1) Open Drain
PORTD_BASE $08 + constant PORTD_OSPEEDR  \ Reset 0 Output Speed Register - 00=2 MHz  01=25 MHz  10=50 MHz  11=100 MHz
PORTD_BASE $0C + constant PORTD_PUPDR    \ Reset 0 Pullup / Pulldown - 00=none  01=Pullup  10=Pulldown
PORTD_BASE $10 + constant PORTD_IDR      \ RO      Input Data Register
PORTD_BASE $14 + constant PORTD_ODR      \ Reset 0 Output Data Register
PORTD_BASE $18 + constant PORTD_BSRR     \ WO      Bit set/reset register   31:16 Reset 15:0 Set
        \ +$1C                                     ... is Lock Register, unused
PORTD_BASE $20 + constant PORTD_AFRL     \ Reset 0 Alternate function  low register
PORTD_BASE $24 + constant PORTD_AFRH     \ Reset 0 Alternate function high register

\ -----------------------------------------------------------------------------
\  Pin wiggling primitives
\ -----------------------------------------------------------------------------

: led-on   ( -- ) 1 15 lshift  16 lshift  porta_bsrr ! ;
: led-off  ( -- ) 1 15 lshift             porta_bsrr ! ;

1 3 lshift constant spi-sck
1 4 lshift constant spi-miso
1 5 lshift constant spi-mosi

: sck-high  ( -- ) spi-sck            portb_bsrr ! ;
: sck-low   ( -- ) spi-sck  16 lshift portb_bsrr ! ;
: mosi-high ( -- ) spi-mosi           portb_bsrr ! ;
: mosi-low  ( -- ) spi-mosi 16 lshift portb_bsrr ! ;

: miso ( -- ? ) spi-miso portb_idr bit@ ;

1 6 lshift constant fpga-reset-pin
1 7 lshift constant fpga-done-pin

: reset-high ( -- ) fpga-reset-pin            portb_bsrr ! ;
: reset-low  ( -- ) fpga-reset-pin  16 lshift portb_bsrr ! ;

: done? ( -- ? ) fpga-done-pin portb_idr bit@ ;

1 2 lshift constant fpga-cs-pin

: cs-high  ( -- ) fpga-cs-pin            portd_bsrr ! ;
: cs-low   ( -- ) fpga-cs-pin  16 lshift portd_bsrr ! ;

: interface-init ( -- )

  sck-high cs-low reset-low

  %01 2 2* lshift portd_moder ! \ Set select pin as output, low

  %01 3 2* lshift                 \ Set SCK pin as output
                                   \ MISO is input
  %01 5 2* lshift or                \ Set MOSI pin as output
  %01 6 2* lshift or                 \ Set Reset pin as output
                      portb_moder !   \ Done is input with pullup
  %01 6 2* lshift     portb_pupdr !    \ Set pullup for done pin
;

\ -----------------------------------------------------------------------------
\  Primitives for SPI bit-banging
\ -----------------------------------------------------------------------------

: bit-spix ( ? -- ? )
  if mosi-high else mosi-low then
  sck-low
  sck-high
  miso
;

: spix ( x -- x ) $FF and

    dup 128 and bit-spix if 128 or else 128 bic then
    dup 64 and bit-spix if 64 or else 64 bic then
    dup 32 and bit-spix if 32 or else 32 bic then
    dup 16 and bit-spix if 16 or else 16 bic then

    dup 8 and bit-spix if 8 or else 8 bic then
    dup 4 and bit-spix if 4 or else 4 bic then
    dup 2 and bit-spix if 2 or else 2 bic then
    dup 1 and bit-spix if 1 or else 1 bic then
;

: >spi ( x -- ) spix drop ;
: spi> ( -- x ) 0 spix ;


\ -----------------------------------------------------------------------------
\  FPGA Bootloader
\ -----------------------------------------------------------------------------

: fpga-reset ( -- )

  interface-init  \ Prepare pins

  reset-low  \ Pull ICE40 CRESET low to reset FPGA
  cs-low     \ Pull ICE40 SPI CS low whilst resetting FPGA
  1 ms       \ Wait for a millisecond
  reset-high \ Release ICE40 reset to boot into Slave mode

  begin done? not until \ Make sure ICE40 CDONE has been pulleed low by FPGA in response

  2 ms       \ Delay 2 millisecs to allow FPGA to reconfigure itself
;

: fpga-boot ( addr len -- )

  led-on

  fpga-reset                    \ Initialise the interface wires
  0 do dup i + c@ >spi loop drop \ Write the bitstream
  begin 0 >spi done? until        \ Wait for ICE40 CDONE release, keep clocking dummies until it works
  8 0 do 0 >spi loop               \ Send the ICE40 at least 49 clocks using dummy data
  cs-high                           \ Release ICE40 SPI CS

  led-off
;

: boot ( -- ) bitstream fpga-boot ;

\ -----------------------------------------------------------------------------
\  Terminal bridge
\ -----------------------------------------------------------------------------

: reset-j1-high ( -- ) 1 13 lshift            portc_bsrr ! ;
: reset-j1-low  ( -- ) 1 13 lshift  16 lshift portc_bsrr ! ;
: reset-j1-init ( -- ) %01 13 2* lshift portc_moder ! ;
: reset-j1 ( -- ) reset-j1-init reset-j1-low 100 ms reset-j1-high ;

: terminal ( -- )
  cr ." Terminal into Mecrisp-Ice. Press CTRL+C to return and CTRL+D to reset."
  cr reset-j1-init reset-j1-high led-on

  false
  begin
    bridge-key? if bridge-key emit then
    key?        if drop key dup bridge-emit then
  dup 4 = if reset-j1 drop false then
  dup 3 = until
  drop

  cr ." Return to Mecrisp-Stellaris" cr led-off
;

: ice ( -- )
  boot
  reset-j1
  terminal
;

