Прошивка моста BL616 для Tang Primer 25K и программатор TangProgrammer

- src/: прошивка встроенного отладчика BL616 — составное USB-устройство
  1209:C0CA: DirtyJTAG v3 (WinUSB через MS OS 2.0) + CDC-ACM UART к FPGA
  B3/C3; UART до 20 Мбод на DMA, JTAG до 30 МГц через аппаратный SPI;
- bin/: загрузчик Sipeed (0x0) и штатная прошивка 2025030317 для отката;
- gui/: TangProgrammer.exe (C# WinForms, .NET Framework 4) — загрузка FPGA
  в SRAM и во флеш-память, копия и стирание флеш-памяти FPGA, прошивка
  моста; светлая и тёмная тема;
- scripts/: setup, build, flash, build_gui, make_portable, а также
  make_release и publish_release для релизов Gitea;
- fpga_test/, tests/: тестовые проекты Gowin (loopback, stream) и тесты
  UART на Python;
- tools/openFPGALoader/: openFPGALoader 1.1.1 для Windows x64.
This commit is contained in:
2026-10-02 14:59:10 +03:00
commit 8bc1583805
57 changed files with 6439 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
# Build the firmware: build\build_out\tang25k_debugger_bl616.bin
param([switch]$Clean)
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'common.ps1')
$root = Get-ProjectRoot
Set-BuildEnv $root
Push-Location $root
try {
if ($Clean) {
Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue
}
& make.exe CHIP=bl616 BOARD=bl616dk
if ($LASTEXITCODE) { throw "build failed ($LASTEXITCODE)" }
$bin = Get-Item build\build_out\tang25k_debugger_bl616.bin
Write-Host ("OK: {0} ({1} bytes)" -f $bin.FullName, $bin.Length) -ForegroundColor Green
} finally {
Pop-Location
}
+25
View File
@@ -0,0 +1,25 @@
# Build TangProgrammer.exe (the GUI for openFPGALoader and the bridge firmware)
# with the C# compiler that comes with Windows (.NET Framework 4.x): nothing
# has to be installed.
#
# .\scripts\build_gui.ps1 -> TangProgrammer.exe in the project root
# .\scripts\build_gui.ps1 -Out some.exe -> elsewhere
param([string]$Out)
$ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent
$src = Join-Path $root 'gui'
if (-not $Out) { $Out = Join-Path $root 'TangProgrammer.exe' }
$csc = Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319\csc.exe'
if (-not (Test-Path $csc)) {
$csc = Join-Path $env:WINDIR 'Microsoft.NET\Framework\v4.0.30319\csc.exe'
}
if (-not (Test-Path $csc)) { throw '.NET Framework 4 C# compiler (csc.exe) not found' }
$sources = Get-ChildItem (Join-Path $src '*.cs') | ForEach-Object { $_.FullName }
& $csc /nologo /target:winexe /optimize+ /codepage:65001 "/out:$Out" `
"/win32icon:$src\app.ico" "/win32manifest:$src\app.manifest" "/resource:$src\app.ico,TangProgrammer.app.ico" `
/r:System.dll /r:System.Drawing.dll /r:System.Windows.Forms.dll /r:System.Management.dll `
$sources
if ($LASTEXITCODE) { throw "csc failed ($LASTEXITCODE) - is $Out running?" }
Write-Host "OK: $Out" -ForegroundColor Green
+42
View File
@@ -0,0 +1,42 @@
# Shared helpers for build/flash scripts.
# A mapped drive that points back to a share on this very PC (D: -> \\THISPC\Projects)
# makes every compiler launch go through SMB and antivirus scanning: builds
# get ~50x slower. Translate such paths to the local folder behind the share.
function Resolve-LocalPath([string]$Path) {
$full = (Resolve-Path $Path).ProviderPath
$drive = Get-PSDrive -Name $full.Substring(0, 1) -ErrorAction SilentlyContinue
if ($drive -and $drive.DisplayRoot -match '^\\\\([^\\]+)\\([^\\]+)') {
if ($Matches[1] -ieq $env:COMPUTERNAME) {
$share = Get-CimInstance Win32_Share -Filter "Name='$($Matches[2])'" -ErrorAction SilentlyContinue
if ($share) {
return Join-Path $share.Path $full.Substring(3)
}
}
}
return $full
}
function Get-ProjectRoot {
return Resolve-LocalPath (Join-Path $PSScriptRoot '..')
}
function Set-BuildEnv([string]$Root) {
$sdk = Join-Path $Root 'third_party\bouffalo_sdk'
$tc = Join-Path $Root 'third_party\toolchain_gcc_t-head_windows\bin'
if (-not (Test-Path $sdk) -or -not (Test-Path $tc)) {
throw "SDK/toolchain not found in $Root\third_party - run scripts\setup.ps1 first"
}
$env:BL_SDK_BASE = $sdk
$env:PATH = "$tc;$sdk\tools\make;$sdk\tools\ninja;$sdk\tools\cmake\bin;$env:SystemRoot\System32;$env:SystemRoot"
}
# COM port name(s) of present devices whose instance id matches the pattern
function Find-ComPort([string]$IdPattern) {
Get-CimInstance Win32_PnPEntity -Filter "PNPClass='Ports'" -ErrorAction SilentlyContinue |
Where-Object { $_.PNPDeviceID -match $IdPattern -and $_.Name -match '\((COM\d+)\)' } |
ForEach-Object { [regex]::Match($_.Name, '\((COM\d+)\)').Groups[1].Value }
}
$BootromPortId = 'VID_349B&PID_6160' # BL616 ROM USB bootloader "Bouffalo CDC DEMO"
$FirmwarePortId = 'VID_1209&PID_C0CA&MI_01' # this firmware's CDC UART
+72
View File
@@ -0,0 +1,72 @@
# Flash the BL616 on the Tang Primer 25K Dock.
#
# .\scripts\flash.ps1 this firmware (Sipeed loader @0x0 + firmware @0x20000)
# .\scripts\flash.ps1 -Restore original Sipeed debugger firmware
#
# The BL616 has to be in its ROM USB bootloader ("Bouffalo CDC DEMO"). The
# script gets it there by itself when this firmware is already running
# (serial port opened at 1234 baud). Otherwise, unplug the board and plug it
# back while either
# - holding the S3 button (this firmware is installed), or
# - shorting the TDO test point to 3V3 (bottom side, always works).
param(
[switch]$Restore,
[string]$Port
)
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'common.ps1')
$root = Get-ProjectRoot
$sdk = Join-Path $root 'third_party\bouffalo_sdk'
$tool = Join-Path $sdk 'tools\bflb_tools\bouffalo_flash_cube\BLFlashCommand.exe'
$cfg = if ($Restore) { 'restore_prog_cfg.ini' } else { 'flash_prog_cfg.ini' }
if (-not $Restore -and -not (Test-Path (Join-Path $root 'build\build_out\tang25k_debugger_bl616.bin'))) {
throw 'firmware not built yet: run scripts\build.ps1'
}
function Wait-BootromPort([int]$Seconds) {
$deadline = (Get-Date).AddSeconds($Seconds)
do {
$p = @(Find-ComPort $BootromPortId)
if ($p.Count) { return $p[0] }
Start-Sleep -Milliseconds 300
} while ((Get-Date) -lt $deadline)
return $null
}
if (-not $Port) {
$Port = @(Find-ComPort $BootromPortId) | Select-Object -First 1
}
if (-not $Port) {
$fw = @(Find-ComPort $FirmwarePortId) | Select-Object -First 1
if ($fw) {
Write-Host "Firmware found on $fw, rebooting it into the ROM bootloader..."
try {
$sp = New-Object System.IO.Ports.SerialPort $fw, 1234
$sp.Open()
Start-Sleep -Milliseconds 200
$sp.Close()
} catch {
Write-Warning "could not open ${fw}: $_ (is a terminal using it?)"
}
$Port = Wait-BootromPort 15
}
}
if (-not $Port) {
Write-Host 'Waiting for the BL616 ROM bootloader (Bouffalo CDC DEMO)...' -ForegroundColor Yellow
Write-Host ' unplug the board, then plug it back while holding S3'
Write-Host ' (or while shorting the TDO test point to 3V3 on the bottom side)'
$Port = Wait-BootromPort 120
}
if (-not $Port) { throw 'BL616 bootloader port not found' }
Write-Host "Flashing $cfg via $Port" -ForegroundColor Cyan
Push-Location $root
try {
& $tool --interface=uart --baudrate=2000000 --port=$Port --chipname=bl616 --cpu_id= --config=$cfg
if ($LASTEXITCODE) { throw "BLFlashCommand failed ($LASTEXITCODE)" }
} finally {
Pop-Location
}
Write-Host 'Done. Unplug and plug the board back (power cycle) to start the new firmware.' -ForegroundColor Green
+80
View File
@@ -0,0 +1,80 @@
# Make a portable copy of TangProgrammer for another PC: the program and only
# the tools it runs (no SDK, no toolchain, no sources).
#
# .\scripts\make_portable.ps1 -> dist\TangProgrammer\ and dist\TangProgrammer.zip
#
# The FPGA and FPGA-flash tabs need TangProgrammer.exe + tools\openFPGALoader.
# The bridge firmware tab also needs BLFlashCommand (for flashing over the USB
# bootloader only the exe and chips\ are used, not the 200+ MB of debug probe
# tools around it), the firmware images and the flash configs.
$ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent
$dist = Join-Path $root 'dist'
$out = Join-Path $dist 'TangProgrammer'
$cube = Join-Path $root 'third_party\bouffalo_sdk\tools\bflb_tools\bouffalo_flash_cube'
$bridgeFw = Join-Path $root 'build\build_out\tang25k_debugger_bl616.bin'
if (Test-Path $out) { Remove-Item -Recurse -Force $out }
New-Item -ItemType Directory -Force (Join-Path $out 'tools') | Out-Null
& (Join-Path $PSScriptRoot 'build_gui.ps1') -Out (Join-Path $out 'TangProgrammer.exe')
Copy-Item -Recurse (Join-Path $root 'tools\openFPGALoader') (Join-Path $out 'tools\openFPGALoader')
if (Test-Path (Join-Path $cube 'BLFlashCommand.exe')) {
$dst = Join-Path $out 'tools\bouffalo_flash_cube'
New-Item -ItemType Directory -Force $dst | Out-Null
Copy-Item (Join-Path $cube 'BLFlashCommand.exe') $dst
Copy-Item -Recurse (Join-Path $cube 'chips') $dst
} else {
Write-Warning 'Bouffalo SDK is not installed (scripts\setup.ps1): the bridge firmware tab will not work in the copy'
}
$fwDir = Join-Path $out 'firmware'
New-Item -ItemType Directory -Force $fwDir | Out-Null
Copy-Item (Join-Path $root 'bin\bl616_bootloader_0x20000_nano20k_signed.bin') $fwDir
Copy-Item (Join-Path $root 'bin\bl616_fpga_partner_25kDock.bin') $fwDir
if (Test-Path $bridgeFw) {
Copy-Item $bridgeFw $fwDir
} else {
Write-Warning 'bridge firmware is not built (scripts\build.ps1): it is missing from the copy'
}
# the same configs as in the project root, pointing at firmware\
Set-Content -Encoding ASCII (Join-Path $out 'flash_prog_cfg.ini') @'
[cfg]
# 0: no erase, 1: programmed section erase, 2: chip erase
erase = 1
skip_mode = 0x0, 0x0
boot2_isp_mode = 0
# Sipeed-encrypted stage-1 loader: the eFuse secure boot only runs images
# encrypted with Sipeed's key at 0x0. It unconditionally jumps to 0x20000.
[bootloader]
filedir = ./firmware/bl616_bootloader_0x20000_nano20k_signed.bin
address = 0x0
[FW]
filedir = ./firmware/tang25k_debugger_bl616.bin
address = 0x20000
'@
Set-Content -Encoding ASCII (Join-Path $out 'restore_prog_cfg.ini') @'
[cfg]
# 0: no erase, 1: programmed section erase, 2: chip erase
erase = 1
skip_mode = 0x0, 0x0
boot2_isp_mode = 0
# Original Sipeed debugger firmware (FT2232 emulation), version 2025030317
[FW]
filedir = ./firmware/bl616_fpga_partner_25kDock.bin
address = 0x0
'@
Copy-Item (Join-Path $root 'gui\portable_readme.txt') (Join-Path $out 'README.txt')
$zip = Join-Path $dist 'TangProgrammer.zip'
if (Test-Path $zip) { Remove-Item -Force $zip }
Compress-Archive -Path $out -DestinationPath $zip
$size = (Get-ChildItem -Recurse $out | Measure-Object -Sum Length).Sum
Write-Host ("OK: {0} ({1:N1} MB), {2} ({3:N1} MB)" -f $out, ($size / 1MB), $zip, ((Get-Item $zip).Length / 1MB)) -ForegroundColor Green
+56
View File
@@ -0,0 +1,56 @@
# Collect the files of a release into dist\release\v<version>\:
#
# TangProgrammer-v<version>-win64.zip portable programmer (make_portable.ps1)
# TangProgrammer.exe the program alone
# tang25k_debugger_bl616.bin bridge firmware, @0x20000
# bl616_bootloader_0x20000_nano20k_signed.bin Sipeed loader, @0x0
# bl616_fpga_partner_25kDock.bin original Sipeed firmware, @0x0
# fpga_test_bitstreams-GW5A-25.zip fpga_test\*\impl\pnr\*.fs, if built
# SHA256SUMS.txt
#
# .\scripts\build.ps1
# .\scripts\make_release.ps1 version from FW_VERSION_STR in src\sys.h
param([string]$Version)
$ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent
if (-not $Version) {
$m = Select-String -Path (Join-Path $root 'src\sys.h') -Pattern '#define\s+FW_VERSION_STR\s+"([^"]+)"'
if (-not $m) { throw 'FW_VERSION_STR not found in src\sys.h' }
$Version = $m.Matches[0].Groups[1].Value
}
$tag = 'v' + $Version.TrimStart('v')
$bridgeFw = Join-Path $root 'build\build_out\tang25k_debugger_bl616.bin'
if (-not (Test-Path $bridgeFw)) { throw 'bridge firmware is not built: run scripts\build.ps1' }
if (-not (Test-Path (Join-Path $root 'third_party\bouffalo_sdk\tools\bflb_tools\bouffalo_flash_cube\BLFlashCommand.exe'))) {
throw 'Bouffalo SDK is not installed (scripts\setup.ps1): the portable copy needs BLFlashCommand'
}
$out = Join-Path $root "dist\release\$tag"
if (Test-Path $out) { Remove-Item -Recurse -Force $out }
New-Item -ItemType Directory -Force $out | Out-Null
& (Join-Path $PSScriptRoot 'make_portable.ps1')
Copy-Item (Join-Path $root 'dist\TangProgrammer.zip') (Join-Path $out "TangProgrammer-$tag-win64.zip")
Copy-Item (Join-Path $root 'dist\TangProgrammer\TangProgrammer.exe') $out
Copy-Item $bridgeFw $out
Copy-Item (Join-Path $root 'bin\bl616_bootloader_0x20000_nano20k_signed.bin') $out
Copy-Item (Join-Path $root 'bin\bl616_fpga_partner_25kDock.bin') $out
$fs = @(Get-ChildItem (Join-Path $root 'fpga_test\*\impl\pnr\*.fs') -ErrorAction SilentlyContinue)
if ($fs.Count) {
Compress-Archive -Path ($fs | ForEach-Object { $_.FullName }) -DestinationPath (Join-Path $out 'fpga_test_bitstreams-GW5A-25.zip')
} else {
Write-Warning 'no test bitstreams: build them with gw_sh build.tcl in fpga_test\loopback and fpga_test\stream'
}
# sha256sum format, so that "sha256sum -c SHA256SUMS.txt" works
$sums = Get-ChildItem $out -File | Sort-Object Name | ForEach-Object {
(Get-FileHash -Algorithm SHA256 $_.FullName).Hash.ToLower() + ' ' + $_.Name
}
[IO.File]::WriteAllText((Join-Path $out 'SHA256SUMS.txt'), ($sums -join "`n") + "`n")
Get-ChildItem $out -File | Sort-Object Name | Format-Table Name, @{ n = 'KB'; e = { [math]::Ceiling($_.Length / 1KB) }; a = 'right' } -AutoSize
Write-Host "OK: $out" -ForegroundColor Green
+91
View File
@@ -0,0 +1,91 @@
# Publish dist\release\v<version>\ (made by make_release.ps1) as a Gitea
# release: the release text is the version's section of CHANGELOG.md. The tag
# has to be pushed already. Re-running uploads only the files that are missing.
#
# Needs a Gitea access token with the write:repository scope
# (Settings -> Applications -> Generate New Token):
#
# $env:GITEA_TOKEN = '<token>'
# .\scripts\publish_release.ps1 version from src\sys.h
# .\scripts\publish_release.ps1 -Version 1.0.1
param(
[string]$Version,
[string]$Token = $env:GITEA_TOKEN,
[string]$Remote = 'origin'
)
$ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent
if (-not $Version) {
$m = Select-String -Path (Join-Path $root 'src\sys.h') -Pattern '#define\s+FW_VERSION_STR\s+"([^"]+)"'
if (-not $m) { throw 'FW_VERSION_STR not found in src\sys.h' }
$Version = $m.Matches[0].Groups[1].Value
}
$Version = $Version.TrimStart('v')
$tag = 'v' + $Version
$dir = Join-Path $root "dist\release\$tag"
if (-not (Test-Path $dir)) { throw "$dir not found: run scripts\make_release.ps1 first" }
if (-not $Token) { throw 'no token: set $env:GITEA_TOKEN (Gitea: Settings -> Applications -> Generate New Token, scope write:repository)' }
# http://server/owner/repo.git -> http://server/api/v1/repos/owner/repo
$url = (& git -C $root remote get-url $Remote).Trim()
if ($url -notmatch '^(https?://[^/]+)/(.+?)(\.git)?/?$') { throw "not an http(s) remote: $url" }
$api = "$($Matches[1])/api/v1/repos/$($Matches[2])"
# the release text: the lines under "## v<version>" up to the next "## "
$notes = New-Object System.Collections.Generic.List[string]
$inside = $false
foreach ($line in [IO.File]::ReadAllLines((Join-Path $root 'CHANGELOG.md'), [Text.Encoding]::UTF8)) {
if ($line -match '^## ') {
if ($inside) { break }
$inside = $line -match "^## v?$([regex]::Escape($Version))\b"
} elseif ($inside) {
$notes.Add($line)
}
}
if (-not $inside) { throw "no '## $tag' section in CHANGELOG.md" }
$body = ($notes -join "`n").Trim()
Add-Type -AssemblyName System.Net.Http
$http = New-Object System.Net.Http.HttpClient
$http.Timeout = [TimeSpan]::FromMinutes(10)
$http.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue 'token', $Token
function Invoke-Gitea([string]$Method, [string]$Path, [System.Net.Http.HttpContent]$Content) {
$req = New-Object System.Net.Http.HttpRequestMessage ([System.Net.Http.HttpMethod]::new($Method)), ($api + $Path)
if ($Content) { $req.Content = $Content }
$resp = $http.SendAsync($req).GetAwaiter().GetResult()
$text = $resp.Content.ReadAsStringAsync().GetAwaiter().GetResult()
$code = [int]$resp.StatusCode
if ($code -ge 400 -and $code -ne 404) { throw "$Method $Path -> $code $text" }
return [pscustomobject]@{ Code = $code; Json = $(if ($text) { $text | ConvertFrom-Json } else { $null }) }
}
if ((Invoke-Gitea GET "/tags/$tag").Code -eq 404) {
throw "tag $tag is not on the server: git tag -a $tag -m $tag; git push $Remote $tag"
}
$r = Invoke-Gitea GET "/releases/tags/$tag"
if ($r.Code -eq 404) {
$json = ConvertTo-Json @{ tag_name = $tag; name = $tag; body = $body; draft = $false; prerelease = $false }
$r = Invoke-Gitea POST '/releases' (New-Object System.Net.Http.StringContent $json, ([Text.Encoding]::UTF8), 'application/json')
Write-Host "Created release $tag"
} else {
Write-Host "Release $tag exists, adding missing files"
}
$release = $r.Json
$have = @($release.assets | ForEach-Object { $_.name })
foreach ($f in Get-ChildItem $dir -File | Sort-Object Name) {
if ($have -contains $f.Name) {
Write-Host " = $($f.Name) (already there)"
continue
}
$file = [System.Net.Http.ByteArrayContent]::new([IO.File]::ReadAllBytes($f.FullName))
$file.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::new('application/octet-stream')
$form = New-Object System.Net.Http.MultipartFormDataContent
$form.Add($file, 'attachment', $f.Name)
Invoke-Gitea POST "/releases/$($release.id)/assets?name=$([Uri]::EscapeDataString($f.Name))" $form | Out-Null
Write-Host (" + {0} ({1:N0} KB)" -f $f.Name, ($f.Length / 1KB))
}
Write-Host "OK: $($release.html_url)" -ForegroundColor Green
+15
View File
@@ -0,0 +1,15 @@
# Fetch the Bouffalo SDK and the T-Head RISC-V toolchain into third_party\.
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'common.ps1')
$root = Get-ProjectRoot
$tp = Join-Path $root 'third_party'
New-Item -ItemType Directory -Force $tp | Out-Null
if (-not (Test-Path (Join-Path $tp 'bouffalo_sdk'))) {
git clone --depth 1 --branch v2.3.16 https://github.com/bouffalolab/bouffalo_sdk.git (Join-Path $tp 'bouffalo_sdk')
}
if (-not (Test-Path (Join-Path $tp 'toolchain_gcc_t-head_windows'))) {
git clone --depth 1 https://github.com/bouffalolab/toolchain_gcc_t-head_windows.git (Join-Path $tp 'toolchain_gcc_t-head_windows')
}
Write-Host 'SDK and toolchain are in place.' -ForegroundColor Green