diff --git a/exercises/110_quiz9.zig b/exercises/110_quiz9.zig index 4ac2032..171a509 100644 --- a/exercises/110_quiz9.zig +++ b/exercises/110_quiz9.zig @@ -68,40 +68,31 @@ const testing = std.testing; pub fn main() !void { var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity + + // The LCD display on our robot is not behaving as expected. In order to + // get it functioning properly, we must initialize it by sending the + // correct sequence of half-bytes to PORTB's lower four pins. // - // Let's first take a look at toggling bits. + // See if you can solve the following problems to get the lcd working and + // reveal the message our robot has stored in his EEPROM. // - // ------------------------------------------------------------------------ - // Toggling bits with XOR: - // ------------------------------------------------------------------------ - // XOR stands for "exclusive or". We can toggle bits with the ^ (XOR) - // bitwise operator, like so: + // .--. .--. + // | | | | + // +--------------------------+ + // | +----------------------+ | + // | | | | + // | | XXXXXXXX XXXXXXXX | | <-- LCD + // | | | | + // | +----------------------+ | + // | _________ | + // | |_|_|_|_|_| | + // | | + // +--------------------------+ + // | | // - // - // In order to output a 1, the logic of an XOR operation requires that the - // two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will - // both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior - // of outputing a 0 when both inputs are 1s is what makes it different from - // the OR operator; it also gives us the ability to toggle bits by putting - // 1s into our bitmask. - // - // - 1s in our bitmask operand, can be thought of as causing the - // corresponding bits in the other operand to flip to the opposite value. - // - 0s cause no change. - // - // The 0s in our bitmask preserve these values - // -XOR op- ---expanded--- in the output. - // _______________/ - // / / - // 0110 1 1 0 0 - // ^ 1111 0 1 0 1 (bitmask) - // ------ - - - - - // = 1001 1 0 0 1 <- This bit was already cleared. - // \_______\ - // \ - // We can think of these bits having flipped - // because of the presence of 1s in those columns - // of our bitmask. + // The last two problems throw you a bit of a curve ball. Try solving them + // on your own. If you need help, scroll to the bottom to see some in depth + // explanations on toggling, setting, and clearing bits in Zig. print("Toggle pins with XOR on PORTB\n", .{}); print("-----------------------------\n", .{}); @@ -121,49 +112,6 @@ pub fn main() !void { newline(); - // Now let's take a look at setting bits with the | operator. - // - // ------------------------------------------------------------------------ - // Setting bits with OR: - // ------------------------------------------------------------------------ - // We can set bits on PORTB with the | (OR) operator, like so: - // - // var PORTB: u4 = 0b1001; - // PORTB = PORTB | 0b0010; - // print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 - // - // -OR op- ---expanded--- - // _ Set only this bit. - // / - // 1001 1 0 0 1 - // | 0010 0 0 1 0 (bit mask) - // ------ - - - - - // = 1011 1 0 1 1 - // \___\_______\ - // \ - // These bits remain untouched because OR-ing with - // a 0 effects no change. - // - // ------------------------------------------------------------------------ - // To create a bit mask like 0b0010 used above: - // - // 1. First, shift the value 1 over one place with the bitwise << (shift - // left) operator as indicated below: - // 1 << 0 -> 0001 - // 1 << 1 -> 0010 <-- Shift 1 one place to the left - // 1 << 2 -> 0100 - // 1 << 3 -> 1000 - // - // This allows us to rewrite the above code like this: - // - // var PORTB: u4 = 0b1001; - // PORTB = PORTB | (1 << 1); - // print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 - // - // Finally, as in the C language, Zig allows us to use the |= operator, so - // we can rewrite our code again in an even more compact and idiomatic - // form: PORTB |= (1 << 1) - print("Set pins with OR on PORTB\n", .{}); print("-------------------------\n", .{}); @@ -183,86 +131,6 @@ pub fn main() !void { newline(); - // So now we've covered how to toggle and set bits. What about clearing - // them? Well, this is where Zig throws us a curve ball. Don't worry we'll - // go through it step by step. - - // ------------------------------------------------------------------------ - // Clearing bits with AND and NOT: - // ------------------------------------------------------------------------ - // We can clear bits with the & (AND) bitwise operator, like so: - - // PORTB = 0b1110; // reset PORTB - // PORTB = PORTB & 0b1011; - // print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010 - // - // - 0s clear bits when used in conjuction with a bitwise AND. - // - 1s do nothing, thus preserving the original bits. - // - // -AND op- ---expanded--- - // __________ Clear only this bit. - // / - // 1110 1 1 1 0 - // & 1011 1 0 1 1 (bit mask) - // ------ - - - - - // = 1010 1 0 1 0 <- This bit was already cleared. - // \_______\ - // \ - // These bits remain untouched because AND-ing with a - // 1 preserves the original bit value whether 0 or 1. - // - // ------------------------------------------------------------------------ - // We can use the ~ (NOT) operator to easily create a bit mask like 1011: - // - // 1. First, shift the value 1 over two places with the bit-wise << (shift - // left) operator as indicated below: - // 1 << 0 -> 0001 - // 1 << 1 -> 0010 - // 1 << 2 -> 0100 <- The 1 has been shifted two places to the left - // 1 << 3 -> 1000 - // - // 2. The second step in creating our bit mask is to invert the bits - // ~0100 -> 1011 - // in C we would write this as: - // ~(1 << 2) -> 1011 - // - // But if we try to compile ~(1 << 2) in Zig, we'll get an error: - // unable to perform binary not operation on type 'comptime_int' - // - // Before Zig can invert our bits, it needs to know the number of - // bits it's being asked to invert. - // - // We do this with the @as (cast as) built-in like this: - // @as(u4, 1 << 2) -> 0100 - // - // Finally, we can invert our new mask by placing the NOT ~ operator - // before our expression, like this: - // ~@as(u4, 1 << 2) -> 1011 - // - // If you are offput by the fact that you can't simply invert bits like - // you can in languages such as C without casting to a particular size - // of integer, you're not alone. However, this is actually another - // instance where Zig is really helpful because it protects you from - // difficult to debug integer overflow bugs that can have you tearing - // your hair out. In the interest of keeping things sane, Zig requires - // you simply to tell it the size of number you are inverting. In the - // words of Andrew Kelley, "If you want to invert the bits of an - // integer, zig has to know how many bits there are." - // - // For more insight into the Zig team's position on why the language - // takes the approach it does with the ~ operator, take a look at - // Andrew's comments on the following github issue: - // https://github.com/ziglang/zig/issues/1382#issuecomment-414459529 - // - // Whew, so after all that what we end up with is: - // PORTB = PORTB & ~@as(u4, 1 << 2); - // - // We can shorten this with the &= combined AND and assignment operator, - // which applies the AND operator on PORTB and then reassigns PORTB. Here's - // what that looks like: - // PORTB &= ~@as(u4, 1 << 2); - // - print("Clear pins with AND and NOT on PORTB\n", .{}); print("------------------------------------\n", .{}); @@ -283,37 +151,222 @@ pub fn main() !void { newline(); newline(); - // ------------------------------------------------------------------------ - // Conclusion - // ------------------------------------------------------------------------ - // - // While the examples in this exercise have used only 4-bit wide variables, - // working with 8 bits is no different. Here's a an example where we set - // every other bit beginning with the two's place: - - // var PORTD: u8 = 0b0000_0000; - // print("PORTD: {b:0>8}\n", .{PORTD}); - // PORTD |= (1 << 1); - // PORTD = setBit(u8, PORTD, 3); - // PORTD |= (1 << 5) | (1 << 7); - // print("PORTD: {b:0>8} // set every other bit\n", .{PORTD}); - // PORTD = ~PORTD; - // print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); - // newline(); - // - // // Here we clear every other bit beginning with the two's place. - // - // PORTD = 0b1111_1111; - // print("PORTD: {b:0>8}\n", .{PORTD}); - // PORTD &= ~@as(u8, 1 << 1); - // PORTD = clearBit(u8, PORTD, 3); - // PORTD &= ~@as(u8, (1 << 5) | (1 << 7)); - // print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD}); - // PORTD = ~PORTD; - // print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); - // newline(); } + +// ************************************************************************ +// IN-DEPTH EXPLANATIONS BELOW +// ************************************************************************ + + + + + + + + + + + + +// ------------------------------------------------------------------------ +// Toggling bits with XOR: +// ------------------------------------------------------------------------ +// XOR stands for "exclusive or". We can toggle bits with the ^ (XOR) +// bitwise operator, like so: +// +// +// In order to output a 1, the logic of an XOR operation requires that the +// two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will +// both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior +// of outputing a 0 when both inputs are 1s is what makes it different from +// the OR operator; it also gives us the ability to toggle bits by putting +// 1s into our bitmask. +// +// - 1s in our bitmask operand, can be thought of as causing the +// corresponding bits in the other operand to flip to the opposite value. +// - 0s cause no change. +// +// The 0s in our bitmask preserve these values +// -XOR op- ---expanded--- in the output. +// _______________/ +// / / +// 0110 1 1 0 0 +// ^ 1111 0 1 0 1 (bitmask) +// ------ - - - - +// = 1001 1 0 0 1 <- This bit was already cleared. +// \_______\ +// \ +// We can think of these bits having flipped +// because of the presence of 1s in those columns +// of our bitmask. +// +// Now let's take a look at setting bits with the | operator. +// + + + + + + +// ------------------------------------------------------------------------ +// Setting bits with OR: +// ------------------------------------------------------------------------ +// We can set bits on PORTB with the | (OR) operator, like so: +// +// var PORTB: u4 = 0b1001; +// PORTB = PORTB | 0b0010; +// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 +// +// -OR op- ---expanded--- +// _ Set only this bit. +// / +// 1001 1 0 0 1 +// | 0010 0 0 1 0 (bit mask) +// ------ - - - - +// = 1011 1 0 1 1 +// \___\_______\ +// \ +// These bits remain untouched because OR-ing with +// a 0 effects no change. +// +// ------------------------------------------------------------------------ +// To create a bit mask like 0b0010 used above: +// +// 1. First, shift the value 1 over one place with the bitwise << (shift +// left) operator as indicated below: +// 1 << 0 -> 0001 +// 1 << 1 -> 0010 <-- Shift 1 one place to the left +// 1 << 2 -> 0100 +// 1 << 3 -> 1000 +// +// This allows us to rewrite the above code like this: +// +// var PORTB: u4 = 0b1001; +// PORTB = PORTB | (1 << 1); +// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 +// +// Finally, as in the C language, Zig allows us to use the |= operator, so +// we can rewrite our code again in an even more compact and idiomatic +// form: PORTB |= (1 << 1) + +// So now we've covered how to toggle and set bits. What about clearing +// them? Well, this is where Zig throws us a curve ball. Don't worry we'll +// go through it step by step. + + + + + + +// ------------------------------------------------------------------------ +// Clearing bits with AND and NOT: +// ------------------------------------------------------------------------ +// We can clear bits with the & (AND) bitwise operator, like so: + +// PORTB = 0b1110; // reset PORTB +// PORTB = PORTB & 0b1011; +// print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010 +// +// - 0s clear bits when used in conjuction with a bitwise AND. +// - 1s do nothing, thus preserving the original bits. +// +// -AND op- ---expanded--- +// __________ Clear only this bit. +// / +// 1110 1 1 1 0 +// & 1011 1 0 1 1 (bit mask) +// ------ - - - - +// = 1010 1 0 1 0 <- This bit was already cleared. +// \_______\ +// \ +// These bits remain untouched because AND-ing with a +// 1 preserves the original bit value whether 0 or 1. +// +// ------------------------------------------------------------------------ +// We can use the ~ (NOT) operator to easily create a bit mask like 1011: +// +// 1. First, shift the value 1 over two places with the bit-wise << (shift +// left) operator as indicated below: +// 1 << 0 -> 0001 +// 1 << 1 -> 0010 +// 1 << 2 -> 0100 <- The 1 has been shifted two places to the left +// 1 << 3 -> 1000 +// +// 2. The second step in creating our bit mask is to invert the bits +// ~0100 -> 1011 +// in C we would write this as: +// ~(1 << 2) -> 1011 +// +// But if we try to compile ~(1 << 2) in Zig, we'll get an error: +// unable to perform binary not operation on type 'comptime_int' +// +// Before Zig can invert our bits, it needs to know the number of +// bits it's being asked to invert. +// +// We do this with the @as (cast as) built-in like this: +// @as(u4, 1 << 2) -> 0100 +// +// Finally, we can invert our new mask by placing the NOT ~ operator +// before our expression, like this: +// ~@as(u4, 1 << 2) -> 1011 +// +// If you are offput by the fact that you can't simply invert bits like +// you can in languages such as C without casting to a particular size +// of integer, you're not alone. However, this is actually another +// instance where Zig is really helpful because it protects you from +// difficult to debug integer overflow bugs that can have you tearing +// your hair out. In the interest of keeping things sane, Zig requires +// you simply to tell it the size of number you are inverting. In the +// words of Andrew Kelley, "If you want to invert the bits of an +// integer, zig has to know how many bits there are." +// +// For more insight into the Zig team's position on why the language +// takes the approach it does with the ~ operator, take a look at +// Andrew's comments on the following github issue: +// https://github.com/ziglang/zig/issues/1382#issuecomment-414459529 +// +// Whew, so after all that what we end up with is: +// PORTB = PORTB & ~@as(u4, 1 << 2); +// +// We can shorten this with the &= combined AND and assignment operator, +// which applies the AND operator on PORTB and then reassigns PORTB. Here's +// what that looks like: +// PORTB &= ~@as(u4, 1 << 2); +// + +// ------------------------------------------------------------------------ +// Conclusion +// ------------------------------------------------------------------------ +// +// While the examples in this quiz have used only 4-bit wide variables, +// working with 8 bits is no different. Here's a an example where we set +// every other bit beginning with the two's place: + +// var PORTD: u8 = 0b0000_0000; +// print("PORTD: {b:0>8}\n", .{PORTD}); +// PORTD |= (1 << 1); +// PORTD = setBit(u8, PORTD, 3); +// PORTD |= (1 << 5) | (1 << 7); +// print("PORTD: {b:0>8} // set every other bit\n", .{PORTD}); +// PORTD = ~PORTD; +// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); +// newline(); +// +// // Here we clear every other bit beginning with the two's place. +// +// PORTD = 0b1111_1111; +// print("PORTD: {b:0>8}\n", .{PORTD}); +// PORTD &= ~@as(u8, 1 << 1); +// PORTD = clearBit(u8, PORTD, 3); +// PORTD &= ~@as(u8, (1 << 5) | (1 << 7)); +// print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD}); +// PORTD = ~PORTD; +// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); +// newline(); + + + // ---------------------------------------------------------------------------- // Here are some helper functions for manipulating bits // ----------------------------------------------------------------------------