本帖最后由 musiclee 于 2023-3-7 15:17 编辑
上一篇 [Rust on SF102] 安路科技 SF1 FPGA 系列评测 (一)- Hello rust! 尝试了针对安路科技 SF1 系列 FPGA 配置对应的 Rust 交叉编译环境,并编写了一个简单的 LED Blink 例程,并顺利地在SF102 DEMO 板上运行成功。接下来我们将正式创建一个 FPGA SOC 项目,用于评估后续的外设驱动。
FPGA SOC 项目
1. 创建项目
2. 配置时钟,点选菜单 Tools -> IP Generator 生成锁相环:
点击 OK,并填写生成的目标文件名:
点击 OK,选择锁相环对应的 IP:
开发板输入时钟为 25MHz,将 PLL 配置为简单倍频到 100MHz 输出:
确认即可生成对应的 PLL IP 代码,软件会询问是否加入项目,确认选择加入。
3. 点选菜单 Tools -> IP Generator 生成 RISC-V 硬核 IP 代码
步骤和 PLL 相同,填写目标代码文件名:
IP 选择 SF1 RISCV:
当前我们仅验证 UART 编程,所以勾选最简配置:
确认生成并加入项目。至此完成必要的 IP 文件生成。
4. 编写主模块
主模块代码例化了 PLL 和 RISC-V 硬核,实现了支持 UART 的最小硬件系统:
`timescale 1ns/1ps
module SF1Simple (
inout led1,
inout led2,
inout led3,
output uart_tx,
input uart_rx,
input clk,
input reset
);
wire syspll_reset;
wire mcu_core_reset;
wire syspll_extlock;
wire syspll_clk0_out;
wire [31:0] mcu_apb_prdata;
wire mcu_apb_pready;
wire mcu_apb_pslverr;
wire mcu_uart_tx;
wire mcu_gpio0_out;
wire mcu_gpio0_dir;
wire mcu_gpio1_out;
wire mcu_gpio1_dir;
wire mcu_gpio2_out;
wire mcu_gpio2_dir;
wire mcu_mtip;
wire mcu_sysrstreq;
wire mcu_apb_clk_down;
wire [31:0] mcu_apb_pwdata_down;
wire [3:0] mcu_apb_pstrobe_down;
wire [2:0] mcu_apb_pprot_down;
wire mcu_apb_penable_down;
wire mcu_apb_pwrite_down;
wire [11:0] mcu_apb_paddr_down;
wire mcu_apb_psel0_down;
wire mcu_apb_psel1_down;
wire mcu_apb_psel2_down;
wire gpio0_O_gpio_in;
wire gpio1_O_gpio_in;
wire gpio2_O_gpio_in;
pll syspll (
.refclk (clk ),
.reset (syspll_reset ),
.extlock (syspll_extlock ),
.clk0_out (syspll_clk0_out)
);
rv32 mcu (
.soft_ip_apbm_en (1'b0 ),
.qspi0cfg1_mode (1'b1 ),
.qspi0cfg2_mode (1'b1 ),
.apb_clk (1'b0 ),
.apb_paddr (32'h0 ),
.apb_pwrite (1'b0 ),
.apb_penable (1'b0 ),
.apb_pprot (3'b000 ),
.apb_pstrobe (4'b0000 ),
.apb_psel (1'b0 ),
.apb_pwdata (32'h0 ),
.apb_prdata (mcu_apb_prdata[31:0] ),
.apb_pready (mcu_apb_pready ),
.apb_pslverr (mcu_apb_pslverr ),
.uart_tx (mcu_uart_tx ),
.uart_rx (uart_rx ),
.gpio0_in (gpio0_O_gpio_in ),
.gpio0_out (mcu_gpio0_out ),
.gpio0_dir (mcu_gpio0_dir ),
.gpio1_in (gpio1_O_gpio_in ),
.gpio1_out (mcu_gpio1_out ),
.gpio1_dir (mcu_gpio1_dir ),
.gpio2_in (gpio2_O_gpio_in ),
.gpio2_out (mcu_gpio2_out ),
.gpio2_dir (mcu_gpio2_dir ),
.timer_clk (syspll_clk0_out ),
.mtip (mcu_mtip ),
.core_clk (syspll_clk0_out ),
.core_reset (mcu_core_reset ),
.nmi (1'b0 ),
.clic_irq (16'h0 ),
.sysrstreq (mcu_sysrstreq ),
.apb_clk_down (mcu_apb_clk_down ),
.apb_prdata_down (32'h0 ),
.apb_pwdata_down (mcu_apb_pwdata_down[31:0]),
.apb_pstrobe_down (mcu_apb_pstrobe_down[3:0]),
.apb_pprot_down (mcu_apb_pprot_down[2:0] ),
.apb_penable_down (mcu_apb_penable_down ),
.apb_pwrite_down (mcu_apb_pwrite_down ),
.apb_pslverr_down (1'b0 ),
.apb_pready_down (1'b0 ),
.apb_paddr_down (mcu_apb_paddr_down[11:0] ),
.apb_psel0_down (mcu_apb_psel0_down ),
.apb_psel1_down (mcu_apb_psel1_down ),
.apb_psel2_down (mcu_apb_psel2_down )
);
gpio_controler_2 gpio0 (
.O_gpio_in (gpio0_O_gpio_in),
.I_gpio_dir (mcu_gpio0_dir ),
.I_gpio_out (mcu_gpio0_out ),
.IO_gpio (led1 )
);
gpio_controler_2 gpio1 (
.O_gpio_in (gpio1_O_gpio_in),
.I_gpio_dir (mcu_gpio1_dir ),
.I_gpio_out (mcu_gpio1_out ),
.IO_gpio (led2 )
);
gpio_controler_2 gpio2 (
.O_gpio_in (gpio2_O_gpio_in),
.I_gpio_dir (mcu_gpio2_dir ),
.I_gpio_out (mcu_gpio2_out ),
.IO_gpio (led3 )
);
assign syspll_reset = (! reset);
assign mcu_core_reset = (! reset);
assign uart_tx = mcu_uart_tx;
endmodule
module gpio_controler_2 (
output O_gpio_in,
input I_gpio_dir,
input I_gpio_out,
inout IO_gpio
);
assign IO_gpio = I_gpio_dir ? I_gpio_out : 1'bz;
assign O_gpio_in = IO_gpio;
endmodule
5. 分配引脚
在软件界面左侧点击 IO Constraint ,为用到的端口分配引脚编号(参照原理图sf1s60cg121i_demo_board_v21.pdf)
生成的 .adc 文件内容如下(也可以直接编写 .adc 文件加入项目):
set_pin_assignment { clk } { LOCATION = D7; IOSTANDARD = LVCMOS33; }
set_pin_assignment { reset } { LOCATION = H3; IOSTANDARD = LVCMOS33; }
set_pin_assignment { uart_rx } { LOCATION = E4; IOSTANDARD = LVCMOS33; }
set_pin_assignment { uart_tx } { LOCATION = A4; IOSTANDARD = LVCMOS33; }
set_pin_assignment { led1 } { LOCATION = J4; IOSTANDARD = LVCMOS33; }
set_pin_assignment { led2 } { LOCATION = J5; IOSTANDARD = LVCMOS33; }
set_pin_assignment { led3 } { LOCATION = H5; IOSTANDARD = LVCMOS33; }
6. 综合布线
工具栏点击 run 按钮,正常编译通过:
至此 FPGA 项目设计完成。
编写 Rust 代码
在编写驱动 UART 的 Rust 代码之前,先简要介绍安路科技 SF1内嵌硬核的 UART 外设资源,SF1 支持一个名为 UART 0 的外设,对应的寄存器映射地址空间为 0xE001_0000 ~ 0x1001_FFFF,下面是其寄存器列表概览:
要驱动 UART 实现在串口终端输出字符串,最基本的步骤需要配置 UART_DIV、UART_TXCTRL 和 UART_SETUP 这三个寄存器。具体说明详见 TN817_SF1 MCU用户指南.pdf 。
Rust 开发环境以及项目创建指南已经在上一篇 [Rust on SF102] 安路科技 SF1 FPGA 系列评测 (一)- Hello rust! 中详细介绍,因此这里只展示主代码 main.rs:
#![no_std]
#![no_main]
extern crate riscv_rt;
extern crate panic_halt;
use riscv_rt::entry;
use core::ptr::{write_volatile,read_volatile};
// 时钟配置寄存器
const MISC_BASE_ADDRESS: u32 = 0xE0000000;
const CLOCKCTL_ADDRESS: u32 = MISC_BASE_ADDRESS + 0x10;
const CLOCKCTL: *mut u32 = CLOCKCTL_ADDRESS as *mut u32;
// 定义串口寄存器地址
const UART: u32 = 0xE001_0000;
const UART_TXDATA: u32 = UART + 0x00;
// const UART_RXDATA: u32 = UART + 0x04;
const UART_TXCTRL: u32 = UART + 0x08;
const UART_RXCTRL: u32 = UART + 0x0C;
const UART_IE: u32 = UART + 0x10;
// const UART_IP: u32 = UART + 0x14;
const UART_DIV: u32 = UART + 0x18;
// const UART_STATUS: u32 = UART + 0x1C;
const UART_SETUP: u32 = UART + 0x20;
// 定义串口波特率
const BAUD_RATE: u32 = 115200;
// 定义系统时钟频率
const SYSCLK: u32 = 50_000_000;
#[entry]
fn main() -> ! {
// 初始化串口寄存器
unsafe {
write_volatile(CLOCKCTL, 0x83);
// 设置波特率分频寄存器
let div = SYSCLK / BAUD_RATE - 1;
write_volatile(UART_DIV as *mut u32, div);
// 设置发送控制寄存器,使能发送功能和中断
let txctrl = read_volatile(UART_TXCTRL as *mut u32) | 0x1;
write_volatile(UART_TXCTRL as *mut u32, txctrl);
// 设置接收控制寄存器,使能接收功能和中断
let rxctrl = read_volatile(UART_RXCTRL as *mut u32) | 0x1;
write_volatile(UART_RXCTRL as *mut u32, rxctrl);
write_volatile(UART_SETUP as *mut u32, 0x30);
// 设置中断使能寄存器,使能发送就绪和接收就绪中断
let ie = (1 << 0) | (1 << 1);
write_volatile(UART_IE as *mut u32, ie);
}
// 向串口输出 "Hello world." 字符串
let message = b"Hello world.\r\n";
for &byte in message.iter() {
send_byte(byte);
}
loop {}
}
// 定义一个函数,用于发送一个字节到串口
fn send_byte(byte: u8) {
unsafe {
// 等待发送数据缓冲区为空
while read_volatile(UART_TXDATA as *const u32) & (1 << 31) != 0 {}
// 写入数据到发送数据缓冲区
write_volatile(UART_TXDATA as *mut u32, byte as u32);
}
}
以下是 Cargo 项目的配置文件和链接文件,创建 Rust 项目请参考第一篇 [Rust on SF102] 安路科技 SF1 FPGA 系列评测 (一)- Hello rust!
cargo/config 文件
[target.riscv32imac-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Tmemory.x",
"-C", "link-arg=-Tlink.x"
]
[build]
target = "riscv32imac-unknown-none-elf"
memory.x 文件
MEMORY
{
flash : ORIGIN = 0x000E0000, LENGTH = 4M
ilm : ORIGIN = 0x08000000, LENGTH = 8K
ram : ORIGIN = 0x20000000, LENGTH = 8K
}
REGION_ALIAS("REGION_TEXT", flash);
REGION_ALIAS("REGION_RODATA", flash);
REGION_ALIAS("REGION_DATA", ram);
REGION_ALIAS("REGION_BSS", ram);
REGION_ALIAS("REGION_HEAP", ram);
REGION_ALIAS("REGION_STACK", ram);
编译并生成 hex 文件
cargo build --release
cargo objcopy --release -- -O ihex oledtest.hex
烧写运行
首先需要连接好主板 USB 和 下载器,如图 Type-C 接口会在电脑上识别为一个USB串口设备
打开对应的串口终端,设置波特率为 115200, 具体参数如下:
然后在 TD 软件下载 FPGA(具体步骤参考 [Rust on SF102] 安路科技 SF1 FPGA 系列评测 (一)- Hello rust! ):
下载成功后,将会在串口终端看到输出信息:
至此,UART 驱动成功!
FPGA 项目文件:
td2.zip
(315.37 KB, 下载次数: 2)