Skip to content

Added bitstream reader and writer #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 18, 2025
Merged
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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
This command-line tool converts [FASM][fasm-spec] files into bitstreams, simplifying the assembly of human-readable FPGA configurations into the binary formats needed to program various FPGAs.

At this stage, it can generate a set of frames in the same manner as [fasm2frames](https://github.com/chipsalliance/f4pga-xc-fasm/blob/25dc605c9c0896204f0c3425b52a332034cf5e5c/xc_fasm/fasm2frames.py).
It has been tested with the Artix-7 [counter example][counter-example], where it produces identical frames—at approximately 10 times the speed compared to the pure Python textX based parser implementation.
It has been tested with the Artix-7 [counter example][counter-example], where it produces identical frames—at and a working bitstream at approximately 10 times the speed compared to the pure Python textX based parser implementation.

## Usage

First, install [Bazel][bazel] and ensure you have a basic C/C++ toolchain set up. Then run:

```
bazel run -c opt //fpga:fpga-as -- --prjxray_db_path=/some/path/prjxray-db/artix7 --part=xc7a35tcsg324-1 < /some/path.fasm
bazel run -c opt //fpga:fpga-as -- --prjxray_db_path=/some/path/prjxray-db/artix7 --part=xc7a35tcsg324-1 < /some/path.fasm > output.bit
```

Finally, load the bitstream in your FPGA using [openFPGALoader][open-fpga-loader]

```
openFPGALoader -b arty output.bit
```

## Installation
Expand Down Expand Up @@ -59,3 +65,4 @@ Using this metadata, you can search the segbits database for the specific featur
[fasm-spec]: https://fasm.readthedocs.io/en/stable/#
[bazel]: https://bazel.build/
[counter-example]: https://github.com/chipsalliance/f4pga-examples/blob/13f11197b33dae1cde3bf146f317d63f0134eacf/xc7/counter_test/counter.v
[open-fpga-loader]: https://github.com/trabucayre/openFPGALoader
4 changes: 4 additions & 0 deletions fpga/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load("@rules_license//rules:license.bzl", "license")

package(
default_applicable_licenses = [":license"],
default_visibility = ["//:__subpackages__"],
)

license(
Expand Down Expand Up @@ -38,6 +39,7 @@ cc_library(
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings:str_format",
"@abseil-cpp//absl/types:span",
],
)

Expand Down Expand Up @@ -134,6 +136,8 @@ cc_binary(
":database-parsers",
":fasm-parser",
":memory-mapped-file",
"//fpga/xilinx:arch-types",
"//fpga/xilinx:bitstream",
"@abseil-cpp//absl/cleanup:cleanup",
"@abseil-cpp//absl/container:flat_hash_map",
"@abseil-cpp//absl/container:flat_hash_set",
Expand Down
16 changes: 14 additions & 2 deletions fpga/assembler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "fpga/database-parsers.h"
#include "fpga/database.h"
#include "fpga/fasm-parser.h"
#include "fpga/xilinx/arch-types.h"
#include "fpga/xilinx/bitstream.h"

struct TileSiteInfo {
std::string tile;
Expand Down Expand Up @@ -330,6 +331,7 @@ static absl::StatusOr<std::string> GetOptFlagOrFromEnv(
return flag_value.value();
}

#if 0
static void GetPrettyFrameLine(
const uint32_t address,
const std::array<fpga::word_t, fpga::kFrameWordCount> &bits,
Expand Down Expand Up @@ -369,6 +371,7 @@ static void PrintFrames(const fpga::Frames &frames, std::ostream &out,
}
}
}
#endif

int main(int argc, char *argv[]) {
const std::string usage = Usage(argv[0]);
Expand Down Expand Up @@ -430,7 +433,16 @@ int main(int argc, char *argv[]) {
<< '\n';
return EXIT_FAILURE;
}
PrintFrames(frames, std::cout, false);
const fpga::Part &part_data = part_database_result->tiles().part;
const auto bitstream_status =
fpga::xilinx::BitStream<fpga::xilinx::Architecture::kXC7>::Encode<
fpga::Frames>(part_data, "fasm", "fpga-source", frames, std::cout);
if (!bitstream_status.ok()) {
std::cerr << StatusToErrorMessage("could not generate bistream",
bitstream_status)
<< '\n';
return EXIT_FAILURE;
}
// Write bitstream
return EXIT_SUCCESS;
}
11 changes: 8 additions & 3 deletions fpga/database-parsers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "absl/base/optimization.h"
Expand Down Expand Up @@ -354,9 +355,13 @@ absl::StatusOr<Part> Unmarshal(const rapidjson::Value &json) {
struct Part part;
OK_OR_RETURN(IsObject(json));
ASSIGN_OR_RETURN(part.idcode, GetMember<uint32_t>(json, "idcode"));
ASSIGN_OR_RETURN(
part.iobanks,
(GetMember<absl::flat_hash_map<uint32_t, std::string>>(json, "iobanks")));
std::optional<IOBanksIDsToLocation> iobanks;
ASSIGN_OR_RETURN(iobanks,
(OptGetMember<absl::flat_hash_map<uint32_t, std::string>>(
json, "iobanks")));
if (iobanks) {
part.iobanks = std::move(iobanks.value());
}
ASSIGN_OR_RETURN(part.global_clock_regions, (GetMember<GlobalClockRegions>(
json, "global_clock_regions")));
return part;
Expand Down
2 changes: 1 addition & 1 deletion fpga/database-parsers.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ struct SegmentBit {
// Word index of the bit to enable.
uint32_t word_bit;

// If the char '!' is prepended.
// False if the char '!' is prepended.
bool is_set;
};

Expand Down
39 changes: 19 additions & 20 deletions fpga/database.cc
Original file line number Diff line number Diff line change
Expand Up @@ -280,19 +280,7 @@ absl::StatusOr<SegmentsBitsWithPseudoPIPs> ParseTileTypeDatabase(
}

absl::StatusOr<fpga::BanksTilesRegistry> CreateBanksRegistry(
const std::filesystem::path &part_json_path,
const std::filesystem::path &package_pins_path) {
// Parse part.json.
const absl::StatusOr<std::unique_ptr<fpga::MemoryBlock>> part_json_result =
fpga::MemoryMapFile(part_json_path);
if (!part_json_result.ok()) return part_json_result.status();
const absl::StatusOr<fpga::Part> part_result =
fpga::ParsePartJSON(part_json_result.value()->AsStringView());
if (!part_result.ok()) {
return part_result.status();
}
const fpga::Part &part = part_result.value();

const fpga::Part &part, const std::filesystem::path &package_pins_path) {
// Parse package pins.
const absl::StatusOr<std::unique_ptr<fpga::MemoryBlock>>
package_pins_csv_result = fpga::MemoryMapFile(package_pins_path);
Expand Down Expand Up @@ -344,16 +332,27 @@ absl::StatusOr<PartDatabase> PartDatabase::Parse(std::string_view database_path,
}
return {};
};
auto banks_tiles_registry_result = CreateBanksRegistry(
std::filesystem::path(database_path) / part_name / "part.json",
std::filesystem::path(database_path) / part_name / "package_pins.csv");
// Parse part.json.
const absl::StatusOr<std::unique_ptr<fpga::MemoryBlock>> part_json_result =
fpga::MemoryMapFile(std::filesystem::path(database_path) / part_name /
"part.json");
if (!part_json_result.ok()) return part_json_result.status();
const absl::StatusOr<fpga::Part> part_result =
fpga::ParsePartJSON(part_json_result.value()->AsStringView());
if (!part_result.ok()) {
return part_result.status();
}
const fpga::Part &part = part_result.value();

auto banks_tiles_registry_result =
CreateBanksRegistry(part, std::filesystem::path(database_path) / part_name /
"package_pins.csv");
if (!banks_tiles_registry_result.ok()) {
return banks_tiles_registry_result.status();
}
const Tiles tiles_foo(std::move(tilegrid_result.value()),
std::move(tiles_database),
std::move(banks_tiles_registry_result.value()));
const std::shared_ptr<Tiles> tiles = std::make_shared<Tiles>(tiles_foo);
const std::shared_ptr<Tiles> tiles = std::make_shared<Tiles>(
std::move(tilegrid_result.value()), std::move(tiles_database),
std::move(banks_tiles_registry_result.value()), part);
return absl::StatusOr<PartDatabase>(tiles);
}

Expand Down
6 changes: 4 additions & 2 deletions fpga/database.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,15 @@ class PartDatabase {
~PartDatabase() = default;
struct Tiles {
Tiles(TileGrid grid, TileTypesSegmentsBitsGetter bits,
BanksTilesRegistry banks)
BanksTilesRegistry banks, Part part)
: grid(std::move(grid)),
bits(std::move(bits)),
banks(std::move(banks)) {}
banks(std::move(banks)),
part(std::move(part)) {}
TileGrid grid;
TileTypesSegmentsBitsGetter bits;
BanksTilesRegistry banks;
Part part;
};
explicit PartDatabase(std::shared_ptr<Tiles> part_tiles)
: tiles_(std::move(part_tiles)) {}
Expand Down
6 changes: 6 additions & 0 deletions fpga/memory-mapped-file.h
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
#ifndef FPGA_MEMORY_MAPPED_FILE_H
#define FPGA_MEMORY_MAPPED_FILE_H

#include <cstdint>
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>

#include "absl/status/statusor.h"
#include "absl/types/span.h"

namespace fpga {
// Taken from: verible/common/strings/mem-block.h
class MemoryBlock {
public:
virtual ~MemoryBlock() = default;
virtual std::string_view AsStringView() const = 0;
absl::Span<const uint8_t> AsBytesView() const {
std::string_view const bytes = AsStringView();
return {reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()};
}

protected:
MemoryBlock() = default;
Expand Down
99 changes: 99 additions & 0 deletions fpga/xilinx/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ cc_library(
],
deps = [
":arch-xc7-frame",
"//fpga:database-parsers",
"//fpga:memory-mapped-file",
"@abseil-cpp//absl/container:btree",
"@abseil-cpp//absl/log:check",
"@abseil-cpp//absl/status:statusor",
],
)

Expand Down Expand Up @@ -195,6 +200,7 @@ cc_library(
":arch-xc7-configuration-packet",
":bit-ops",
":configuration-packet",
"@abseil-cpp//absl/container:btree",
"@abseil-cpp//absl/log:check",
"@abseil-cpp//absl/types:optional",
"@abseil-cpp//absl/types:span",
Expand All @@ -206,15 +212,108 @@ cc_test(
srcs = [
"configuration-xc7_test.cc",
],
data = [
"testdata/xc7-configuration.bit",
"testdata/xc7-configuration.debug.bit",
"testdata/xc7-configuration.perframecrc.bit",
"testdata/xc7-configuration-test.json",
],
deps = [
":arch-types",
":arch-xc7-frame",
":bitstream-reader",
":configuration",
":configuration-packet",
":frames",
"//fpga:database-parsers",
"//fpga:memory-mapped-file",
"@abseil-cpp//absl/log:check",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/types:optional",
"@abseil-cpp//absl/types:span",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)

cc_library(
name = "bitstream-reader",
hdrs = [
"bitstream-reader.h",
],
deps = [
":arch-types",
":big-endian-span",
"@abseil-cpp//absl/types:optional",
"@abseil-cpp//absl/types:span",
],
)

cc_test(
name = "bitstream-reader_test",
srcs = [
"bitstream-reader-xc7_test.cc",
],
deps = [
":arch-types",
":bitstream-reader",
"@abseil-cpp//absl/types:span",
"@googletest//:gtest_main",
],
)

cc_library(
name = "bitstream-writer",
srcs = [
"bitstream-writer.cc",
],
hdrs = [
"bitstream-writer.h",
],
deps = [
":arch-types",
":arch-xc7-configuration-packet",
":bit-ops",
":configuration-packet",
"@abseil-cpp//absl/log:check",
"@abseil-cpp//absl/strings",
"@abseil-cpp//absl/time",
"@abseil-cpp//absl/types:optional",
"@abseil-cpp//absl/types:span",
],
)

cc_test(
name = "bitstream-writer_test",
srcs = [
"bitstream-writer_test.cc",
],
deps = [
":arch-types",
":arch-xc7-configuration-packet",
":bit-ops",
":bitstream-writer",
":configuration-packet",
"@abseil-cpp//absl/types:span",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)

cc_library(
name = "bitstream",
hdrs = [
"bitstream.h",
],
deps = [
":arch-types",
":bitstream-writer",
":configuration",
":frames",
"//fpga:database-parsers",
"@abseil-cpp//absl/container:btree",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings:string_view",
],
)
1 change: 0 additions & 1 deletion fpga/xilinx/arch-xc7-configuration-packet.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ class ConfigurationPacket
private:
using BaseType =
ConfigurationPacketBase<ConfigurationRegister, ConfigurationPacket>;
using ParseResult = BaseType::ParseResult;

public:
using BaseType::BaseType;
Expand Down
1 change: 1 addition & 0 deletions fpga/xilinx/arch-xc7-frame.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ std::ostream &operator<<(std::ostream &o, BlockType value) {
case BlockType::kBlockRam: o << "Block RAM"; break;
case BlockType::kCFGCLB: o << "Config CLB"; break;
case BlockType::kReserved: o << "Reserved"; break;
case BlockType::kInvalid: o << "Invalid"; break;
}
return o;
}
Expand Down
1 change: 1 addition & 0 deletions fpga/xilinx/arch-xc7-frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ enum class BlockType : unsigned int {
kBlockRam = 0x1,
kCFGCLB = 0x2,
kReserved = 0x3,
kInvalid = 0xFFFFFFFF,
};

std::ostream &operator<<(std::ostream &o, BlockType value);
Expand Down
Loading