Compare commits

...

7 Commits

Author SHA1 Message Date
d75c373da7 Add support for Zephyr
Most of the basic SoC drivers are copied from the official SERV
source code and have only been adapted. This commit also adds
device tree files and other definitions to support the Trashernet
hardware.

A preliminary driver (polling-based) for the Trashernet Ethernet
module is also included.
2024-12-03 08:36:51 +01:00
b8f273c9b6 fpga: bench: Add bench to run larger programs from external RAM 2024-11-08 16:06:04 +01:00
9487f5c2ef fpga: top: Refactor indentation 2024-11-08 15:52:46 +01:00
f21122c54b fpga: Add script to remove $asserts from netlists
For some reason, GHDL adds some (not all) asserts to the
synthesis netlist. This script removes them.
2024-11-08 15:37:15 +01:00
0943656548 fpga: run.py: Increase NVC heap size to simulate full 8 MB of external RAM 2024-11-08 10:31:19 +01:00
4ca35ca18c fpga: aps6404l device model: Print message instead of failing for out of bounds accesses 2024-11-08 10:29:07 +01:00
539d2e0908 fpga: aps6404l device model: Allow preloading memory from file 2024-11-08 10:28:54 +01:00
38 changed files with 1218 additions and 11 deletions

View File

@ -46,6 +46,7 @@ $(BUILD_DIR)/netlist-post-synthesis.json: $(VU_FLAG) $(SOURCES_VERILOG)
# Collect GHDL sources from VUnit
$(eval GHDLINCDIRS=$(shell find "$(VU_DIR)/ghdl/libraries" -maxdepth 1 -mindepth 1 -type d | sed "s/^/-P/" | tr '\n' ' '))
yosys -m ghdl -p "read_verilog $(SOURCES_VERILOG); ghdl --std=08 $(GHDLINCDIRS) design.top; synth_ice40 -abc9 -device $(YOSYS_DEVICE) -top top -json $@"
./patch_asserts.sh $@
$(BUILD_DIR)/netlist-post-pnr.asc: $(BUILD_DIR)/netlist-post-synthesis.json $(CONSTRAINTS)
nextpnr-ice40 --$(DEVICE) --package $(PACKAGE) --asc $(BUILD_DIR)/netlist-post-pnr.asc --report $(BUILD_DIR)/report.json --detailed-timing-report --json $(BUILD_DIR)/netlist-post-synthesis.json --pcf $(CONSTRAINTS)

View File

@ -0,0 +1,181 @@
-- -------------------------------------------------------------------------- --
-- TRASHERNET SoC - A Trashy Ethernet SoC for FPGAs --
-- -------------------------------------------------------------------------- --
-- Inspection-only test bench to run a larger memory image from external RAM.
-- -------------------------------------------------------------------------- --
-- Author : Markus Koch <markus@notsyncing.net>
-- Contributors : None
-- License : Mozilla Public License (MPL) Version 2
-- -------------------------------------------------------------------------- --
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
library design;
use design.all;
library generics;
use generics.wishbone_pkg.all;
library device_models;
use device_models.all;
library vunit_lib;
context vunit_lib.vunit_context;
library osvvm;
context osvvm.osvvmContext;
use osvvm.ScoreboardPkg_slv.all;
entity bench_top_zephyr is
generic(
runner_cfg : string := runner_cfg_default
);
end entity bench_top_zephyr;
architecture RTL of bench_top_zephyr is
constant UART_BAUD : integer := 250000;
signal clk_in : std_logic;
signal uart_tx : std_logic;
signal uart_rx : std_logic;
signal eth_rx_p : std_logic;
signal eth_tx_p : std_logic_vector(3 downto 0);
signal eth_tx_n : std_logic_vector(3 downto 0);
signal eth_led_green_n : std_logic;
signal eth_led_orange_n : std_logic;
signal led_user : std_logic;
signal psram_ce_n : std_logic;
signal psram_sclk : std_logic;
signal psram_sio : std_logic_vector(3 downto 0);
signal flash_ce_n : std_logic;
signal flash_sclk : std_logic;
signal flash_sio : std_logic_vector(3 downto 0);
signal pmod : std_logic_vector(7 downto 0);
begin
top_inst : entity design.top
generic map(
F_IN => 50000000,
F_CLK => 25000000,
F_CLK_PHY => 50000000,
UART_BAUD => UART_BAUD,
CPU => "neorv32"
)
port map(
clk_in => clk_in,
uart_tx => uart_tx,
uart_rx => uart_rx,
eth_rx_p => eth_rx_p,
eth_tx_p => eth_tx_p,
eth_tx_n => eth_tx_n,
eth_led_green_n => eth_led_green_n,
eth_led_orange_n => eth_led_orange_n,
led_user => led_user,
psram_ce_n => psram_ce_n,
psram_sclk => psram_sclk,
psram_sio => psram_sio,
flash_ce_n => flash_ce_n,
flash_sclk => flash_sclk,
flash_sio => flash_sio,
pmod => pmod
);
aps6404l_inst : entity device_models.aps6404l
generic map(
SIZE => 8 * 1024 * 1024,
--MEMFILE => "../sw/demo/decrypt-1.vhex",
LOG_EN => false
)
port map(
ce_n => psram_ce_n,
sclk => psram_sclk,
sio => psram_sio
);
uart_decoder : process is
constant DELAY : time := (1 sec / UART_BAUD);
variable d : std_logic_vector(7 downto 0);
variable print_time : boolean := true;
procedure print(text : character) is
variable lb : line;
begin
write(lb, text);
write(output, lb.all);
flush(output);
end procedure print;
begin
wait until falling_edge(uart_tx);
wait for 0.5 * DELAY;
for i in 0 to 7 loop
wait for DELAY;
d(i) := uart_tx;
end loop;
wait for 1.0 * DELAY;
if print_time then
write(output, "{" & time'image(now) & "} ");
print_time := false;
end if;
print(character'val(to_integer(unsigned(d))));
if character'val(to_integer(unsigned(d))) = LF then
print_time := true;
end if;
--report "UART RX: " & character'val(to_integer(unsigned(d)));
end process uart_decoder;
test : process is
procedure uart_tx(d : std_logic_vector(7 downto 0)) is
constant DELAY : time := (1 sec / UART_BAUD);
begin
uart_rx <= '0';
wait for DELAY;
for i in d'low to d'high loop
uart_rx <= d(i);
wait for DELAY;
end loop;
uart_rx <= '1';
wait for DELAY;
end procedure uart_tx;
begin
test_runner_setup(runner, runner_cfg);
report "Waiting for internal reset to be complete...";
wait for 10 us;
report "Starting tests...";
while test_suite loop
if run("run_program") then
uart_rx <= '1';
wait for (1 sec / UART_BAUD) * 10;
report ("Jumping to external RAM...");
-- JUMP
uart_tx(x"03");
-- to RAM (+4, jalr zero)
uart_tx(x"40");
uart_tx(x"00");
uart_tx(x"00");
uart_tx(x"00");
report ("This is a manual test. Add wait statement here and watch the output...");
wait for 0 ns;
report ("Stopping test.");
end if;
end loop;
test_runner_cleanup(runner);
end process test;
test_runner_watchdog(runner, 1000 ms);
clock_driver : process
constant period : time := 20 ns;
begin
clk_in <= '0';
wait for period / 2;
clk_in <= '1';
wait for period - (period / 2);
end process clock_driver;
end architecture RTL;

View File

@ -237,10 +237,10 @@ begin
servant_rom_vhdl_inst : entity work.servant_ram_vhdl
generic map(
memfile => "../sw/bootrom/bootrom.vhex",
read_only => true,
adr_width => 9,
force_vlog => not in_simulation -- GHDL + Yosys doesn't keep the memfile
memfile => "../sw/bootrom/bootrom.vhex",
read_only => true,
adr_width => 9,
force_vlog => not in_simulation -- GHDL + Yosys doesn't keep the memfile
)
port map(
clk => clk,

View File

@ -16,7 +16,9 @@ use std.textio.all;
entity aps6404l is
generic(
LOG_EN : boolean := true
SIZE : natural := 1024;
LOG_EN : boolean := true;
MEMFILE : string := ""
);
port(
ce_n : in std_logic;
@ -41,7 +43,24 @@ begin
test : process is
type rx_state_t is (COMMAND, READ, WRITE, COMPLETE);
type byte_vector is array (natural range <>) of std_logic_vector(7 downto 0);
variable mem : byte_vector(0 to 1023);
subtype mem_t is byte_vector(0 to SIZE - 1);
impure function init_ram_hex return mem_t is
file text_file : text;
variable text_line : line;
variable ram_content : mem_t := (others => x"5A");
begin
if MEMFILE /= "" then
file_open(text_file, MEMFILE, read_mode);
for i in 0 to SIZE - 1 loop
exit when endfile(text_file);
readline(text_file, text_line);
hread(text_line, ram_content(i));
end loop;
end if;
return ram_content;
end function;
variable mem : mem_t := init_ram_hex;
variable bytes : byte_vector(0 to 32);
variable cnt, bytecnt : integer;
variable state : rx_state_t;
@ -111,8 +130,13 @@ begin
print(" Addr: " & to_hstring(bytes(1)) & to_hstring(bytes(2)) & to_hstring(bytes(3)));
addr := to_integer(unsigned(std_logic_vector'(bytes(1), bytes(2), bytes(3))));
elsif bytecnt > 4 then
dout := mem(addr);
print(" Read " & integer'image(addr) & ": " & to_hstring(mem(addr)));
if addr < SIZE then
dout := mem(addr);
print(" Read " & integer'image(addr) & ": " & to_hstring(mem(addr)));
else
dout := (others => 'X');
print(" Read " & integer'image(addr) & ": OUT-OF-BOUNDS");
end if;
addr := addr + 1;
end if;
@ -122,8 +146,12 @@ begin
addr := to_integer(unsigned(std_logic_vector'(bytes(1), bytes(2), bytes(3))));
elsif bytecnt > 3 then
print(" Write " & integer'image(addr) & ": " & to_hstring(bytes(bytecnt)));
mem(addr) := bytes(bytecnt);
addr := addr + 1;
if (addr < SIZE) then
mem(addr) := bytes(bytecnt);
else
print(" Error: OUT OF BOUNDS access! Ignoring write.");
end if;
addr := addr + 1;
end if;
when COMPLETE =>

14
fpga/patch_asserts.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
file="$1"
echo "Removing \$assert from $file..."
LOCS=`grep -n '$assert' "$file" | sed 's/:.*//g'`
IFS=$'\n'
for LOC in $LOCS; do
START=$(($LOC-2))
END=$(($LOC+14))
echo "Deleting lines $START -> $END"
sed -i -e "${START},${END}d" "$file"
done

View File

@ -57,6 +57,7 @@ if not "osvvm" in libs:
vu.add_compile_option("ghdl.a_flags", ["-frelaxed", "-fsynopsys"])
vu.add_compile_option("nvc.a_flags", ["--relaxed"])
#vu.set_sim_option("nvc.elab_flags", ["-O3"])
vu.set_sim_option("nvc.sim_flags", ["--format=fst", "--wave=wave.fst"])
vu.set_sim_option("nvc.heap_size", "256M")
vu.set_sim_option("nvc.sim_flags", ["--format=fst", "--wave=wave.fst", "--dump-arrays"])
vu.main()

9
zephyr/west.yml Normal file
View File

@ -0,0 +1,9 @@
manifest:
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
projects:
- name: zephyr
remote: zephyrproject-rtos
revision: v3.7.0
import: true

View File

@ -0,0 +1,4 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
add_subdirectory(drivers)

8
zephyr/zephyr/Kconfig Normal file
View File

@ -0,0 +1,8 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
menu "SweRVolf"
rsource "drivers/Kconfig"
endmenu

View File

@ -0,0 +1,8 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
#
# SPDX-License-Identifier: Apache-2.0
config BOARD_TRASHERNET_SOC
bool "Trashernet SoC"
depends on SOC_RISCV32_SERVANT

View File

@ -0,0 +1,13 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
#
# SPDX-License-Identifier: Apache-2.0
config BOARD
default "trashernet_soc"
depends on BOARD_TRASHERNET_SOC
config ETH_TRASHERNET
bool
default y
depends on NET_L2_ETHERNET

View File

@ -0,0 +1,17 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
#
# SPDX-License-Identifier: Apache-2.0
identifier: trashernet_soc
name: Trashernet SoC
type: mcu
arch: riscv32
toolchain:
- zephyr
ram: 32
vendor: notsyncing.net
testing:
ignore_tags:
- net
- bluetooth

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
* Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
*
* SPDX-License-Identifier: Apache-2.0
*/
/dts-v1/;
#include <serv.dtsi>
/ {
chosen {
zephyr,sram = &ram;
zephyr,console = &uart0;
};
ram: memory@0 {
compatible = "mmio-sram";
reg = <0x40000000 0x800000>;
};
soc {
compatible = "olofk,serv";
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@0 {
reg = <0x0 0x1>;
compatible = "trashernet,serial";
};
eth0: ethernet@0 {
reg = <0x0 0x1>;
compatible = "notsyncing,trashernet";
local-mac-address = [10 b1 7d df 6f db];
};
};
};

View File

@ -0,0 +1,13 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
#
# SPDX-License-Identifier: Apache-2.0
CONFIG_BOARD_TRASHERNET_SOC=y
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART_TRASHERNET=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_UART_CONSOLE=y
CONFIG_XIP=n

View File

@ -0,0 +1,7 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
add_subdirectory_ifdef(CONFIG_SERIAL_HAS_DRIVER serial)
add_subdirectory_ifdef(CONFIG_SYS_CLOCK_EXISTS timer)
add_subdirectory_ifdef(CONFIG_ETH_TRASHERNET trashernet)

View File

@ -0,0 +1,12 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
menu "Device Drivers"
rsource "serial/Kconfig"
rsource "timer/Kconfig"
rsource "trashernet/Kconfig"
endmenu

View File

@ -0,0 +1,7 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
# SPDX-License-Identifier: Apache-2.0
set(ZEPHYR_CURRENT_LIBRARY drivers__serial)
zephyr_library_sources_ifdef(CONFIG_UART_TRASHERNET uart_trashernet.c)

View File

@ -0,0 +1,13 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
# SPDX-License-Identifier: Apache-2.0
if SERIAL
menuconfig UART_TRASHERNET
bool "Trashernet serial driver"
select SERIAL_HAS_DRIVER
help
Enables the Trashernet serial driver
endif

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
* Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <inttypes.h>
#define UART0_BASE 0x81000000
#define UART0_SR (*((volatile uint32_t *) (UART0_BASE + 0x00)))
#define UART0_DR (*((volatile uint32_t *) (UART0_BASE + 0x04)))
#define UART0_SR_RX_DATA_EMPTY (1 << 0)
#define UART0_SR_TX_FULL (1 << 1)
#define DT_DRV_COMPAT trashernet_serial
static struct k_spinlock lock;
static void uart_trashernet_poll_out(const struct device *dev, unsigned char c)
{
ARG_UNUSED(dev);
k_spinlock_key_t key = k_spin_lock(&lock);
while (UART0_SR & UART0_SR_TX_FULL);
UART0_DR = c;
k_spin_unlock(&lock, key);
}
static int uart_trashernet_poll_in(const struct device *dev, unsigned char *c)
{
ARG_UNUSED(dev);
*c = UART0_DR;
return (UART0_SR & UART0_SR_RX_DATA_EMPTY) ? -1 : 0;
}
static int uart_trashernet_init(const struct device *dev)
{
ARG_UNUSED(dev);
return 0;
}
static const struct uart_driver_api uart_trashernet_driver_api = {
.poll_in = uart_trashernet_poll_in,
.poll_out = uart_trashernet_poll_out,
.err_check = NULL,
};
struct my_dev_data {
};
struct my_dev_cfg {
};
#define CREATE_MY_DEVICE(inst) \
static struct my_dev_data my_data_##inst = { \
}; \
static const struct my_dev_cfg my_cfg_##inst = { \
}; \
DEVICE_DT_INST_DEFINE(inst, \
uart_trashernet_init, \
NULL, \
&my_data_##inst, \
&my_cfg_##inst, \
PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, \
&uart_trashernet_driver_api);
DT_INST_FOREACH_STATUS_OKAY(CREATE_MY_DEVICE)

View File

@ -0,0 +1,5 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
zephyr_library_sources_ifdef(CONFIG_SERV_TIMER serv_timer.c)

View File

@ -0,0 +1,9 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
config SERV_TIMER
bool "SERV Timer"
select TICKLESS_CAPABLE
help
This module implements a kernel device driver for the SERV
timer driver. It provides the standard "system clock driver" interfaces.

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
This is basically a 32-bit version of riscv_machine_timer.c for Zephyr
*/
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/sys_clock.h>
#include <zephyr/spinlock.h>
#include <zephyr/device.h>
#include <soc.h>
#define CYC_PER_TICK ((uint32_t)((uint32_t)sys_clock_hw_cycles_per_sec() \
/ (uint32_t)CONFIG_SYS_CLOCK_TICKS_PER_SEC))
#define MAX_CYC 0xffffffffu
#define MAX_TICKS ((MAX_CYC - CYC_PER_TICK) / CYC_PER_TICK)
#define MIN_DELAY 1000
#define TICKLESS IS_ENABLED(CONFIG_TICKLESS_KERNEL)
static struct k_spinlock lock;
static uint32_t last_count;
static ALWAYS_INLINE void set_mtimecmp(uint32_t time)
{
//printk("set_mtimecmp to %d @ %d\n", time, sys_read32(SERV_TIMER_BASE));
sys_write32(time, SERV_TIMER_BASE);
}
static ALWAYS_INLINE uint32_t mtime(void)
{
return sys_read32(SERV_TIMER_BASE);
}
static void timer_isr(void *arg)
{
//printk("timer isr\n");
ARG_UNUSED(arg);
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t now = mtime();
uint32_t dticks = ((now - last_count) / CYC_PER_TICK);
last_count += dticks * CYC_PER_TICK;
if (!TICKLESS) {
uint32_t next = last_count + CYC_PER_TICK;
if ((int32_t)(next - now) < MIN_DELAY) {
next += CYC_PER_TICK;
}
set_mtimecmp(next);
}
k_spin_unlock(&lock, key);
sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? dticks : 1);
}
int irq_testy()
{
printk("test isr\n");
return 0;
}
int sys_clock_driver_init()
{
IRQ_CONNECT(RISCV_MACHINE_TIMER_IRQ, 0, timer_isr, NULL, 0);
//IRQ_CONNECT(5, 0, irq_testy, NULL, 0);
last_count = mtime();
set_mtimecmp(last_count + (uint32_t)CYC_PER_TICK);
//irq_enable(5);
irq_enable(RISCV_MACHINE_TIMER_IRQ);
return 0;
}
void sys_clock_set_timeout(int32_t ticks, bool idle)
{
ARG_UNUSED(idle);
#if defined(CONFIG_TICKLESS_KERNEL)
/* RISCV has no idle handler yet, so if we try to spin on the
* logic below to reset the comparator, we'll always bump it
* forward to the "next tick" due to MIN_DELAY handling and
* the interrupt will never fire! Just rely on the fact that
* the OS gave us the proper timeout already.
*/
if (idle) {
return;
}
ticks = ticks == K_TICKS_FOREVER ? MAX_TICKS : ticks;
ticks = MAX(MIN(ticks - 1, (int32_t)MAX_TICKS), 0);
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t now = mtime();
uint32_t adj, cyc = ticks * CYC_PER_TICK;
/* Round up to next tick boundary. */
adj = (now - last_count) + (CYC_PER_TICK - 1);
if (cyc <= MAX_CYC - adj) {
cyc += adj;
} else {
cyc = MAX_CYC;
}
cyc = (cyc / CYC_PER_TICK) * CYC_PER_TICK;
if ((int32_t)(cyc + last_count - now) < MIN_DELAY) {
cyc += CYC_PER_TICK;
}
set_mtimecmp(cyc + last_count);
k_spin_unlock(&lock, key);
#endif
}
uint32_t sys_clock_elapsed(void)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return 0;
}
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t ret = (mtime() - last_count) / CYC_PER_TICK;
k_spin_unlock(&lock, key);
return ret;
}
uint32_t sys_timer_cycle_get_32(void)
{
return mtime();
}
uint32_t sys_clock_cycle_get_32(void)
{
return mtime();
}
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);

View File

@ -0,0 +1,5 @@
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
# SPDX-License-Identifier: Apache-2.0
zephyr_library_sources_ifdef(CONFIG_ETH_TRASHERNET trashernet.c)

View File

@ -0,0 +1,10 @@
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
# SPDX-License-Identifier: Apache-2.0
config ETH_TRASHERNET
bool "Trashernet 10MBit/s PHY driver"
depends on NETWORKING
depends on !TEST_NET
depends on NET_L2_ETHERNET
help
This module implements a kernel device driver for the Trashernet 10 MBit/s PHY.

View File

@ -0,0 +1,253 @@
/* TRASHERNET Stand-alone Ethernet Controller with SPI
*
* Copyright (c) 2016 Markus Koch <markus@notsyncing.net>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT notsyncing_trashernet
#define LOG_MODULE_NAME eth_trashernet
#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL
// TODO: Load this stuff from DT
#define TRASHERNET_BASE 0x82000000
#define TRASHERNET_SR (*((volatile uint32_t *) (TRASHERNET_BASE + 0x00)))
#define TRASHERNET_DR (*((volatile uint8_t *) (TRASHERNET_BASE + 0x04)))
#define TRASHERNET_SR_LINK_UP (1 << 0)
#define TRASHERNET_SR_RX_AVAILABLE (1 << 1)
#define TRASHERNET_SR_TX_NOTEMPTY (1 << 2)
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <string.h>
#include <errno.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/ethernet.h>
#define ETH_TRASHERNET_RX_THREAD_STACK_SIZE 800
#define ETH_TRASHERNET_RX_THREAD_PRIO 2
struct eth_trashernet_config {
int32_t timeout;
};
struct eth_trashernet_runtime {
struct net_if *iface;
K_KERNEL_STACK_MEMBER(thread_stack, ETH_TRASHERNET_RX_THREAD_STACK_SIZE);
struct k_thread thread;
struct k_sem int_sem;
bool iface_initialized : 1;
uint8_t mac_address[6];
};
static void eth_trashernet_set_data(const struct device *dev, uint8_t *buffer, uint16_t length)
{
uint16_t i;
for (i=0; i < length; ++i) {
TRASHERNET_DR = buffer[i];
}
}
static int eth_trashernet_tx(const struct device *dev, struct net_pkt *pkt)
{
struct eth_trashernet_runtime *context = dev->data;
uint16_t len = net_pkt_get_len(pkt);
struct net_buf *frag;
LOG_DBG("TX %s: pkt %p (len %u)", dev->name, pkt, len);
while (TRASHERNET_SR & TRASHERNET_SR_TX_NOTEMPTY); // FIXME: To be fixed in HDL: We currently do not support TX packet separation, so we have to wait for the TX buffer to be completely empty before adding the next packet.
for (frag = pkt->frags; frag; frag = frag->frags) {
eth_trashernet_set_data(dev, frag->data, frag->len);
}
TRASHERNET_SR = TRASHERNET_SR_TX_NOTEMPTY; // Frame complete, TX now
//LOG_DBG("%s: Tx successful", dev->name);
return 0;
}
static void eth_trashernet_get_data(const struct device *dev, uint8_t *buffer, uint16_t length)
{
for (int x = 0; x < length; ++x) {
buffer[x] = TRASHERNET_DR;
}
}
static int eth_trashernet_rx(const struct device *dev)
{
struct eth_trashernet_runtime *context = dev->data;
const struct eth_trashernet_config *config = dev->config;
uint16_t frm_len;
uint16_t lengthfr;
struct net_buf *pkt_buf;
struct net_pkt *pkt;
frm_len = TRASHERNET_SR >> 16;
TRASHERNET_SR = (1 << 1); // Confirm that we've read the length
if (!frm_len) {
LOG_ERR("%s: Receive called without data available!", dev->name);
return 0;
}
//LOG_DBG("RX Data %d", frm_len);
pkt = net_pkt_rx_alloc_with_buffer(context->iface, frm_len,
AF_UNSPEC, 0, K_MSEC(config->timeout));
if (!pkt) {
LOG_ERR("%s: Could not allocate rx buffer", dev->name);
return 0;
}
pkt_buf = pkt->buffer;
lengthfr = frm_len;
do {
size_t frag_len;
uint8_t *data_ptr;
size_t spi_frame_len;
data_ptr = pkt_buf->data;
/* Review the space available for the new frag */
frag_len = net_buf_tailroom(pkt_buf);
if (frm_len > frag_len) {
spi_frame_len = frag_len;
} else {
spi_frame_len = frm_len;
}
eth_trashernet_get_data(dev, data_ptr, spi_frame_len);
net_buf_add(pkt_buf, spi_frame_len);
/* One fragment has been written via SPI */
frm_len -= spi_frame_len;
pkt_buf = pkt_buf->frags;
} while (frm_len > 0);
net_pkt_set_iface(pkt, context->iface);
LOG_DBG("%s: Received packet of length %u", dev->name, lengthfr);
if (net_recv_data(net_pkt_iface(pkt), pkt) < 0) {
net_pkt_unref(pkt);
}
return 0;
}
static void eth_trashernet_rx_thread(void *p1, void *p2, void *p3)
{
int status_last = 0;
int status = 0;
ARG_UNUSED(p2);
ARG_UNUSED(p3);
const struct device *dev = p1;
struct eth_trashernet_runtime *context = dev->data;
while (true) {
//k_sem_take(&context->int_sem, K_FOREVER); // Set to wait for IRQ
//k_sem_give(&context->int_sem); // Set in IRQ
status = TRASHERNET_SR;
if ((status ^ status_last) & TRASHERNET_SR_LINK_UP) { // Link change
if (status & TRASHERNET_SR_LINK_UP) {
LOG_INF("%s: Link up", dev->name);
net_eth_carrier_on(context->iface);
} else {
LOG_INF("%s: Link down", dev->name);
if (context->iface_initialized) {
net_eth_carrier_off(context->iface);
}
}
}
if (status & TRASHERNET_SR_RX_AVAILABLE) { // New RX data
eth_trashernet_rx(dev);
} else {
k_msleep(10); // We poll
}
status_last = status;
}
}
static enum ethernet_hw_caps eth_trashernet_get_capabilities(const struct device *dev)
{
ARG_UNUSED(dev);
return ETHERNET_LINK_10BASE_T
#if defined(CONFIG_NET_VLAN)
| ETHERNET_HW_VLAN
#endif
;
}
static void eth_trashernet_iface_init(struct net_if *iface)
{
const struct device *dev = net_if_get_device(iface);
struct eth_trashernet_runtime *context = dev->data;
net_if_set_link_addr(iface, context->mac_address,
sizeof(context->mac_address),
NET_LINK_ETHERNET);
if (context->iface == NULL) {
context->iface = iface;
}
ethernet_init(iface);
net_if_carrier_off(iface);
context->iface_initialized = true;
}
static const struct ethernet_api api_funcs = {
.iface_api.init = eth_trashernet_iface_init,
.get_capabilities = eth_trashernet_get_capabilities,
.send = eth_trashernet_tx,
};
static int eth_trashernet_init(const struct device *dev)
{
const struct eth_trashernet_config *config = dev->config;
struct eth_trashernet_runtime *context = dev->data;
/* Start interruption-poll thread */
k_thread_create(&context->thread, context->thread_stack,
ETH_TRASHERNET_RX_THREAD_STACK_SIZE,
eth_trashernet_rx_thread,
(void *)dev, NULL, NULL,
K_PRIO_COOP(ETH_TRASHERNET_RX_THREAD_PRIO),
0, K_NO_WAIT);
LOG_INF("%s: Initialized", dev->name);
return 0;
}
#define TRASHERNET_DEFINE(inst) \
static struct eth_trashernet_runtime eth_trashernet_runtime_##inst = { \
.mac_address = DT_INST_PROP(inst, local_mac_address), \
.int_sem = Z_SEM_INITIALIZER((eth_trashernet_runtime_##inst).int_sem, 0, UINT_MAX), \
}; \
\
static const struct eth_trashernet_config eth_trashernet_config_##inst = { \
.timeout = 500, \
}; \
\
ETH_NET_DEVICE_DT_INST_DEFINE(inst, eth_trashernet_init, NULL, &eth_trashernet_runtime_##inst, \
&eth_trashernet_config_##inst, CONFIG_ETH_INIT_PRIORITY, \
&api_funcs, NET_ETH_MTU);
DT_INST_FOREACH_STATUS_OKAY(TRASHERNET_DEFINE);

View File

@ -0,0 +1,14 @@
# Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
# SPDX-License-Identifier: Apache-2.0
description: Trashernet PHY
compatible: "notsyncing,trashernet"
include: [ethernet-controller.yaml]
properties:
full-duplex:
type: boolean
description: |
Optional feature flag - Enables full duplex reception and transmission.

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
* Copyright (c) 2024 Markus Koch <markus@notsyncing.net>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
#address-cells = <1>;
#size-cells = <1>;
compatible = "olofk,servant";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "olofk,serv";
riscv,isa = "rv32i_zicsr";
reg = <0>;
device_type = "cpu";
};
};
};

5
zephyr/zephyr/module.yml Normal file
View File

@ -0,0 +1,5 @@
build:
settings:
board_root: zephyr
dts_root: zephyr
soc_root: zephyr

View File

@ -0,0 +1,8 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
zephyr_sources(
soc_irq.S
vector.S
irq.c
cpu_idle.c)

View File

@ -0,0 +1,26 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
if SOC_RISCV32_SERVANT
config SOC
string
default "servant"
config SYS_CLOCK_HW_CYCLES_PER_SEC
int
default 25000000
config RISCV_SOC_INTERRUPT_INIT
bool
default y
config NUM_IRQS
int
default 8
config SERV_TIMER
bool
default y
endif # SOC_RISCV32_SERVANT

View File

@ -0,0 +1,8 @@
# Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
# SPDX-License-Identifier: Apache-2.0
config SOC_RISCV32_SERVANT
bool "servant SoC"
select RISCV
select ATOMIC_OPERATIONS_C
select RISCV_ISA_EXT_ZICSR

View File

@ -0,0 +1,22 @@
#include <zephyr/irq.h>
#include <zephyr/tracing/tracing.h>
// Override arch_cpu_idle() and arch_cpu_atomic_idle() to prevent insertion
// of `wfi` instructions which lock up our system. This was introduced in
// Zephyr 3.6.0 with commit 5fb6e267f629dedb8382da6bcad8018b1bb8930a.
//
// This is probably a hardware bug in SERV. This issue is tracked as #131.
// https://github.com/olofk/serv/issues/131
void arch_cpu_idle(void)
{
sys_trace_idle();
irq_unlock(MSTATUS_IEN);
}
void arch_cpu_atomic_idle(unsigned int key)
{
sys_trace_idle();
irq_unlock(key);
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2017 Jean-Paul Etienne <fractalclone@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief interrupt management code for riscv SOCs supporting the riscv
privileged architecture specification
*/
#include <zephyr/irq.h>
void arch_irq_enable(unsigned int irq)
{
uint32_t mie;
/*
* CSR mie register is updated using atomic instruction csrrs
* (atomic read and set bits in CSR register)
*/
__asm__ volatile ("csrrs %0, mie, %1\n"
: "=r" (mie)
: "r" (1 << irq));
}
void arch_irq_disable(unsigned int irq)
{
uint32_t mie;
/*
* Use atomic instruction csrrc to disable device interrupt in mie CSR.
* (atomic read and clear bits in CSR register)
*/
__asm__ volatile ("csrrc %0, mie, %1\n"
: "=r" (mie)
: "r" (1 << irq));
};
int arch_irq_is_enabled(unsigned int irq)
{
uint32_t mie;
__asm__ volatile ("csrr %0, mie" : "=r" (mie));
return !!(mie & (1 << irq));
}
#if defined(CONFIG_RISCV_SOC_INTERRUPT_INIT)
void soc_interrupt_init(void)
{
/* ensure that all interrupts are disabled */
(void)irq_lock();
__asm__ volatile ("csrwi mie, 0\n"
"csrwi mip, 0\n");
}
#endif

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) 2018 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/arch/riscv/common/linker.ld>

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) 2020 Olof Kindgren <olof.kindgren@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __RISCV32_SERVANT_SOC_H_
#define __RISCV32_SERVANT_SOC_H_
#include <soc_common.h>
/* Timer configuration */
#define SERV_TIMER_BASE 0x80000000
#define SERV_TIMER_IRQ 7
#endif /* __RISCV32_SERVANT_SOC_H_ */

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2017 Jean-Paul Etienne <fractalclone@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file configuration macros for riscv SOCs supporting the riscv
* privileged architecture specification
*/
#ifndef __SOC_COMMON_H_
#define __SOC_COMMON_H_
/* IRQ numbers */
#define RISCV_MACHINE_SOFT_IRQ 3 /* Machine Software Interrupt */
#define RISCV_MACHINE_TIMER_IRQ 7 /* Machine Timer Interrupt */
#define RISCV_MACHINE_EXT_IRQ 11 /* Machine External Interrupt */
/* ECALL Exception numbers */
#define SOC_MCAUSE_ECALL_EXP 11 /* Machine ECALL instruction */
#define SOC_MCAUSE_USER_ECALL_EXP 8 /* User ECALL instruction */
/* SOC-specific MCAUSE bitfields */
#ifdef CONFIG_64BIT
/* Interrupt Mask */
#define SOC_MCAUSE_IRQ_MASK (1 << 63)
/* Exception code Mask */
#define SOC_MCAUSE_EXP_MASK 0x7FFFFFFFFFFFFFFF
#else
/* Interrupt Mask */
#define SOC_MCAUSE_IRQ_MASK (1 << 31)
/* Exception code Mask */
#define SOC_MCAUSE_EXP_MASK 0x7FFFFFFF
#endif
/* SOC-Specific EXIT ISR command */
#define SOC_ERET mret
#ifndef _ASMLANGUAGE
#if defined(CONFIG_RISCV_SOC_INTERRUPT_INIT)
void soc_interrupt_init(void);
#endif
#if defined(CONFIG_RISCV_HAS_PLIC)
void riscv_plic_irq_enable(uint32_t irq);
void riscv_plic_irq_disable(uint32_t irq);
int riscv_plic_irq_is_enabled(uint32_t irq);
void riscv_plic_set_priority(uint32_t irq, uint32_t priority);
int riscv_plic_get_irq(void);
#endif
#endif /* !_ASMLANGUAGE */
#endif /* __SOC_COMMON_H_ */

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2017 Jean-Paul Etienne <fractalclone@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* common interrupt management code for riscv SOCs supporting the riscv
* privileged architecture specification
*/
#include <offsets.h>
#include <zephyr/linker/sections.h>
#include <soc.h>
/* exports */
GTEXT(__soc_handle_irq)
/*
* SOC-specific function to handle pending IRQ number generating the interrupt.
* Exception number is given as parameter via register a0.
*/
SECTION_FUNC(exception.other, __soc_handle_irq)
/* Clear exception number from CSR mip register */
li t1, 1
sll t0, t1, a0
csrrc t1, mip, t0
/* Return */
jalr x0, ra
/*
* __soc_is_irq is defined as .weak to allow re-implementation by
* SOCs that does not truly follow the riscv privilege specification.
*/
WTEXT(__soc_is_irq)
/*
* SOC-specific function to determine if the exception is the result of a
* an interrupt or an exception
* return 1 (interrupt) or 0 (exception)
*
*/
SECTION_FUNC(exception.other, __soc_is_irq)
/* Read mcause and check if interrupt bit is set */
csrr t0, mcause
li t1, SOC_MCAUSE_IRQ_MASK
and t0, t0, t1
/* If interrupt bit is not set, return with 0 */
addi a0, x0, 0
beqz t0, not_interrupt
addi a0, a0, 1
not_interrupt:
/* return */
jalr x0, ra

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2017 Jean-Paul Etienne <fractalclone@gmail.com>
* Contributors: 2018 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/toolchain.h>
/* exports */
GTEXT(__start)
/* imports */
GTEXT(__initialize)
GTEXT(_isr_wrapper)
SECTION_FUNC(vectors, __start)
.option norvc;
/*
* Set mtvec (Machine Trap-Vector Base-Address Register)
* to __isr_wrapper.
*/
la t0, _isr_wrapper
csrw mtvec, t0
/* Jump to __initialize */
tail __initialize