Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions devices.nix
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,23 @@ in
};

# Digilent Arty S7-50 (xc7s50, csga324, 100MHz osc), Xilinx via the openXC7
# flow. DDR3 runs as x8 on the low byte-lane (arty-s7-x8, 128MB): the
# hardware-verified read and write path at cmdslot=2,wrshift=-1. The x16 high
# lane (per-lane write-leveling) is still under debug. Boot monitor runs from
# the 64K EBR SRAM and DDR is promoted separately. Pins from the working
# bring-up (clk=R2, UART R12/V12).
# flow. The DDR3 is the silicon-proven ddr3v2 stack (the harbor-native ROHD
# port of UberDDR3): full x16 256MB (MT41K128M16) at 300MHz CK. This PHY runs
# its OWN calibration in hardware, so the host does not need a training pass.
# cmdslot=2 sets the command slot and wrshift=-1 sets the write launch. Add
# train=runtime to the dram params to expose the FSBL knob-ABI window instead
# (per controller, optional).
#
# FSBL-from-SRAM boot (the DDR bootstrap decouple): a 64K on-chip BRAM at
# 0x08000000 holds the FSBL stack, .data and .bss (its console struct
# included), so the FSBL runs, prints and can drive the DDR WITHOUT a working
# DRAM read first. The maskrom xipboot runs the FSBL in place from flash XIP.
# The FSBL scratch lives in the SRAM (weir tools/fdt_ld.zig routes it there
# when the tree has an mmio-sram node), then it copies main Weir into DRAM.
# DRAM stays at 0x80000000 so Weir and Ferrite link there unchanged.
#
# clk MUST be SSTL135: R2 is on the 1.35V DDR bank, so `clk=R2` alone
# (defaults to LVCMOS33) yields a dead SoC, the clock is never received.
creek-v1-arty = {
ip = river-hdl.mkSoC (
creek-v1-base
Expand All @@ -76,12 +88,13 @@ in
oscFreq = 100000000;
memories = [
"0x20000000:16M:flash:arty-s7"
"0x80000000:64K:sram"
"0x90000000:128M:dram:arty-s7-x8:ddr3fast=true,clockfreq=400000000,cmdslot=2,wrshift=-1,trainable=true"
"0x08000000:64K:sram"
"0x80000000:256M:dram:arty-s7:ddr3v2=true,clockfreq=300000000,cmdslot=2,wrshift=-1,trainable=true"
];
bootProgram = "monitor";
devices = creek-v1-base.devices ++ [ "debug-jtag" ];
bootProgram = "xipboot";
pins = [
"clk=R2"
"clk=R2 SSTL135"
"uart_tx=uart@tx:R12"
"uart_rx=uart@rx:V12"
];
Expand Down
67 changes: 67 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
url = "github:MidstallSoftware/asix";
inputs.nixpkgs.follows = "nixpkgs";
};
openxc7.url = "github:openXC7/toolchain-nix";
};

outputs =
Expand Down Expand Up @@ -54,6 +55,7 @@
{
system,
pkgs,
inputs',
...
}:
let
Expand Down Expand Up @@ -116,7 +118,14 @@
# above in _module.args.pkgs.
overlayAttrs = {
flakever = flakeverConfig;
river-hdl = pkgs.callPackage ./pkgs/river-hdl { };
river-hdl = pkgs.callPackage ./pkgs/river-hdl {
# openXC7 Xilinx toolchain: mkFpga uses it for spartan7 targets.
# The chipdb/nextpnr/prjxray come from the flake's per-system
# packages; the python deps for prjxray's fasm2frames come from
# openXC7's own pinned nixpkgs (a consistent 3.12 set).
openxc7 = inputs'.openxc7.packages;
openxc7Nixpkgs = inputs.openxc7.inputs.nixpkgs.legacyPackages.${system};
};
};

checks = {
Expand Down Expand Up @@ -158,6 +167,7 @@
fpgaVendors = [
"ecp5"
"ice40"
"spartan7"
];
asicVendors = [
"sky130"
Expand Down
2 changes: 1 addition & 1 deletion nix/common-dart.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ lib: {
pubspecLock = lib.importJSON ../pubspec.lock.json;

gitHashes = {
harbor = "sha256-Mnfls8OTiyFbRe7E3u/SvNDqQocxLpnMQpIDRSkq4uM=";
harbor = "sha256-FZd+6+ZqwIw50CgBRp4aC0bZhGJIamsuRuXplCiPGgs=";
};
}
4 changes: 4 additions & 0 deletions packages/river/lib/src/impl/core/v1.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class RiverCoreConfigV1 extends RiverCoreConfig {
// not just a perf choice. Microcode datapath (shared ALU) over the
// static fabric: ~12k vs ~21k LUT on small FPGAs.
microcodeMode: MicrocodeMode.full,
// Decode 2 pattern-ROM entries/cycle: halves the decode pattern-scan
// (the biggest per-instruction cost) for a couple of comparators of
// area, staying fully microcoded + patchable. Bigger tiers scale up.
microcodeDecodeLanes: 2,
);

/// RC1.f - River Core V1 full (RV64GC_Zicsr_Zifencei), in-order single-issue,
Expand Down
12 changes: 12 additions & 0 deletions packages/river/lib/src/river_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,17 @@ class RiverCoreConfig {
final List<InterruptController> interrupts;
final HarborMmuConfig mmu;
final MicrocodeMode microcodeMode;

/// Number of microcode-decode LANES: how many decode-pattern ROM entries the
/// dynamic decoder reads + compares against the instruction PER CYCLE. The
/// decoder's pattern search is O(patterns/lanes) cycles, so `lanes` trades a
/// few comparators of area for a proportionally shorter decode (the pattern
/// scan is the dominant per-instruction cost on the microcode datapath). 1 =
/// the plain one-per-cycle linear scan. Larger cores set more lanes; the ROM
/// stays ROM-driven and runtime-patchable either way. No effect unless
/// [microcodeMode] uses a standalone (ROM-scanning) decoder.
final int microcodeDecodeLanes;

final ExecutionMode executionMode;
final IssueWidth issueWidth;

Expand Down Expand Up @@ -300,6 +311,7 @@ class RiverCoreConfig {
required this.interrupts,
required this.mmu,
this.microcodeMode = MicrocodeMode.none,
this.microcodeDecodeLanes = 1,
this.executionMode = ExecutionMode.inOrder,
this.issueWidth = IssueWidth.single,
IssueWidth? commitWidth,
Expand Down
6 changes: 3 additions & 3 deletions packages/river_emulator/lib/src/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1017,9 +1017,9 @@ class RiverCore implements CsrContext {
(t) => t.causeCode == mop.causeCode && t.interrupt == mop.isInterrupt,
orElse: () => Trap.illegal,
);
// ecall: microcode hardcodes cause 8 (ecallU); real cause depends on
// originating mode: U/VU=8, HS=9, VS=10 (H), M=11.
if (mop.causeCode == 8 && !mop.isInterrupt) {
// The micro-op's modeCause bit re-encodes the cause by originating
// mode (ecall: U/VU=8, HS=9, VS=10 (H), M=11), matching the RTL.
if (mop.modeCause && !mop.isInterrupt) {
trapKind = switch (mode) {
PrivilegeMode.machine => Trap.ecallM,
PrivilegeMode.supervisor => virt ? Trap.ecallVS : Trap.ecallS,
Expand Down
34 changes: 28 additions & 6 deletions packages/river_hdl/lib/src/boards.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ class DdrBoard {
/// clean). False for boards whose write eye closes on its own (ECP5/x16).
final bool writeVerify;

/// Whether the controller runs hardware MPR read-calibration before opening
/// the bus (sequencer sweeps each byte lane's read window x IDELAY tap against
/// the DRAM MPR pattern and locks the eye). The openXC7 Arty ddr3Fast path
/// needs it: the CK-based ISERDESE2 capture eye drifts per boot, so a static
/// tap only boots ~half the time (the Ferrite first-ifetch coin-flip). False
/// for boards whose read eye is stable (ECP5 DQS-strobed capture).
final bool readLevel;

/// Post-read-cal cadence self-test gate (ddr3Fast). Requires [readLevel].
final bool selfTest;

/// Per-board DDR tuning defaults, forwarded to the controller when a memory
/// region does not override them (see genip's effective-value merge). Null
/// leaves the genip global default in force. cmdSlot/wrShift/wrBeat/window
Expand All @@ -60,6 +71,8 @@ class DdrBoard {
this.dqsComplementPins = const {},
this.trainableRead = false,
this.writeVerify = false,
this.readLevel = false,
this.selfTest = false,
this.cmdSlot,
this.wrShift,
this.wrBeat,
Expand Down Expand Up @@ -122,9 +135,11 @@ class DdrBoard {
colWidth: 10,
casLatency: 6,
),
// Read-cal PARKED OFF (see _artyS7x8): wedges the boot on HW.
readLevel: false,
pins: {
'sdram_ck': 'R5 SSTL135',
'sdram_ck_n': 'T4 SSTL135',
'sdram_ck': 'R5 DIFF_SSTL135',
'sdram_ck_n': 'T4 DIFF_SSTL135',
'sdram_cke': 'T2 SSTL135',
'sdram_cs_n': 'R3 SSTL135',
'sdram_ras_n': 'U1 SSTL135',
Expand Down Expand Up @@ -202,6 +217,11 @@ class DdrBoard {
// 90-degree phase inert): reads clean, a re-driven write always lands, so
// write-verify-retry makes CPU writes correct.
writeVerify: true,
// Read-cal (MPR eye sweep) is PARKED OFF: on HW the sRdCal FSM does not
// complete (sim-clean, unsimmable on the real PHY), so its bus gate wedges
// the whole wishbone before the FSBL prints. The RTL stays behind the flag
// (readlevel=true) for on-board debugging; the default boot is the base.
readLevel: false,
// HW-proven x8 DDR3 tuning, baked in so a plain build needs no per-region params.
cmdSlot: 2,
wrShift: -1,
Expand All @@ -210,8 +230,8 @@ class DdrBoard {
readRetry: 6,
window: 5,
pins: {
'sdram_ck': 'R5 SSTL135',
'sdram_ck_n': 'T4 SSTL135',
'sdram_ck': 'R5 DIFF_SSTL135',
'sdram_ck_n': 'T4 DIFF_SSTL135',
'sdram_cke': 'T2 SSTL135',
'sdram_cs_n': 'R3 SSTL135',
'sdram_ras_n': 'U1 SSTL135',
Expand Down Expand Up @@ -270,15 +290,17 @@ class DdrBoard {
casLatency: 6,
),
writeVerify: true,
// Read-cal PARKED OFF (see _artyS7x8): wedges the boot on HW.
readLevel: false,
cmdSlot: 2,
wrShift: -1,
wrBeat: 0,
trainable: true,
readRetry: 6,
window: 5,
pins: {
'sdram_ck': 'R5 SSTL135',
'sdram_ck_n': 'T4 SSTL135',
'sdram_ck': 'R5 DIFF_SSTL135',
'sdram_ck_n': 'T4 DIFF_SSTL135',
'sdram_cke': 'T2 SSTL135',
'sdram_cs_n': 'R3 SSTL135',
'sdram_ras_n': 'U1 SSTL135',
Expand Down
17 changes: 13 additions & 4 deletions packages/river_hdl/lib/src/compat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -511,13 +511,15 @@ final List<MicroOpEncoding> kMicroOpTable = [
'funct': BitRange(0, 4),
'causeCode': BitRange(5, 10),
'isInterrupt': BitRange(11, 11),
'modeCause': BitRange(12, 12),
}),
toMap: (mop) {
final m = mop as RiscVTrapOp;
return {
'funct': TrapMicroOp.funct,
'causeCode': m.causeCode,
'isInterrupt': m.isInterrupt ? 1 : 0,
'modeCause': m.modeCause ? 1 : 0,
};
},
),
Expand Down Expand Up @@ -545,10 +547,17 @@ final List<MicroOpEncoding> kMicroOpTable = [
5 + MicroOpLink.width + mxlen.size - 1,
),
}),
toMap: (mop) => {
'funct': WriteLinkRegisterMicroOp.funct,
'link': MicroOpLink.rd.value,
'pcOffset': 4,
toMap: (mop) {
// pcOffset is the instruction length that forms the link (return) address
// = PC + len. It MUST come from the op, not a fixed 4: compressed calls
// (c.jalr, rv_c.dart) carry pcOffset: 2, and hardcoding 4 here returned
// two bytes too far, breaking every function-pointer/vtable call on rc1-s.
final m = mop as RiscVWriteLinkRegister;
return {
'funct': WriteLinkRegisterMicroOp.funct,
'link': MicroOpLink.rd.value,
'pcOffset': m.pcOffset,
};
},
),
MicroOpEncoding(
Expand Down
Loading
Loading