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 bc5dcd6e..a39bf0d4 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/man5/srec_gowin.5 b/doc/man5/srec_gowin.5
new file mode 100644
index 00000000..422408a9
--- /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"
+.SH NAME
+srec_gowin \- Gowin bitstream file format
+.if require_index \{
+.XX "srec_gowin(5)" "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/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