diff --git a/README.md b/README.md
index 69511a5d..d0c6902f 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ The SRecord package understands a number of file formats:
* **Fairchild Fairbug**: input/output
* **Formatted Binary**: input/output
* **Four Packed Code (FPC)**: input/output
+* **Gowin fs**: input;
* **Hexdump**: output only; a simple hexdump
* **HP64000 Absolute**: input only
* **IDT/sim**: input/output
diff --git a/doc/dictionaries/names.txt b/doc/dictionaries/names.txt
index 844eb274..de9e24db 100644
--- a/doc/dictionaries/names.txt
+++ b/doc/dictionaries/names.txt
@@ -57,6 +57,7 @@ Giancristofaro
Gracic
GraphViz
groff
+Gowin
Hanspeter
Heilig
hexdump
diff --git a/doc/etc/README.man b/doc/etc/README.man
index dc0b7acd..d0600a16 100644
--- a/doc/etc/README.man
+++ b/doc/etc/README.man
@@ -103,6 +103,9 @@ The Formatted Binary format is understood for both reading and writing.
Four Packed Code (FPC)
The FPC format is understood for both reading and writing.
.\" ---------- G ---------------------------------------------------------
+.TP
+Gowin Bitstream
+The Gowin bitstream format is understood for reading only.
.\" ---------- H ---------------------------------------------------------
.TP
Hexdump
diff --git a/doc/html/index.html b/doc/html/index.html
index 2e53e888..0c9d416e 100644
--- a/doc/html/index.html
+++ b/doc/html/index.html
@@ -169,6 +169,11 @@
The FPC format is understood for both reading and writing.
.\" ---------- G ---------------------------------------------------------
+
+Gowin bitstream
+
+The Gowin bitstream format is understood for reading only.
+
.\" ---------- H ---------------------------------------------------------
Hexdump
diff --git a/doc/man1/srec_input.1 b/doc/man1/srec_input.1
index 821274b6..67510474 100644
--- a/doc/man1/srec_input.1
+++ b/doc/man1/srec_input.1
@@ -122,6 +122,10 @@ This option says to use the FPC format to read the file.
See \f[I]srec_fpc\fP(5) for a description of this file format.
.\" ---------- G ---------------------------------------------------------
.TP 8n
+\fB\-GOwin_fs\fP
+This option says to use the Gowin bitstream format to read the file.
+See \f[I]srec_gowin\fP(5) for a description of this file format.
+.TP 8n
\fB\-Guess\fP
This option may be used to ask the command to guess the input format.
This is slower than specifying an explicit format,
diff --git a/doc/man5/srec_gowin.5 b/doc/man5/srec_gowin.5
new file mode 100644
index 00000000..2777c766
--- /dev/null
+++ b/doc/man5/srec_gowin.5
@@ -0,0 +1,56 @@
+'\" t
+.\" srecord - manipulate eprom load files
+.\" Copyright (C) 2023 Daniel Anselmi
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation; either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+.\" GNU General Public License for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with this program. If not, see
+.\" .
+.\"
+.ds n) srec_gowin
+.TH \*(n) 5 SRecord "Reference Manual"
+.if require_index \{
+.XX "srec_gowin(5)" "Gowin bitstream file format"
+.\}
+.SH NAME
+srec_gowin \- Gowin bitstream file format
+.SH DESCRIPTION
+This format is the Gowin bitstream file format. It is generated by the IDE from Gowin to program their FPGAs.
+
+Lines starting with "//" contain comments.
+Each byte is represented by a string containing 8 chars of '0' or '1' for each byte.
+A line can contain multiple bytes.
+There is no spacing between multiple bytes except an optional newline.
+Each line contains a multiple of 8 bits (except comment lines).
+
+There is no addressing and no checksum in this format.
+
+.SH EXAMPLE
+Here is an example Gowin bitstream file.
+It contains the data \[lq]Hello, World\[rq].
+.RS
+.nf
+.ft CW
+//comment
+01001000
+0110010101101100
+01101100
+0110111100101100
+00100000
+010101110110111101110010
+0110110001100100
+.ft P
+.fi
+.RE
+
+.ds n) srec_cat
+.so man1/z_copyright.so
diff --git a/srecord/arglex/tool.cc b/srecord/arglex/tool.cc
index 2382a7e4..3c4011ca 100644
--- a/srecord/arglex/tool.cc
+++ b/srecord/arglex/tool.cc
@@ -119,6 +119,7 @@ srecord::arglex_tool::arglex_tool(int argc, char **argv) :
{ "-GENerate", token_generator },
{ "-GENerator", token_generator },
{ "-Gnu_CRypt", token_gcrypt }, // undocumented
+ { "-GOwin_fs", token_gowin_fs },
{ "-GUess", token_guess, },
{ "-HAVal", token_haval, },
{ "-HEXadecimal_Dump", token_hexdump, },
diff --git a/srecord/arglex/tool.h b/srecord/arglex/tool.h
index f2a462eb..cacdd8c4 100644
--- a/srecord/arglex/tool.h
+++ b/srecord/arglex/tool.h
@@ -112,6 +112,7 @@ class arglex_tool:
token_four_packed_code,
token_gcrypt,
token_generator,
+ token_gowin_fs,
token_guess,
token_haval,
token_hexdump,
diff --git a/srecord/arglex/tool/input.cc b/srecord/arglex/tool/input.cc
index a2fb9540..7699a8e0 100644
--- a/srecord/arglex/tool/input.cc
+++ b/srecord/arglex/tool/input.cc
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -347,6 +348,11 @@ srecord::arglex_tool::get_simple_input()
ifp = input_file_four_packed_code::create(fn);
break;
+ case token_gowin_fs:
+ token_next();
+ ifp = input_file_gowin_fs::create(fn);
+ break;
+
case token_guess:
token_next();
ifp = input_file::guess(fn, *this);
diff --git a/srecord/input/file/gowin_fs.cc b/srecord/input/file/gowin_fs.cc
new file mode 100644
index 00000000..567406ad
--- /dev/null
+++ b/srecord/input/file/gowin_fs.cc
@@ -0,0 +1,128 @@
+//
+// srecord - manipulate eprom load files
+// Copyright (C) 2023 Daniel Anselmi
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or (at your
+// option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+// License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see .
+//
+
+#include
+#include
+
+#include
+#include
+#include
+
+
+srecord::input_file_gowin_fs::input_file_gowin_fs(
+ const std::string &a_filename
+) :
+ input_file(a_filename),
+ address(0),
+ done(false)
+{
+}
+
+srecord::input_file::pointer
+srecord::input_file_gowin_fs::create(const std::string &a_filename)
+{
+ return pointer(new input_file_gowin_fs(a_filename));
+}
+
+// See base class for documentation.
+bool srecord::input_file_gowin_fs::read(record &record)
+{
+ if (done)
+ return false;
+
+ int length = 0;
+ srecord::record::data_t data[srecord::record::max_data_length];
+
+ for (;;)
+ {
+ int c = peek_char();
+ if (c == EOF)
+ {
+ done = true;
+ break;
+ }
+ else if (c == '/')
+ {
+ skip_until_eol();
+ continue;
+ }
+ else if (c == '\n' || c == ' ' || c == '\t')
+ {
+ // Files from Gowin IDE don't have spaces or tabs between the
+ // data or lines with only spaces. We accept them here anyway.
+ get_char();
+ continue;
+ }
+
+ /* reading 8 chars to get a byte */
+ srecord::record::data_t d = get_byte_binary();
+ data[length++] = d;
+
+ if (length >= (int)sizeof(data))
+ break;
+ }
+
+ record = srecord::record(srecord::record::type_data, address, data, length);
+ address += length;
+ return true;
+}
+
+void
+srecord::input_file_gowin_fs::skip_until_eol()
+{
+ while (get_char() != '\n');
+}
+
+int
+srecord::input_file_gowin_fs::get_byte_binary()
+{
+ int val = 0;
+ for (int i = 0x80; i; i >>= 1)
+ {
+ int c = get_binary_digit_value();
+ if (c < 0)
+ fatal_error("expecting binary digit");
+ if (c)
+ val |= i;
+ }
+ return val;
+}
+
+int
+srecord::input_file_gowin_fs::get_binary_digit_value()
+{
+ int c = get_char();
+ if (c != '0' && c != '1')
+ return -1;
+ return c - '0';
+}
+
+const char *
+srecord::input_file_gowin_fs::get_file_format_name()
+ const
+{
+ return "Gowin bitstream";
+}
+
+
+int
+srecord::input_file_gowin_fs::format_option_number()
+ const
+{
+ return arglex_tool::token_gowin_fs;
+}
diff --git a/srecord/input/file/gowin_fs.h b/srecord/input/file/gowin_fs.h
new file mode 100644
index 00000000..6e00ecdf
--- /dev/null
+++ b/srecord/input/file/gowin_fs.h
@@ -0,0 +1,120 @@
+//
+// srecord - manipulate eprom load files
+// Copyright (C) 2023 Daniel Anselmi
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or (at your
+// option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+// License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see .
+//
+
+#ifndef SRECORD_INPUT_GOWIN_FS_H
+#define SRECORD_INPUT_GOWIN_FS_H
+
+#include
+
+namespace srecord {
+
+/**
+ * The srecord::input_file_gowin_fs class is used to represent the parse
+ * state when reading a gowin .fs file.
+ */
+class input_file_gowin_fs:
+ public input_file
+{
+public:
+ /**
+ * The destructor.
+ */
+ virtual ~input_file_gowin_fs() = default;
+
+ /**
+ * The create class method is used to create new dynamically
+ * allocated instances of this class.
+ *
+ * @param file_name
+ * The name of the file to be read.
+ * @returns
+ * smart pointer to new instance
+ */
+ static pointer create(const std::string &file_name);
+
+ /**
+ * The default constructor.
+ */
+ input_file_gowin_fs() = delete;
+
+ /**
+ * The copy constructor.
+ */
+ input_file_gowin_fs(const input_file_gowin_fs &) = delete;
+
+ /**
+ * The assignment operator. Do not use.
+ */
+ input_file_gowin_fs &operator=(const input_file_gowin_fs &) = delete;
+
+protected:
+ // See base class for documentation.
+ bool read(record &record);
+
+ // See base class for documentation.
+ const char *get_file_format_name() const;
+
+ // See base class for documentation.
+ int format_option_number() const;
+
+private:
+ /**
+ * The constructor.
+ *
+ * @param file_name
+ * The name of the file to be read.
+ */
+ input_file_gowin_fs(const std::string &file_name);
+
+ /**
+ * The address instance variable is used to remember the current
+ * address of the next data record. This is set and advanced by
+ * the #read method.
+ */
+ uint32_t address;
+
+ /**
+ * The done instance variable is used to remember that we
+ * don't expect more input data.
+ */
+ bool done;
+
+ /**
+ * The get_byte_binary method is used to fetch a byte value from
+ * the input. It calls get_binary_digit_value 8 times, and
+ * assembles them big-endian (most significant bit first).
+ */
+ int get_byte_binary();
+
+ /**
+ * The get_binary_digit_value method is used to fetch one binary digit
+ * from the input, via the get_char method. It returns a value from 0 to 1.
+ * If there's currently not a binary digit in the input stream
+ * it returns -1.
+ */
+ int get_binary_digit_value();
+
+ /**
+ * skip input until there is a newline in the input.
+ */
+ void skip_until_eol();
+};
+
+};
+
+#endif // SRECORD_INPUT_GOWIN_FS_H
diff --git a/srecord/input/file/guess.cc b/srecord/input/file/guess.cc
index bb0a6118..e0ca13be 100644
--- a/srecord/input/file/guess.cc
+++ b/srecord/input/file/guess.cc
@@ -32,6 +32,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -78,6 +79,7 @@ static func_p table[] =
srecord::input_file_fastload::create,
srecord::input_file_formatted_binary::create,
srecord::input_file_four_packed_code::create,
+ srecord::input_file_gowin_fs::create,
srecord::input_file_hp64k::create,
srecord::input_file_idt::create,
srecord::input_file_intel::create,
diff --git a/srecord/srecord.h b/srecord/srecord.h
index b1a0d70f..cb9722ce 100644
--- a/srecord/srecord.h
+++ b/srecord/srecord.h
@@ -36,6 +36,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/test/02/t0268a.sh b/test/02/t0268a.sh
new file mode 100755
index 00000000..ebc698c9
--- /dev/null
+++ b/test/02/t0268a.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# srecord - Manipulate EPROM load files
+# Copyright (C) 2023 Daniel Anselmi
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or (at
+# your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+TEST_SUBJECT="input gowin"
+. test_prelude.sh
+
+cat > test.bit << 'fubar'
+//comment line
+01001000
+0110010101101100
+01100101
+0110111100101100
+00100000
+010101110110111101110010
+0110110001100100
+fubar
+if test $? -ne 0; then no_result; fi
+
+cat > test.ok << 'fubar'
+S0220000687474703A2F2F737265636F72642E736F75726365666F7267652E6E65742F1D
+S10F000048656C656F2C20576F726C64AF
+S5030001FB
+fubar
+if test $? -ne 0; then no_result; fi
+
+srec_cat test.bit -gowin -o test.out
+if test $? -ne 0; then fail; fi
+
+diff test.ok test.out
+if test $? -ne 0; then fail; fi
+
+#
+# The things tested here, worked.
+# No other guarantees are made.
+#
+pass